Chromium Code Reviews| 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 |