OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library barback.test.utils; | 5 library barback.test.utils; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 | 10 |
11 import 'package:barback/barback.dart'; | 11 import 'package:barback/barback.dart'; |
12 import 'package:barback/src/asset_cascade.dart'; | 12 import 'package:barback/src/asset_cascade.dart'; |
13 import 'package:barback/src/asset_set.dart'; | |
14 import 'package:barback/src/cancelable_future.dart'; | |
13 import 'package:barback/src/package_graph.dart'; | 15 import 'package:barback/src/package_graph.dart'; |
14 import 'package:barback/src/utils.dart'; | 16 import 'package:barback/src/utils.dart'; |
15 import 'package:path/path.dart' as pathos; | 17 import 'package:path/path.dart' as pathos; |
16 import 'package:scheduled_test/scheduled_test.dart'; | 18 import 'package:scheduled_test/scheduled_test.dart'; |
17 import 'package:stack_trace/stack_trace.dart'; | 19 import 'package:stack_trace/stack_trace.dart'; |
18 import 'package:unittest/compact_vm_config.dart'; | 20 import 'package:unittest/compact_vm_config.dart'; |
19 | 21 |
22 export 'transformer/bad.dart'; | |
23 export 'transformer/check_content.dart'; | |
24 export 'transformer/create_asset.dart'; | |
25 export 'transformer/many_to_one.dart'; | |
26 export 'transformer/mock.dart'; | |
27 export 'transformer/one_to_many.dart'; | |
28 export 'transformer/rewrite.dart'; | |
29 | |
20 var _configured = false; | 30 var _configured = false; |
21 | 31 |
22 MockProvider _provider; | 32 MockProvider _provider; |
23 PackageGraph _graph; | 33 PackageGraph _graph; |
24 | 34 |
25 /// Calls to [buildShouldSucceed] and [buildShouldFail] set expectations on | 35 /// Calls to [buildShouldSucceed] and [buildShouldFail] set expectations on |
26 /// successive [BuildResult]s from [_graph]. This keeps track of how many calls | 36 /// successive [BuildResult]s from [_graph]. This keeps track of how many calls |
27 /// have already been made so later calls know which result to look for. | 37 /// have already been made so later calls know which result to look for. |
28 int _nextBuildResult; | 38 int _nextBuildResult; |
29 | 39 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
89 /// Schedules a change to the contents of an asset identified by [name] to | 99 /// Schedules a change to the contents of an asset identified by [name] to |
90 /// [contents]. | 100 /// [contents]. |
91 /// | 101 /// |
92 /// Does not update it in the graph. | 102 /// Does not update it in the graph. |
93 void modifyAsset(String name, String contents) { | 103 void modifyAsset(String name, String contents) { |
94 schedule(() { | 104 schedule(() { |
95 _provider._modifyAsset(name, contents); | 105 _provider._modifyAsset(name, contents); |
96 }, "modify asset $name"); | 106 }, "modify asset $name"); |
97 } | 107 } |
98 | 108 |
109 /// Schedules an error when loading the asset identified by [name]. | |
Bob Nystrom
2013/07/30 22:25:02
"error" -> "error to be generated".
nweiz
2013/07/30 22:46:21
Done.
| |
110 /// | |
111 /// Does not update the asset in the graph. | |
112 void setAssetError(String name) { | |
113 schedule(() { | |
114 _provider._setAssetError(name); | |
115 }, "set error for asset $name"); | |
116 } | |
117 | |
99 /// Schedules a pause of the internally created [PackageProvider]. | 118 /// Schedules a pause of the internally created [PackageProvider]. |
100 /// | 119 /// |
101 /// All asset requests that the [PackageGraph] makes to the provider after this | 120 /// All asset requests that the [PackageGraph] makes to the provider after this |
102 /// will not complete until [resumeProvider] is called. | 121 /// will not complete until [resumeProvider] is called. |
103 void pauseProvider() { | 122 void pauseProvider() { |
104 schedule(() => _provider._pause(), "pause provider"); | 123 schedule(() => _provider._pause(), "pause provider"); |
105 } | 124 } |
106 | 125 |
107 /// Schedules an unpause of the provider after a call to [pauseProvider] and | 126 /// Schedules an unpause of the provider after a call to [pauseProvider] and |
108 /// allows all pending asset loads to finish. | 127 /// allows all pending asset loads to finish. |
109 void resumeProvider() { | 128 void resumeProvider() { |
110 schedule(() => _provider._resume(), "resume provider"); | 129 schedule(() => _provider._resume(), "resume provider"); |
111 } | 130 } |
112 | 131 |
113 /// Asserts that the current build step shouldn't have finished by this point in | 132 /// Asserts that the current build step shouldn't have finished by this point in |
114 /// the schedule. | 133 /// the schedule. |
115 /// | 134 /// |
116 /// This uses the same build counter as [buildShouldSucceed] and | 135 /// This uses the same build counter as [buildShouldSucceed] and |
117 /// [buildShouldFail], so those can be used to validate build results before and | 136 /// [buildShouldFail], so those can be used to validate build results before and |
118 /// after this. | 137 /// after this. |
119 void buildShouldNotBeDone() { | 138 void buildShouldNotBeDone() { |
120 var resultAllowed = false; | 139 _futureShouldNotCompleteUntil( |
121 var trace = new Trace.current(); | 140 _graph.results.elementAt(_nextBuildResult), |
122 _graph.results.elementAt(_nextBuildResult).then((result) { | 141 schedule(() => pumpEventQueue(), "build should not terminate"), |
123 if (resultAllowed) return; | 142 "build"); |
124 | |
125 currentSchedule.signalError( | |
126 new Exception("Expected build not to terminate " | |
127 "here, but it terminated with result: $result"), trace); | |
128 }).catchError((error) { | |
129 if (resultAllowed) return; | |
130 currentSchedule.signalError(error); | |
131 }); | |
132 | |
133 schedule(() { | |
134 // Pump the event queue in case the build completes out-of-band after we get | |
135 // here. If it does, we want to signal an error. | |
136 return pumpEventQueue().then((_) { | |
137 resultAllowed = true; | |
138 }); | |
139 }, "ensuring build doesn't terminate"); | |
140 } | 143 } |
141 | 144 |
142 /// Expects that the next [BuildResult] is a build success. | 145 /// Expects that the next [BuildResult] is a build success. |
143 void buildShouldSucceed() { | 146 void buildShouldSucceed() { |
144 expect(_getNextBuildResult().then((result) { | 147 expect(_getNextBuildResult().then((result) { |
148 result.errors.forEach(currentSchedule.signalError); | |
145 expect(result.succeeded, isTrue); | 149 expect(result.succeeded, isTrue); |
146 }), completes); | 150 }), completes); |
147 } | 151 } |
148 | 152 |
149 /// Expects that the next [BuildResult] emitted is a failure. | 153 /// Expects that the next [BuildResult] emitted is a failure. |
150 /// | 154 /// |
151 /// [matchers] is a list of matchers to match against the errors that caused the | 155 /// [matchers] is a list of matchers to match against the errors that caused the |
152 /// build to fail. Every matcher is expected to match an error, but the order of | 156 /// build to fail. Every matcher is expected to match an error, but the order of |
153 /// matchers is unimportant. | 157 /// matchers is unimportant. |
154 void buildShouldFail(List matchers) { | 158 void buildShouldFail(List matchers) { |
(...skipping 28 matching lines...) Expand all Loading... | |
183 void expectAsset(String name, [String contents]) { | 187 void expectAsset(String name, [String contents]) { |
184 var id = new AssetId.parse(name); | 188 var id = new AssetId.parse(name); |
185 | 189 |
186 if (contents == null) { | 190 if (contents == null) { |
187 contents = pathos.basenameWithoutExtension(id.path); | 191 contents = pathos.basenameWithoutExtension(id.path); |
188 } | 192 } |
189 | 193 |
190 schedule(() { | 194 schedule(() { |
191 return _graph.getAssetById(id).then((asset) { | 195 return _graph.getAssetById(id).then((asset) { |
192 // TODO(rnystrom): Make an actual Matcher class for this. | 196 // TODO(rnystrom): Make an actual Matcher class for this. |
193 expect(asset, new isInstanceOf<MockAsset>()); | |
194 expect(asset.id, equals(id)); | 197 expect(asset.id, equals(id)); |
195 expect(asset.contents, equals(contents)); | 198 expect(asset.readAsString(), completion(equals(contents))); |
196 }); | 199 }); |
197 }, "get asset $name"); | 200 }, "get asset $name"); |
198 } | 201 } |
199 | 202 |
200 /// Schedules an expectation that the graph will not find an asset matching | 203 /// Schedules an expectation that the graph will not find an asset matching |
201 /// [name]. | 204 /// [name]. |
202 void expectNoAsset(String name) { | 205 void expectNoAsset(String name) { |
203 var id = new AssetId.parse(name); | 206 var id = new AssetId.parse(name); |
204 | 207 |
205 // Make sure the future gets the error. | 208 // Make sure the future gets the error. |
206 schedule(() { | 209 schedule(() { |
207 return _graph.getAssetById(id).then((asset) { | 210 return _graph.getAssetById(id).then((asset) { |
208 fail("Should have thrown error but got $asset."); | 211 fail("Should have thrown error but got $asset."); |
209 }).catchError((error) { | 212 }).catchError((error) { |
210 expect(error, new isInstanceOf<AssetNotFoundException>()); | 213 expect(error, new isInstanceOf<AssetNotFoundException>()); |
211 expect(error.id, equals(id)); | 214 expect(error.id, equals(id)); |
212 }); | 215 }); |
213 }, "get asset $name"); | 216 }, "get asset $name"); |
214 } | 217 } |
215 | 218 |
219 /// Schedules an expectation that a [getAssetById] call for the given asset | |
220 /// won't terminate at this point in the schedule. | |
221 void expectAssetDoesNotComplete(String name) { | |
222 var id = new AssetId.parse(name); | |
223 | |
224 schedule(() { | |
225 return _futureShouldNotCompleteUntil( | |
226 _graph.getAssetById(id), | |
227 pumpEventQueue(), | |
228 "asset $id"); | |
229 }, "asset $id should not complete"); | |
230 } | |
231 | |
216 /// Returns a matcher for an [AssetNotFoundException] with the given [id]. | 232 /// Returns a matcher for an [AssetNotFoundException] with the given [id]. |
217 Matcher isAssetNotFoundException(String name) { | 233 Matcher isAssetNotFoundException(String name) { |
218 var id = new AssetId.parse(name); | 234 var id = new AssetId.parse(name); |
219 return allOf( | 235 return allOf( |
220 new isInstanceOf<AssetNotFoundException>(), | 236 new isInstanceOf<AssetNotFoundException>(), |
221 predicate((error) => error.id == id, 'id is $name')); | 237 predicate((error) => error.id == id, 'id is $name')); |
222 } | 238 } |
223 | 239 |
224 /// Returns a matcher for an [AssetCollisionException] with the given [id]. | 240 /// Returns a matcher for an [AssetCollisionException] with the given [id]. |
225 Matcher isAssetCollisionException(String name) { | 241 Matcher isAssetCollisionException(String name) { |
(...skipping 14 matching lines...) Expand all Loading... | |
240 /// Returns a matcher for an [InvalidOutputException] with the given id and | 256 /// Returns a matcher for an [InvalidOutputException] with the given id and |
241 /// package name. | 257 /// package name. |
242 Matcher isInvalidOutputException(String package, String name) { | 258 Matcher isInvalidOutputException(String package, String name) { |
243 var id = new AssetId.parse(name); | 259 var id = new AssetId.parse(name); |
244 return allOf( | 260 return allOf( |
245 new isInstanceOf<InvalidOutputException>(), | 261 new isInstanceOf<InvalidOutputException>(), |
246 predicate((error) => error.package == package, 'package is $package'), | 262 predicate((error) => error.package == package, 'package is $package'), |
247 predicate((error) => error.id == id, 'id is $name')); | 263 predicate((error) => error.id == id, 'id is $name')); |
248 } | 264 } |
249 | 265 |
266 /// Returns a matcher for a [MockLoadException] with the given [id]. | |
267 Matcher isMockLoadException(String name) { | |
268 var id = new AssetId.parse(name); | |
269 return allOf( | |
270 new isInstanceOf<MockLoadException>(), | |
271 predicate((error) => error.id == id, 'id is $name')); | |
Bob Nystrom
2013/07/30 22:25:02
"is" -> "=="
nweiz
2013/07/30 22:46:21
Done.
| |
272 } | |
273 | |
274 /// Asserts that [future] shouldn't complete until after [delay] completes. | |
275 /// | |
276 /// Once [delay] completes, the output of [future] is ignored, even if it's an | |
277 /// error. | |
278 /// | |
279 /// [description] should describe [future]. | |
280 Future _futureShouldNotCompleteUntil(Future future, Future delay, | |
281 String description) { | |
282 var trace = new Trace.current(); | |
283 var cancelable = new CancelableFuture(future); | |
284 cancelable.then((result) { | |
285 currentSchedule.signalError( | |
286 new Exception("Expected $description not to complete here, but it " | |
287 "completed with result: $result"), | |
288 trace); | |
289 }).catchError((error) { | |
290 currentSchedule.signalError(error); | |
291 }); | |
292 | |
293 return delay.then((_) => cancelable.cancel()); | |
294 } | |
295 | |
250 /// An [AssetProvider] that provides the given set of assets. | 296 /// An [AssetProvider] that provides the given set of assets. |
251 class MockProvider implements PackageProvider { | 297 class MockProvider implements PackageProvider { |
252 Iterable<String> get packages => _packages.keys; | 298 Iterable<String> get packages => _packages.keys; |
253 | 299 |
254 Map<String, _MockPackage> _packages; | 300 Map<String, _MockPackage> _packages; |
255 | 301 |
302 final _errors = new Set<AssetId>(); | |
Bob Nystrom
2013/07/30 22:25:02
Document.
nweiz
2013/07/30 22:46:21
Done.
| |
303 | |
256 /// The completer that [getAsset()] is waiting on to complete when paused. | 304 /// The completer that [getAsset()] is waiting on to complete when paused. |
257 /// | 305 /// |
258 /// If `null` it will return the asset immediately. | 306 /// If `null` it will return the asset immediately. |
259 Completer _pauseCompleter; | 307 Completer _pauseCompleter; |
260 | 308 |
261 /// Tells the provider to wait during [getAsset] until [complete()] | 309 /// Tells the provider to wait during [getAsset] until [complete()] |
262 /// is called. | 310 /// is called. |
263 /// | 311 /// |
264 /// Lets you test the asynchronous behavior of loading. | 312 /// Lets you test the asynchronous behavior of loading. |
265 void _pause() { | 313 void _pause() { |
266 _pauseCompleter = new Completer(); | 314 _pauseCompleter = new Completer(); |
267 } | 315 } |
268 | 316 |
269 void _resume() { | 317 void _resume() { |
270 _pauseCompleter.complete(); | 318 _pauseCompleter.complete(); |
271 _pauseCompleter = null; | 319 _pauseCompleter = null; |
272 } | 320 } |
273 | 321 |
274 MockProvider(assets, | 322 MockProvider(assets, |
275 Map<String, Iterable<Iterable<Transformer>>> transformers) { | 323 Map<String, Iterable<Iterable<Transformer>>> transformers) { |
276 var assetList; | 324 var assetList; |
277 if (assets is Map) { | 325 if (assets is Map) { |
278 assetList = assets.keys.map((asset) { | 326 assetList = assets.keys.map((asset) { |
279 var id = new AssetId.parse(asset); | 327 var id = new AssetId.parse(asset); |
280 return new MockAsset(id, assets[asset]); | 328 return new _MockAsset(id, assets[asset]); |
281 }); | 329 }); |
282 } else if (assets is Iterable) { | 330 } else if (assets is Iterable) { |
283 assetList = assets.map((asset) { | 331 assetList = assets.map((asset) { |
284 var id = new AssetId.parse(asset); | 332 var id = new AssetId.parse(asset); |
285 var contents = pathos.basenameWithoutExtension(id.path); | 333 var contents = pathos.basenameWithoutExtension(id.path); |
286 return new MockAsset(id, contents); | 334 return new _MockAsset(id, contents); |
287 }); | 335 }); |
288 } | 336 } |
289 | 337 |
290 _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package), | 338 _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package), |
291 (package, assets) { | 339 (package, assets) { |
292 var packageTransformers = transformers[package]; | 340 var packageTransformers = transformers[package]; |
293 if (packageTransformers == null) packageTransformers = []; | 341 if (packageTransformers == null) packageTransformers = []; |
294 return new _MockPackage(assets, packageTransformers.toList()); | 342 return new _MockPackage( |
343 new AssetSet.from(assets), packageTransformers.toList()); | |
295 }); | 344 }); |
296 | 345 |
297 // If there are no assets or transformers, add a dummy package. This better | 346 // If there are no assets or transformers, add a dummy package. This better |
298 // simulates the real world, where there'll always be at least the | 347 // simulates the real world, where there'll always be at least the |
299 // entrypoint package. | 348 // entrypoint package. |
300 if (_packages.isEmpty) _packages = {"app": new _MockPackage([], [])}; | 349 if (_packages.isEmpty) { |
350 _packages = {"app": new _MockPackage(new AssetSet(), [])}; | |
351 } | |
301 } | 352 } |
302 | 353 |
303 void _modifyAsset(String name, String contents) { | 354 void _modifyAsset(String name, String contents) { |
304 var id = new AssetId.parse(name); | 355 var id = new AssetId.parse(name); |
305 var asset = _packages[id.package].assets.firstWhere((a) => a.id == id); | 356 _errors.remove(id); |
306 asset.contents = contents; | 357 _packages[id.package].assets[id].contents = contents; |
307 } | 358 } |
308 | 359 |
360 void _setAssetError(String name) => _errors.add(new AssetId.parse(name)); | |
Bob Nystrom
2013/07/30 22:25:02
Document.
nweiz
2013/07/30 22:46:21
Like [_modifyAsset], I didn't document this becaus
| |
361 | |
309 List<AssetId> listAssets(String package, {String within}) { | 362 List<AssetId> listAssets(String package, {String within}) { |
310 if (within != null) { | 363 if (within != null) { |
311 throw new UnimplementedError("Doesn't handle 'within' yet."); | 364 throw new UnimplementedError("Doesn't handle 'within' yet."); |
312 } | 365 } |
313 | 366 |
314 return _packages[package].assets.map((asset) => asset.id); | 367 return _packages[package].assets.map((asset) => asset.id); |
315 } | 368 } |
316 | 369 |
317 Iterable<Iterable<Transformer>> getTransformers(String package) { | 370 Iterable<Iterable<Transformer>> getTransformers(String package) { |
318 var mockPackage = _packages[package]; | 371 var mockPackage = _packages[package]; |
319 if (mockPackage == null) { | 372 if (mockPackage == null) { |
320 throw new ArgumentError("No package named $package."); | 373 throw new ArgumentError("No package named $package."); |
321 } | 374 } |
322 return mockPackage.transformers; | 375 return mockPackage.transformers; |
323 } | 376 } |
324 | 377 |
325 Future<Asset> getAsset(AssetId id) { | 378 Future<Asset> getAsset(AssetId id) { |
379 // Eagerly load the asset so we can test an asset's value changing between | |
380 // when a load starts and when it finishes. | |
381 var package = _packages[id.package]; | |
382 var asset; | |
383 if (package != null) asset = package.assets[id]; | |
384 | |
385 var error = _errors.contains(id); | |
Bob Nystrom
2013/07/30 22:25:02
"hasError"
nweiz
2013/07/30 22:46:21
Done.
| |
386 | |
326 var future; | 387 var future; |
327 if (_pauseCompleter != null) { | 388 if (_pauseCompleter != null) { |
328 future = _pauseCompleter.future; | 389 future = _pauseCompleter.future; |
329 } else { | 390 } else { |
330 future = new Future.value(); | 391 future = new Future.value(); |
331 } | 392 } |
332 | 393 |
333 return future.then((_) { | 394 return future.then((_) { |
334 var package = _packages[id.package]; | 395 if (error) throw new MockLoadException(id); |
335 if (package == null) throw new AssetNotFoundException(id); | 396 if (asset == null) throw new AssetNotFoundException(id); |
336 | 397 return asset; |
337 return package.assets.firstWhere((asset) => asset.id == id, | |
338 orElse: () => throw new AssetNotFoundException(id)); | |
339 }); | 398 }); |
340 } | 399 } |
341 } | 400 } |
342 | 401 |
402 /// Error thrown for assets with [setAssetError] set. | |
403 class MockLoadException implements Exception { | |
404 final AssetId id; | |
405 | |
406 MockLoadException(this.id); | |
407 | |
408 String toString() => "Error loading $id."; | |
409 } | |
410 | |
343 /// Used by [MockProvider] to keep track of which assets and transformers exist | 411 /// Used by [MockProvider] to keep track of which assets and transformers exist |
344 /// for each package. | 412 /// for each package. |
345 class _MockPackage { | 413 class _MockPackage { |
346 final List<MockAsset> assets; | 414 final AssetSet assets; |
347 final List<List<Transformer>> transformers; | 415 final List<List<Transformer>> transformers; |
348 | 416 |
349 _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers) | 417 _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers) |
350 : transformers = transformers.map((phase) => phase.toList()).toList(); | 418 : transformers = transformers.map((phase) => phase.toList()).toList(); |
351 } | 419 } |
352 | 420 |
353 /// A [Transformer] that takes assets ending with one extension and generates | |
354 /// assets with a given extension. | |
355 /// | |
356 /// Appends the output extension to the contents of the input file. | |
357 class RewriteTransformer extends Transformer { | |
358 final String from; | |
359 final String to; | |
360 | |
361 /// The number of times the transformer has been applied. | |
362 int numRuns = 0; | |
363 | |
364 /// The number of currently running transforms. | |
365 int _runningTransforms = 0; | |
366 | |
367 /// The completer that the transform is waiting on to complete. | |
368 /// | |
369 /// If `null` the transform will complete immediately. | |
370 Completer _wait; | |
371 | |
372 /// A future that completes when the first apply of this transformer begins. | |
373 Future get started => _started.future; | |
374 final _started = new Completer(); | |
375 | |
376 /// Creates a transformer that rewrites assets whose extension is [from] to | |
377 /// one whose extension is [to]. | |
378 /// | |
379 /// [to] may be a space-separated list in which case multiple outputs will be | |
380 /// created for each input. | |
381 RewriteTransformer(this.from, this.to); | |
382 | |
383 /// `true` if any transforms are currently running. | |
384 bool get isRunning => _runningTransforms > 0; | |
385 | |
386 /// Tells the transform to wait during its transformation until [complete()] | |
387 /// is called. | |
388 /// | |
389 /// Lets you test the asynchronous behavior of transformers. | |
390 void wait() { | |
391 _wait = new Completer(); | |
392 } | |
393 | |
394 void complete() { | |
395 _wait.complete(); | |
396 _wait = null; | |
397 } | |
398 | |
399 Future<bool> isPrimary(Asset asset) { | |
400 return new Future.value(asset.id.extension == ".$from"); | |
401 } | |
402 | |
403 Future apply(Transform transform) { | |
404 numRuns++; | |
405 if (!_started.isCompleted) _started.complete(); | |
406 _runningTransforms++; | |
407 return transform.primaryInput.then((input) { | |
408 return Future.wait(to.split(" ").map((extension) { | |
409 var id = transform.primaryId.changeExtension(".$extension"); | |
410 return input.readAsString().then((content) { | |
411 transform.addOutput(new MockAsset(id, "$content.$extension")); | |
412 }); | |
413 })).then((_) { | |
414 if (_wait != null) return _wait.future; | |
415 }); | |
416 }).whenComplete(() { | |
417 _runningTransforms--; | |
418 }); | |
419 } | |
420 | |
421 String toString() => "$from->$to"; | |
422 } | |
423 | |
424 /// A [Transformer] that takes an input asset that contains a comma-separated | |
425 /// list of paths and outputs a file for each path. | |
426 class OneToManyTransformer extends Transformer { | |
427 final String extension; | |
428 | |
429 /// The number of times the transformer has been applied. | |
430 int numRuns = 0; | |
431 | |
432 /// Creates a transformer that consumes assets with [extension]. | |
433 /// | |
434 /// That file contains a comma-separated list of paths and it will output | |
435 /// files at each of those paths. | |
436 OneToManyTransformer(this.extension); | |
437 | |
438 Future<bool> isPrimary(Asset asset) { | |
439 return new Future.value(asset.id.extension == ".$extension"); | |
440 } | |
441 | |
442 Future apply(Transform transform) { | |
443 numRuns++; | |
444 return transform.primaryInput.then((input) { | |
445 return input.readAsString().then((lines) { | |
446 for (var line in lines.split(",")) { | |
447 var id = new AssetId(transform.primaryId.package, line); | |
448 transform.addOutput(new MockAsset(id, "spread $extension")); | |
449 } | |
450 }); | |
451 }); | |
452 } | |
453 | |
454 String toString() => "1->many $extension"; | |
455 } | |
456 | |
457 /// A transformer that uses the contents of a file to define the other inputs. | |
458 /// | |
459 /// Outputs a file with the same name as the primary but with an "out" | |
460 /// extension containing the concatenated contents of all non-primary inputs. | |
461 class ManyToOneTransformer extends Transformer { | |
462 final String extension; | |
463 | |
464 /// The number of times the transformer has been applied. | |
465 int numRuns = 0; | |
466 | |
467 /// Creates a transformer that consumes assets with [extension]. | |
468 /// | |
469 /// That file contains a comma-separated list of paths and it will input | |
470 /// files at each of those paths. | |
471 ManyToOneTransformer(this.extension); | |
472 | |
473 Future<bool> isPrimary(Asset asset) { | |
474 return new Future.value(asset.id.extension == ".$extension"); | |
475 } | |
476 | |
477 Future apply(Transform transform) { | |
478 numRuns++; | |
479 return transform.primaryInput.then((primary) { | |
480 return primary.readAsString().then((contents) { | |
481 // Get all of the included inputs. | |
482 var inputs = contents.split(",").map((path) { | |
483 var id = new AssetId(transform.primaryId.package, path); | |
484 return transform.getInput(id); | |
485 }); | |
486 | |
487 return Future.wait(inputs); | |
488 }).then((inputs) { | |
489 // Concatenate them to one output. | |
490 var output = ""; | |
491 return Future.forEach(inputs, (input) { | |
492 return input.readAsString().then((contents) { | |
493 output += contents; | |
494 }); | |
495 }).then((_) { | |
496 var id = transform.primaryId.changeExtension(".out"); | |
497 transform.addOutput(new MockAsset(id, output)); | |
498 }); | |
499 }); | |
500 }); | |
501 } | |
502 | |
503 String toString() => "many->1 $extension"; | |
504 } | |
505 | |
506 /// A transformer that throws an exception when run, after generating the | |
507 /// given outputs. | |
508 class BadTransformer extends Transformer { | |
509 /// The error it throws. | |
510 static const ERROR = "I am a bad transformer!"; | |
511 | |
512 /// The list of asset names that it should output. | |
513 final List<String> outputs; | |
514 | |
515 BadTransformer(this.outputs); | |
516 | |
517 Future<bool> isPrimary(Asset asset) => new Future.value(true); | |
518 Future apply(Transform transform) { | |
519 return newFuture(() { | |
520 // Create the outputs first. | |
521 for (var output in outputs) { | |
522 var id = new AssetId.parse(output); | |
523 transform.addOutput(new MockAsset(id, output)); | |
524 } | |
525 | |
526 // Then fail. | |
527 throw ERROR; | |
528 }); | |
529 } | |
530 } | |
531 | |
532 /// A transformer that outputs an asset with the given id. | |
533 class CreateAssetTransformer extends Transformer { | |
534 final String output; | |
535 | |
536 CreateAssetTransformer(this.output); | |
537 | |
538 Future<bool> isPrimary(Asset asset) => new Future.value(true); | |
539 | |
540 Future apply(Transform transform) { | |
541 return newFuture(() { | |
542 transform.addOutput(new MockAsset(new AssetId.parse(output), output)); | |
543 }); | |
544 } | |
545 } | |
546 | |
547 /// An implementation of [Asset] that never hits the file system. | 421 /// An implementation of [Asset] that never hits the file system. |
548 class MockAsset implements Asset { | 422 class _MockAsset implements Asset { |
Bob Nystrom
2013/07/30 22:25:02
Can you get rid of this entirely?
nweiz
2013/07/30 22:46:21
I considered that, but the fact that MockProvider
Bob Nystrom
2013/07/30 23:14:08
SGTM.
| |
549 final AssetId id; | 423 final AssetId id; |
550 String contents; | 424 String contents; |
551 | 425 |
552 MockAsset(this.id, this.contents); | 426 _MockAsset(this.id, this.contents); |
553 | 427 |
554 Future<String> readAsString({Encoding encoding}) => | 428 Future<String> readAsString({Encoding encoding}) => |
555 new Future.value(contents); | 429 new Future.value(contents); |
556 | 430 |
557 Stream<List<int>> read() => throw new UnimplementedError(); | 431 Stream<List<int>> read() => throw new UnimplementedError(); |
558 | 432 |
559 String toString() => "MockAsset $id $contents"; | 433 String toString() => "MockAsset $id $contents"; |
560 } | 434 } |
OLD | NEW |