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 to be generated when loading the asset identified by |
| 110 /// [name]. |
| 111 /// |
| 112 /// Does not update the asset in the graph. |
| 113 void setAssetError(String name) { |
| 114 schedule(() { |
| 115 _provider._setAssetError(name); |
| 116 }, "set error for asset $name"); |
| 117 } |
| 118 |
99 /// Schedules a pause of the internally created [PackageProvider]. | 119 /// Schedules a pause of the internally created [PackageProvider]. |
100 /// | 120 /// |
101 /// All asset requests that the [PackageGraph] makes to the provider after this | 121 /// All asset requests that the [PackageGraph] makes to the provider after this |
102 /// will not complete until [resumeProvider] is called. | 122 /// will not complete until [resumeProvider] is called. |
103 void pauseProvider() { | 123 void pauseProvider() { |
104 schedule(() => _provider._pause(), "pause provider"); | 124 schedule(() => _provider._pause(), "pause provider"); |
105 } | 125 } |
106 | 126 |
107 /// Schedules an unpause of the provider after a call to [pauseProvider] and | 127 /// Schedules an unpause of the provider after a call to [pauseProvider] and |
108 /// allows all pending asset loads to finish. | 128 /// allows all pending asset loads to finish. |
109 void resumeProvider() { | 129 void resumeProvider() { |
110 schedule(() => _provider._resume(), "resume provider"); | 130 schedule(() => _provider._resume(), "resume provider"); |
111 } | 131 } |
112 | 132 |
113 /// Asserts that the current build step shouldn't have finished by this point in | 133 /// Asserts that the current build step shouldn't have finished by this point in |
114 /// the schedule. | 134 /// the schedule. |
115 /// | 135 /// |
116 /// This uses the same build counter as [buildShouldSucceed] and | 136 /// This uses the same build counter as [buildShouldSucceed] and |
117 /// [buildShouldFail], so those can be used to validate build results before and | 137 /// [buildShouldFail], so those can be used to validate build results before and |
118 /// after this. | 138 /// after this. |
119 void buildShouldNotBeDone() { | 139 void buildShouldNotBeDone() { |
120 var resultAllowed = false; | 140 _futureShouldNotCompleteUntil( |
121 var trace = new Trace.current(); | 141 _graph.results.elementAt(_nextBuildResult), |
122 _graph.results.elementAt(_nextBuildResult).then((result) { | 142 schedule(() => pumpEventQueue(), "build should not terminate"), |
123 if (resultAllowed) return; | 143 "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 } | 144 } |
141 | 145 |
142 /// Expects that the next [BuildResult] is a build success. | 146 /// Expects that the next [BuildResult] is a build success. |
143 void buildShouldSucceed() { | 147 void buildShouldSucceed() { |
144 expect(_getNextBuildResult().then((result) { | 148 expect(_getNextBuildResult().then((result) { |
| 149 result.errors.forEach(currentSchedule.signalError); |
145 expect(result.succeeded, isTrue); | 150 expect(result.succeeded, isTrue); |
146 }), completes); | 151 }), completes); |
147 } | 152 } |
148 | 153 |
149 /// Expects that the next [BuildResult] emitted is a failure. | 154 /// Expects that the next [BuildResult] emitted is a failure. |
150 /// | 155 /// |
151 /// [matchers] is a list of matchers to match against the errors that caused the | 156 /// [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 | 157 /// build to fail. Every matcher is expected to match an error, but the order of |
153 /// matchers is unimportant. | 158 /// matchers is unimportant. |
154 void buildShouldFail(List matchers) { | 159 void buildShouldFail(List matchers) { |
(...skipping 28 matching lines...) Expand all Loading... |
183 void expectAsset(String name, [String contents]) { | 188 void expectAsset(String name, [String contents]) { |
184 var id = new AssetId.parse(name); | 189 var id = new AssetId.parse(name); |
185 | 190 |
186 if (contents == null) { | 191 if (contents == null) { |
187 contents = pathos.basenameWithoutExtension(id.path); | 192 contents = pathos.basenameWithoutExtension(id.path); |
188 } | 193 } |
189 | 194 |
190 schedule(() { | 195 schedule(() { |
191 return _graph.getAssetById(id).then((asset) { | 196 return _graph.getAssetById(id).then((asset) { |
192 // TODO(rnystrom): Make an actual Matcher class for this. | 197 // TODO(rnystrom): Make an actual Matcher class for this. |
193 expect(asset, new isInstanceOf<MockAsset>()); | |
194 expect(asset.id, equals(id)); | 198 expect(asset.id, equals(id)); |
195 expect(asset.contents, equals(contents)); | 199 expect(asset.readAsString(), completion(equals(contents))); |
196 }); | 200 }); |
197 }, "get asset $name"); | 201 }, "get asset $name"); |
198 } | 202 } |
199 | 203 |
200 /// Schedules an expectation that the graph will not find an asset matching | 204 /// Schedules an expectation that the graph will not find an asset matching |
201 /// [name]. | 205 /// [name]. |
202 void expectNoAsset(String name) { | 206 void expectNoAsset(String name) { |
203 var id = new AssetId.parse(name); | 207 var id = new AssetId.parse(name); |
204 | 208 |
205 // Make sure the future gets the error. | 209 // Make sure the future gets the error. |
206 schedule(() { | 210 schedule(() { |
207 return _graph.getAssetById(id).then((asset) { | 211 return _graph.getAssetById(id).then((asset) { |
208 fail("Should have thrown error but got $asset."); | 212 fail("Should have thrown error but got $asset."); |
209 }).catchError((error) { | 213 }).catchError((error) { |
210 expect(error, new isInstanceOf<AssetNotFoundException>()); | 214 expect(error, new isInstanceOf<AssetNotFoundException>()); |
211 expect(error.id, equals(id)); | 215 expect(error.id, equals(id)); |
212 }); | 216 }); |
213 }, "get asset $name"); | 217 }, "get asset $name"); |
214 } | 218 } |
215 | 219 |
| 220 /// Schedules an expectation that a [getAssetById] call for the given asset |
| 221 /// won't terminate at this point in the schedule. |
| 222 void expectAssetDoesNotComplete(String name) { |
| 223 var id = new AssetId.parse(name); |
| 224 |
| 225 schedule(() { |
| 226 return _futureShouldNotCompleteUntil( |
| 227 _graph.getAssetById(id), |
| 228 pumpEventQueue(), |
| 229 "asset $id"); |
| 230 }, "asset $id should not complete"); |
| 231 } |
| 232 |
216 /// Returns a matcher for an [AssetNotFoundException] with the given [id]. | 233 /// Returns a matcher for an [AssetNotFoundException] with the given [id]. |
217 Matcher isAssetNotFoundException(String name) { | 234 Matcher isAssetNotFoundException(String name) { |
218 var id = new AssetId.parse(name); | 235 var id = new AssetId.parse(name); |
219 return allOf( | 236 return allOf( |
220 new isInstanceOf<AssetNotFoundException>(), | 237 new isInstanceOf<AssetNotFoundException>(), |
221 predicate((error) => error.id == id, 'id is $name')); | 238 predicate((error) => error.id == id, 'id == $name')); |
222 } | 239 } |
223 | 240 |
224 /// Returns a matcher for an [AssetCollisionException] with the given [id]. | 241 /// Returns a matcher for an [AssetCollisionException] with the given [id]. |
225 Matcher isAssetCollisionException(String name) { | 242 Matcher isAssetCollisionException(String name) { |
226 var id = new AssetId.parse(name); | 243 var id = new AssetId.parse(name); |
227 return allOf( | 244 return allOf( |
228 new isInstanceOf<AssetCollisionException>(), | 245 new isInstanceOf<AssetCollisionException>(), |
229 predicate((error) => error.id == id, 'id is $name')); | 246 predicate((error) => error.id == id, 'id == $name')); |
230 } | 247 } |
231 | 248 |
232 /// Returns a matcher for a [MissingInputException] with the given [id]. | 249 /// Returns a matcher for a [MissingInputException] with the given [id]. |
233 Matcher isMissingInputException(String name) { | 250 Matcher isMissingInputException(String name) { |
234 var id = new AssetId.parse(name); | 251 var id = new AssetId.parse(name); |
235 return allOf( | 252 return allOf( |
236 new isInstanceOf<MissingInputException>(), | 253 new isInstanceOf<MissingInputException>(), |
237 predicate((error) => error.id == id, 'id is $name')); | 254 predicate((error) => error.id == id, 'id == $name')); |
238 } | 255 } |
239 | 256 |
240 /// Returns a matcher for an [InvalidOutputException] with the given id and | 257 /// Returns a matcher for an [InvalidOutputException] with the given id and |
241 /// package name. | 258 /// package name. |
242 Matcher isInvalidOutputException(String package, String name) { | 259 Matcher isInvalidOutputException(String package, String name) { |
243 var id = new AssetId.parse(name); | 260 var id = new AssetId.parse(name); |
244 return allOf( | 261 return allOf( |
245 new isInstanceOf<InvalidOutputException>(), | 262 new isInstanceOf<InvalidOutputException>(), |
246 predicate((error) => error.package == package, 'package is $package'), | 263 predicate((error) => error.package == package, 'package is $package'), |
247 predicate((error) => error.id == id, 'id is $name')); | 264 predicate((error) => error.id == id, 'id == $name')); |
| 265 } |
| 266 |
| 267 /// Returns a matcher for a [MockLoadException] with the given [id]. |
| 268 Matcher isMockLoadException(String name) { |
| 269 var id = new AssetId.parse(name); |
| 270 return allOf( |
| 271 new isInstanceOf<MockLoadException>(), |
| 272 predicate((error) => error.id == id, 'id == $name')); |
| 273 } |
| 274 |
| 275 /// Asserts that [future] shouldn't complete until after [delay] completes. |
| 276 /// |
| 277 /// Once [delay] completes, the output of [future] is ignored, even if it's an |
| 278 /// error. |
| 279 /// |
| 280 /// [description] should describe [future]. |
| 281 Future _futureShouldNotCompleteUntil(Future future, Future delay, |
| 282 String description) { |
| 283 var trace = new Trace.current(); |
| 284 var cancelable = new CancelableFuture(future); |
| 285 cancelable.then((result) { |
| 286 currentSchedule.signalError( |
| 287 new Exception("Expected $description not to complete here, but it " |
| 288 "completed with result: $result"), |
| 289 trace); |
| 290 }).catchError((error) { |
| 291 currentSchedule.signalError(error); |
| 292 }); |
| 293 |
| 294 return delay.then((_) => cancelable.cancel()); |
248 } | 295 } |
249 | 296 |
250 /// An [AssetProvider] that provides the given set of assets. | 297 /// An [AssetProvider] that provides the given set of assets. |
251 class MockProvider implements PackageProvider { | 298 class MockProvider implements PackageProvider { |
252 Iterable<String> get packages => _packages.keys; | 299 Iterable<String> get packages => _packages.keys; |
253 | 300 |
254 Map<String, _MockPackage> _packages; | 301 Map<String, _MockPackage> _packages; |
255 | 302 |
| 303 /// The set of assets for which [MockLoadException]s should be emitted if |
| 304 /// they're loaded. |
| 305 final _errors = new Set<AssetId>(); |
| 306 |
256 /// The completer that [getAsset()] is waiting on to complete when paused. | 307 /// The completer that [getAsset()] is waiting on to complete when paused. |
257 /// | 308 /// |
258 /// If `null` it will return the asset immediately. | 309 /// If `null` it will return the asset immediately. |
259 Completer _pauseCompleter; | 310 Completer _pauseCompleter; |
260 | 311 |
261 /// Tells the provider to wait during [getAsset] until [complete()] | 312 /// Tells the provider to wait during [getAsset] until [complete()] |
262 /// is called. | 313 /// is called. |
263 /// | 314 /// |
264 /// Lets you test the asynchronous behavior of loading. | 315 /// Lets you test the asynchronous behavior of loading. |
265 void _pause() { | 316 void _pause() { |
266 _pauseCompleter = new Completer(); | 317 _pauseCompleter = new Completer(); |
267 } | 318 } |
268 | 319 |
269 void _resume() { | 320 void _resume() { |
270 _pauseCompleter.complete(); | 321 _pauseCompleter.complete(); |
271 _pauseCompleter = null; | 322 _pauseCompleter = null; |
272 } | 323 } |
273 | 324 |
274 MockProvider(assets, | 325 MockProvider(assets, |
275 Map<String, Iterable<Iterable<Transformer>>> transformers) { | 326 Map<String, Iterable<Iterable<Transformer>>> transformers) { |
276 var assetList; | 327 var assetList; |
277 if (assets is Map) { | 328 if (assets is Map) { |
278 assetList = assets.keys.map((asset) { | 329 assetList = assets.keys.map((asset) { |
279 var id = new AssetId.parse(asset); | 330 var id = new AssetId.parse(asset); |
280 return new MockAsset(id, assets[asset]); | 331 return new _MockAsset(id, assets[asset]); |
281 }); | 332 }); |
282 } else if (assets is Iterable) { | 333 } else if (assets is Iterable) { |
283 assetList = assets.map((asset) { | 334 assetList = assets.map((asset) { |
284 var id = new AssetId.parse(asset); | 335 var id = new AssetId.parse(asset); |
285 var contents = pathos.basenameWithoutExtension(id.path); | 336 var contents = pathos.basenameWithoutExtension(id.path); |
286 return new MockAsset(id, contents); | 337 return new _MockAsset(id, contents); |
287 }); | 338 }); |
288 } | 339 } |
289 | 340 |
290 _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package), | 341 _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package), |
291 (package, assets) { | 342 (package, assets) { |
292 var packageTransformers = transformers[package]; | 343 var packageTransformers = transformers[package]; |
293 if (packageTransformers == null) packageTransformers = []; | 344 if (packageTransformers == null) packageTransformers = []; |
294 return new _MockPackage(assets, packageTransformers.toList()); | 345 return new _MockPackage( |
| 346 new AssetSet.from(assets), packageTransformers.toList()); |
295 }); | 347 }); |
296 | 348 |
297 // If there are no assets or transformers, add a dummy package. This better | 349 // 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 | 350 // simulates the real world, where there'll always be at least the |
299 // entrypoint package. | 351 // entrypoint package. |
300 if (_packages.isEmpty) _packages = {"app": new _MockPackage([], [])}; | 352 if (_packages.isEmpty) { |
| 353 _packages = {"app": new _MockPackage(new AssetSet(), [])}; |
| 354 } |
301 } | 355 } |
302 | 356 |
303 void _modifyAsset(String name, String contents) { | 357 void _modifyAsset(String name, String contents) { |
304 var id = new AssetId.parse(name); | 358 var id = new AssetId.parse(name); |
305 var asset = _packages[id.package].assets.firstWhere((a) => a.id == id); | 359 _errors.remove(id); |
306 asset.contents = contents; | 360 _packages[id.package].assets[id].contents = contents; |
307 } | 361 } |
308 | 362 |
| 363 void _setAssetError(String name) => _errors.add(new AssetId.parse(name)); |
| 364 |
309 List<AssetId> listAssets(String package, {String within}) { | 365 List<AssetId> listAssets(String package, {String within}) { |
310 if (within != null) { | 366 if (within != null) { |
311 throw new UnimplementedError("Doesn't handle 'within' yet."); | 367 throw new UnimplementedError("Doesn't handle 'within' yet."); |
312 } | 368 } |
313 | 369 |
314 return _packages[package].assets.map((asset) => asset.id); | 370 return _packages[package].assets.map((asset) => asset.id); |
315 } | 371 } |
316 | 372 |
317 Iterable<Iterable<Transformer>> getTransformers(String package) { | 373 Iterable<Iterable<Transformer>> getTransformers(String package) { |
318 var mockPackage = _packages[package]; | 374 var mockPackage = _packages[package]; |
319 if (mockPackage == null) { | 375 if (mockPackage == null) { |
320 throw new ArgumentError("No package named $package."); | 376 throw new ArgumentError("No package named $package."); |
321 } | 377 } |
322 return mockPackage.transformers; | 378 return mockPackage.transformers; |
323 } | 379 } |
324 | 380 |
325 Future<Asset> getAsset(AssetId id) { | 381 Future<Asset> getAsset(AssetId id) { |
| 382 // Eagerly load the asset so we can test an asset's value changing between |
| 383 // when a load starts and when it finishes. |
| 384 var package = _packages[id.package]; |
| 385 var asset; |
| 386 if (package != null) asset = package.assets[id]; |
| 387 |
| 388 var hasError = _errors.contains(id); |
| 389 |
326 var future; | 390 var future; |
327 if (_pauseCompleter != null) { | 391 if (_pauseCompleter != null) { |
328 future = _pauseCompleter.future; | 392 future = _pauseCompleter.future; |
329 } else { | 393 } else { |
330 future = new Future.value(); | 394 future = new Future.value(); |
331 } | 395 } |
332 | 396 |
333 return future.then((_) { | 397 return future.then((_) { |
334 var package = _packages[id.package]; | 398 if (hasError) throw new MockLoadException(id); |
335 if (package == null) throw new AssetNotFoundException(id); | 399 if (asset == null) throw new AssetNotFoundException(id); |
336 | 400 return asset; |
337 return package.assets.firstWhere((asset) => asset.id == id, | |
338 orElse: () => throw new AssetNotFoundException(id)); | |
339 }); | 401 }); |
340 } | 402 } |
341 } | 403 } |
342 | 404 |
| 405 /// Error thrown for assets with [setAssetError] set. |
| 406 class MockLoadException implements Exception { |
| 407 final AssetId id; |
| 408 |
| 409 MockLoadException(this.id); |
| 410 |
| 411 String toString() => "Error loading $id."; |
| 412 } |
| 413 |
343 /// Used by [MockProvider] to keep track of which assets and transformers exist | 414 /// Used by [MockProvider] to keep track of which assets and transformers exist |
344 /// for each package. | 415 /// for each package. |
345 class _MockPackage { | 416 class _MockPackage { |
346 final List<MockAsset> assets; | 417 final AssetSet assets; |
347 final List<List<Transformer>> transformers; | 418 final List<List<Transformer>> transformers; |
348 | 419 |
349 _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers) | 420 _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers) |
350 : transformers = transformers.map((phase) => phase.toList()).toList(); | 421 : transformers = transformers.map((phase) => phase.toList()).toList(); |
351 } | 422 } |
352 | 423 |
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. | 424 /// An implementation of [Asset] that never hits the file system. |
548 class MockAsset implements Asset { | 425 class _MockAsset implements Asset { |
549 final AssetId id; | 426 final AssetId id; |
550 String contents; | 427 String contents; |
551 | 428 |
552 MockAsset(this.id, this.contents); | 429 _MockAsset(this.id, this.contents); |
553 | 430 |
554 Future<String> readAsString({Encoding encoding}) => | 431 Future<String> readAsString({Encoding encoding}) => |
555 new Future.value(contents); | 432 new Future.value(contents); |
556 | 433 |
557 Stream<List<int>> read() => throw new UnimplementedError(); | 434 Stream<List<int>> read() => throw new UnimplementedError(); |
558 | 435 |
559 String toString() => "MockAsset $id $contents"; | 436 String toString() => "MockAsset $id $contents"; |
560 } | 437 } |
OLD | NEW |