OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library barback.test.utils; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:convert' show Encoding; |
| 9 |
| 10 import 'package:barback/barback.dart'; |
| 11 import 'package:barback/src/utils.dart'; |
| 12 import 'package:barback/src/utils/cancelable_future.dart'; |
| 13 import 'package:path/path.dart' as pathos; |
| 14 import 'package:scheduled_test/scheduled_test.dart'; |
| 15 import 'package:stack_trace/stack_trace.dart'; |
| 16 import 'package:unittest/compact_vm_config.dart'; |
| 17 |
| 18 export 'transformer/aggregate_many_to_many.dart'; |
| 19 export 'transformer/aggregate_many_to_one.dart'; |
| 20 export 'transformer/bad.dart'; |
| 21 export 'transformer/bad_log.dart'; |
| 22 export 'transformer/catch_asset_not_found.dart'; |
| 23 export 'transformer/check_content.dart'; |
| 24 export 'transformer/check_content_and_rename.dart'; |
| 25 export 'transformer/conditionally_consume_primary.dart'; |
| 26 export 'transformer/create_asset.dart'; |
| 27 export 'transformer/declare_assets.dart'; |
| 28 export 'transformer/declaring_aggregate_many_to_many.dart'; |
| 29 export 'transformer/declaring_aggregate_many_to_one.dart'; |
| 30 export 'transformer/declaring_bad.dart'; |
| 31 export 'transformer/declaring_check_content_and_rename.dart'; |
| 32 export 'transformer/declaring_rewrite.dart'; |
| 33 export 'transformer/emit_nothing.dart'; |
| 34 export 'transformer/has_input.dart'; |
| 35 export 'transformer/lazy_aggregate_many_to_many.dart'; |
| 36 export 'transformer/lazy_aggregate_many_to_one.dart'; |
| 37 export 'transformer/lazy_assets.dart'; |
| 38 export 'transformer/lazy_bad.dart'; |
| 39 export 'transformer/lazy_check_content_and_rename.dart'; |
| 40 export 'transformer/lazy_many_to_one.dart'; |
| 41 export 'transformer/lazy_rewrite.dart'; |
| 42 export 'transformer/many_to_one.dart'; |
| 43 export 'transformer/mock.dart'; |
| 44 export 'transformer/mock_aggregate.dart'; |
| 45 export 'transformer/one_to_many.dart'; |
| 46 export 'transformer/rewrite.dart'; |
| 47 export 'transformer/sync_rewrite.dart'; |
| 48 |
| 49 var _configured = false; |
| 50 |
| 51 MockProvider _provider; |
| 52 Barback _barback; |
| 53 |
| 54 /// Calls to [buildShouldSucceed] and [buildShouldFail] set expectations on |
| 55 /// successive [BuildResult]s from [_barback]. This keeps track of how many |
| 56 /// calls have already been made so later calls know which result to look for. |
| 57 int _nextBuildResult; |
| 58 |
| 59 /// Calls to [buildShouldLog] set expectations on successive log entries from |
| 60 /// [_barback]. This keeps track of how many calls have already been made so |
| 61 /// later calls know which result to look for. |
| 62 int _nextLog; |
| 63 |
| 64 void initConfig() { |
| 65 if (_configured) return; |
| 66 _configured = true; |
| 67 useCompactVMConfiguration(); |
| 68 filterStacks = true; |
| 69 } |
| 70 |
| 71 /// Creates a new [PackageProvider] and [PackageGraph] with the given [assets] |
| 72 /// and [transformers]. |
| 73 /// |
| 74 /// This graph is used internally by most of the other functions in this |
| 75 /// library so you must call it in the test before calling any of the other |
| 76 /// functions. |
| 77 /// |
| 78 /// [assets] may either be an [Iterable] or a [Map]. If it's an [Iterable], |
| 79 /// each element may either be an [AssetId] or a string that can be parsed to |
| 80 /// one. If it's a [Map], each key should be a string that can be parsed to an |
| 81 /// [AssetId] and the value should be a string defining the contents of that |
| 82 /// asset. |
| 83 /// |
| 84 /// [transformers] is a map from package names to the transformers for each |
| 85 /// package. |
| 86 void initGraph([assets, |
| 87 Map<String, Iterable<Iterable<Transformer>>> transformers]) => |
| 88 initStaticGraph(assets, transformers: transformers); |
| 89 |
| 90 void initStaticGraph(assets, {Iterable<String> staticPackages, |
| 91 Map<String, Iterable<Iterable<Transformer>>> transformers}) { |
| 92 if (assets == null) assets = []; |
| 93 if (staticPackages == null) staticPackages = []; |
| 94 if (transformers == null) transformers = {}; |
| 95 |
| 96 _provider = new MockProvider(assets, |
| 97 staticPackages: staticPackages, |
| 98 additionalPackages: transformers.keys); |
| 99 _barback = new Barback(_provider); |
| 100 // Add a dummy listener to the log so it doesn't print to stdout. |
| 101 _barback.log.listen((_) {}); |
| 102 _nextBuildResult = 0; |
| 103 _nextLog = 0; |
| 104 |
| 105 schedule(() => transformers.forEach(_barback.updateTransformers)); |
| 106 |
| 107 // There should be one successful build after adding all the transformers but |
| 108 // before adding any sources. |
| 109 if (!transformers.isEmpty) buildShouldSucceed(); |
| 110 } |
| 111 |
| 112 /// Updates [assets] in the current [PackageProvider]. |
| 113 /// |
| 114 /// Each item in the list may either be an [AssetId] or a string that can be |
| 115 /// parsed as one. |
| 116 void updateSources(Iterable assets) { |
| 117 assets = _parseAssets(assets); |
| 118 schedule(() => _barback.updateSources(assets), |
| 119 "updating ${assets.join(', ')}"); |
| 120 } |
| 121 |
| 122 /// Updates [assets] in the current [PackageProvider]. |
| 123 /// |
| 124 /// Each item in the list may either be an [AssetId] or a string that can be |
| 125 /// parsed as one. Unlike [updateSources], this is not automatically scheduled |
| 126 /// and will be run synchronously when called. |
| 127 void updateSourcesSync(Iterable assets) => |
| 128 _barback.updateSources(_parseAssets(assets)); |
| 129 |
| 130 /// Removes [assets] from the current [PackageProvider]. |
| 131 /// |
| 132 /// Each item in the list may either be an [AssetId] or a string that can be |
| 133 /// parsed as one. |
| 134 void removeSources(Iterable assets) { |
| 135 assets = _parseAssets(assets); |
| 136 schedule(() => _barback.removeSources(assets), |
| 137 "removing ${assets.join(', ')}"); |
| 138 } |
| 139 |
| 140 /// Removes [assets] from the current [PackageProvider]. |
| 141 /// |
| 142 /// Each item in the list may either be an [AssetId] or a string that can be |
| 143 /// parsed as one. Unlike [removeSources], this is not automatically scheduled |
| 144 /// and will be run synchronously when called. |
| 145 void removeSourcesSync(Iterable assets) => |
| 146 _barback.removeSources(_parseAssets(assets)); |
| 147 |
| 148 /// Sets the transformers for [package] to [transformers]. |
| 149 void updateTransformers(String package, Iterable<Iterable> transformers) { |
| 150 schedule(() => _barback.updateTransformers(package, transformers), |
| 151 "updating transformers for $package"); |
| 152 } |
| 153 |
| 154 /// Parse a list of strings or [AssetId]s into a list of [AssetId]s. |
| 155 List<AssetId> _parseAssets(Iterable assets) { |
| 156 return assets.map((asset) { |
| 157 if (asset is String) return new AssetId.parse(asset); |
| 158 return asset; |
| 159 }).toList(); |
| 160 } |
| 161 |
| 162 /// Schedules a change to the contents of an asset identified by [name] to |
| 163 /// [contents]. |
| 164 /// |
| 165 /// Does not update it in the graph. |
| 166 void modifyAsset(String name, String contents) { |
| 167 schedule(() { |
| 168 _provider._modifyAsset(name, contents); |
| 169 }, "modify asset $name"); |
| 170 } |
| 171 |
| 172 /// Schedules an error to be generated when loading the asset identified by |
| 173 /// [name]. |
| 174 /// |
| 175 /// Does not update the asset in the graph. If [async] is true, the error is |
| 176 /// thrown asynchronously. |
| 177 void setAssetError(String name, {bool async: true}) { |
| 178 schedule(() { |
| 179 _provider._setAssetError(name, async); |
| 180 }, "set error for asset $name"); |
| 181 } |
| 182 |
| 183 /// Schedules a pause of the internally created [PackageProvider]. |
| 184 /// |
| 185 /// All asset requests that the [PackageGraph] makes to the provider after this |
| 186 /// will not complete until [resumeProvider] is called. |
| 187 void pauseProvider() { |
| 188 schedule(() => _provider._pause(), "pause provider"); |
| 189 } |
| 190 |
| 191 /// Schedules an unpause of the provider after a call to [pauseProvider] and |
| 192 /// allows all pending asset loads to finish. |
| 193 void resumeProvider() { |
| 194 schedule(() => _provider._resume(), "resume provider"); |
| 195 } |
| 196 |
| 197 /// Asserts that the current build step shouldn't have finished by this point in |
| 198 /// the schedule. |
| 199 /// |
| 200 /// This uses the same build counter as [buildShouldSucceed] and |
| 201 /// [buildShouldFail], so those can be used to validate build results before and |
| 202 /// after this. |
| 203 void buildShouldNotBeDone() { |
| 204 _futureShouldNotCompleteUntil( |
| 205 _barback.results.elementAt(_nextBuildResult), |
| 206 schedule(() => pumpEventQueue(), "build should not terminate"), |
| 207 "build"); |
| 208 } |
| 209 |
| 210 /// Expects that the next [BuildResult] is a build success. |
| 211 void buildShouldSucceed() { |
| 212 expect(_getNextBuildResult("build should succeed").then((result) { |
| 213 result.errors.forEach(currentSchedule.signalError); |
| 214 expect(result.succeeded, isTrue); |
| 215 }), completes); |
| 216 } |
| 217 |
| 218 /// Expects that the next [BuildResult] emitted is a failure. |
| 219 /// |
| 220 /// [matchers] is a list of matchers to match against the errors that caused the |
| 221 /// build to fail. Every matcher is expected to match an error, but the order of |
| 222 /// matchers is unimportant. |
| 223 void buildShouldFail(List matchers) { |
| 224 expect(_getNextBuildResult("build should fail").then((result) { |
| 225 expect(result.succeeded, isFalse); |
| 226 expect(result.errors.length, equals(matchers.length)); |
| 227 for (var matcher in matchers) { |
| 228 expect(result.errors, contains(matcher)); |
| 229 } |
| 230 }), completes); |
| 231 } |
| 232 |
| 233 /// Expects that the nexted logged [LogEntry] matches [matcher] which may be |
| 234 /// either a [Matcher] or a string to match a literal string. |
| 235 void buildShouldLog(LogLevel level, matcher) { |
| 236 expect(_getNextLog("build should log").then((log) { |
| 237 expect(log.level, equals(level)); |
| 238 expect(log.message, matcher); |
| 239 }), completes); |
| 240 } |
| 241 |
| 242 Future<BuildResult> _getNextBuildResult(String description) { |
| 243 var result = currentSchedule.wrapFuture( |
| 244 _barback.results.elementAt(_nextBuildResult++)); |
| 245 return schedule(() => result, description); |
| 246 } |
| 247 |
| 248 Future<LogEntry> _getNextLog(String description) { |
| 249 var result = currentSchedule.wrapFuture( |
| 250 _barback.log.elementAt(_nextLog++)); |
| 251 return schedule(() => result, description); |
| 252 } |
| 253 |
| 254 /// Schedules an expectation that the graph will deliver an asset matching |
| 255 /// [name] and [contents]. |
| 256 /// |
| 257 /// [contents] may be a [String] or a [Matcher] that matches a string. If |
| 258 /// [contents] is omitted, defaults to the asset's filename without an extension |
| 259 /// (which is the same default that [initGraph] uses). |
| 260 void expectAsset(String name, [contents]) { |
| 261 var id = new AssetId.parse(name); |
| 262 |
| 263 if (contents == null) { |
| 264 contents = pathos.basenameWithoutExtension(id.path); |
| 265 } |
| 266 |
| 267 schedule(() { |
| 268 return _barback.getAssetById(id).then((asset) { |
| 269 // TODO(rnystrom): Make an actual Matcher class for this. |
| 270 expect(asset.id, equals(id)); |
| 271 expect(asset.readAsString(), completion(contents)); |
| 272 }); |
| 273 }, "get asset $name"); |
| 274 } |
| 275 |
| 276 /// Schedules an expectation that the graph will not find an asset matching |
| 277 /// [name]. |
| 278 void expectNoAsset(String name) { |
| 279 var id = new AssetId.parse(name); |
| 280 |
| 281 // Make sure the future gets the error. |
| 282 schedule(() { |
| 283 return _barback.getAssetById(id).then((asset) { |
| 284 fail("Should have thrown error but got $asset."); |
| 285 }).catchError((error) { |
| 286 expect(error, new isInstanceOf<AssetNotFoundException>()); |
| 287 expect(error.id, equals(id)); |
| 288 }); |
| 289 }, "get asset $name"); |
| 290 } |
| 291 |
| 292 /// Schedules an expectation that the graph will output all of the given |
| 293 /// assets, and no others. |
| 294 /// |
| 295 /// [assets] may be an iterable of asset id strings, in which case this asserts |
| 296 /// that the graph outputs exactly the assets with those ids. It may also be a |
| 297 /// map from asset id strings to asset contents, in which case the contents must |
| 298 /// also match. |
| 299 void expectAllAssets(assets) { |
| 300 var expected; |
| 301 var expectedString; |
| 302 if (assets is Map) { |
| 303 expected = mapMapKeys(assets, (key, _) => new AssetId.parse(key)); |
| 304 expectedString = expected.toString(); |
| 305 } else { |
| 306 expected = assets.map((asset) => new AssetId.parse(asset)); |
| 307 expectedString = expected.join(', '); |
| 308 } |
| 309 |
| 310 schedule(() { |
| 311 return _barback.getAllAssets().then((actualAssets) { |
| 312 var actualIds = actualAssets.map((asset) => asset.id).toSet(); |
| 313 |
| 314 if (expected is Map) { |
| 315 expected.forEach((id, contents) { |
| 316 expect(actualIds, contains(id)); |
| 317 actualIds.remove(id); |
| 318 expect(actualAssets[id].readAsString(), completion(equals(contents))); |
| 319 }); |
| 320 } else { |
| 321 for (var id in expected) { |
| 322 expect(actualIds, contains(id)); |
| 323 actualIds.remove(id); |
| 324 } |
| 325 } |
| 326 |
| 327 expect(actualIds, isEmpty); |
| 328 }); |
| 329 }, "get all assets, expecting $expectedString"); |
| 330 } |
| 331 |
| 332 /// Schedules an expectation that [Barback.getAllAssets] will return a [Future] |
| 333 /// that completes to a error that matches [matcher]. |
| 334 /// |
| 335 /// If [match] is a [List], then it expects the completed error to be an |
| 336 /// [AggregateException] whose errors match each matcher in the list. Otherwise, |
| 337 /// [match] should be a single matcher that the error should match. |
| 338 void expectAllAssetsShouldFail(Matcher matcher) { |
| 339 schedule(() { |
| 340 expect(_barback.getAllAssets(), throwsA(matcher)); |
| 341 }, "get all assets should fail"); |
| 342 } |
| 343 |
| 344 /// Schedules an expectation that a [getAssetById] call for the given asset |
| 345 /// won't terminate at this point in the schedule. |
| 346 void expectAssetDoesNotComplete(String name) { |
| 347 var id = new AssetId.parse(name); |
| 348 |
| 349 schedule(() { |
| 350 return _futureShouldNotCompleteUntil( |
| 351 _barback.getAssetById(id), |
| 352 pumpEventQueue(), |
| 353 "asset $id"); |
| 354 }, "asset $id should not complete"); |
| 355 } |
| 356 |
| 357 /// Returns a matcher for an [AggregateException] containing errors that match |
| 358 /// [matchers]. |
| 359 Matcher isAggregateException(Iterable<Matcher> errors) { |
| 360 // Match the aggregate error itself. |
| 361 var matchers = [ |
| 362 new isInstanceOf<AggregateException>(), |
| 363 transform((error) => error.errors, hasLength(errors.length), |
| 364 'errors.length == ${errors.length}') |
| 365 ]; |
| 366 |
| 367 // Make sure its contained errors match the matchers. |
| 368 for (var error in errors) { |
| 369 matchers.add(transform((error) => error.errors, contains(error), |
| 370 error.toString())); |
| 371 } |
| 372 |
| 373 return allOf(matchers); |
| 374 } |
| 375 |
| 376 /// Returns a matcher for an [AssetNotFoundException] with the given [id]. |
| 377 Matcher isAssetNotFoundException(String name) { |
| 378 var id = new AssetId.parse(name); |
| 379 return allOf( |
| 380 new isInstanceOf<AssetNotFoundException>(), |
| 381 predicate((error) => error.id == id, 'id == $name')); |
| 382 } |
| 383 |
| 384 /// Returns a matcher for an [AssetCollisionException] with the given [id]. |
| 385 Matcher isAssetCollisionException(String name) { |
| 386 var id = new AssetId.parse(name); |
| 387 return allOf( |
| 388 new isInstanceOf<AssetCollisionException>(), |
| 389 predicate((error) => error.id == id, 'id == $name')); |
| 390 } |
| 391 |
| 392 /// Returns a matcher for a [MissingInputException] with the given [id]. |
| 393 Matcher isMissingInputException(String name) { |
| 394 var id = new AssetId.parse(name); |
| 395 return allOf( |
| 396 new isInstanceOf<MissingInputException>(), |
| 397 predicate((error) => error.id == id, 'id == $name')); |
| 398 } |
| 399 |
| 400 /// Returns a matcher for an [InvalidOutputException] with the given id. |
| 401 Matcher isInvalidOutputException(String name) { |
| 402 var id = new AssetId.parse(name); |
| 403 return allOf( |
| 404 new isInstanceOf<InvalidOutputException>(), |
| 405 predicate((error) => error.id == id, 'id == $name')); |
| 406 } |
| 407 |
| 408 /// Returns a matcher for an [AssetLoadException] with the given id and a |
| 409 /// wrapped error that matches [error]. |
| 410 Matcher isAssetLoadException(String name, error) { |
| 411 var id = new AssetId.parse(name); |
| 412 return allOf( |
| 413 new isInstanceOf<AssetLoadException>(), |
| 414 transform((error) => error.id, equals(id), 'id'), |
| 415 transform((error) => error.error, wrapMatcher(error), 'error')); |
| 416 } |
| 417 |
| 418 /// Returns a matcher for a [TransformerException] with a wrapped error that |
| 419 /// matches [error]. |
| 420 Matcher isTransformerException(error) { |
| 421 return allOf( |
| 422 new isInstanceOf<TransformerException>(), |
| 423 transform((error) => error.error, wrapMatcher(error), 'error')); |
| 424 } |
| 425 |
| 426 /// Returns a matcher for a [MockLoadException] with the given [id]. |
| 427 Matcher isMockLoadException(String name) { |
| 428 var id = new AssetId.parse(name); |
| 429 return allOf( |
| 430 new isInstanceOf<MockLoadException>(), |
| 431 predicate((error) => error.id == id, 'id == $name')); |
| 432 } |
| 433 |
| 434 /// Returns a matcher that runs [transformation] on its input, then matches |
| 435 /// the output against [matcher]. |
| 436 /// |
| 437 /// [description] should be a noun phrase that describes the relation of the |
| 438 /// output of [transformation] to its input. |
| 439 Matcher transform(transformation(value), matcher, String description) => |
| 440 new _TransformMatcher(transformation, wrapMatcher(matcher), description); |
| 441 |
| 442 class _TransformMatcher extends Matcher { |
| 443 final Function _transformation; |
| 444 final Matcher _matcher; |
| 445 final String _description; |
| 446 |
| 447 _TransformMatcher(this._transformation, this._matcher, this._description); |
| 448 |
| 449 bool matches(item, Map matchState) => |
| 450 _matcher.matches(_transformation(item), matchState); |
| 451 |
| 452 Description describe(Description description) => |
| 453 description.add(_description).add(' ').addDescriptionOf(_matcher); |
| 454 } |
| 455 |
| 456 /// Asserts that [future] shouldn't complete until after [delay] completes. |
| 457 /// |
| 458 /// Once [delay] completes, the output of [future] is ignored, even if it's an |
| 459 /// error. |
| 460 /// |
| 461 /// [description] should describe [future]. |
| 462 Future _futureShouldNotCompleteUntil(Future future, Future delay, |
| 463 String description) { |
| 464 var trace = new Trace.current(); |
| 465 var cancelable = new CancelableFuture(future); |
| 466 cancelable.then((result) { |
| 467 currentSchedule.signalError( |
| 468 new Exception("Expected $description not to complete here, but it " |
| 469 "completed with result: $result"), |
| 470 trace); |
| 471 }).catchError((error) { |
| 472 currentSchedule.signalError(error); |
| 473 }); |
| 474 |
| 475 return delay.then((_) => cancelable.cancel()); |
| 476 } |
| 477 |
| 478 /// An [AssetProvider] that provides the given set of assets. |
| 479 class MockProvider implements StaticPackageProvider { |
| 480 Iterable<String> get packages => |
| 481 _assets.keys.toSet().difference(staticPackages); |
| 482 |
| 483 final Set<String> staticPackages; |
| 484 |
| 485 final Map<String, AssetSet> _assets; |
| 486 |
| 487 /// The set of assets for which [MockLoadException]s should be emitted if |
| 488 /// they're loaded. |
| 489 final _errors = new Set<AssetId>(); |
| 490 |
| 491 /// The set of assets for which synchronous [MockLoadException]s should be |
| 492 /// emitted if they're loaded. |
| 493 final _syncErrors = new Set<AssetId>(); |
| 494 |
| 495 /// The completer that [getAsset()] is waiting on to complete when paused. |
| 496 /// |
| 497 /// If `null` it will return the asset immediately. |
| 498 Completer _pauseCompleter; |
| 499 |
| 500 /// Tells the provider to wait during [getAsset] until [complete()] |
| 501 /// is called. |
| 502 /// |
| 503 /// Lets you test the asynchronous behavior of loading. |
| 504 void _pause() { |
| 505 _pauseCompleter = new Completer(); |
| 506 } |
| 507 |
| 508 void _resume() { |
| 509 _pauseCompleter.complete(); |
| 510 _pauseCompleter = null; |
| 511 } |
| 512 |
| 513 MockProvider(assets, {Iterable<String> staticPackages, |
| 514 Iterable<String> additionalPackages}) |
| 515 : staticPackages = staticPackages == null ? new Set() : |
| 516 staticPackages.toSet(), |
| 517 _assets = _normalizeAssets(assets, additionalPackages); |
| 518 |
| 519 static Map<String, AssetSet> _normalizeAssets(assets, |
| 520 Iterable<String> additionalPackages) { |
| 521 var assetList; |
| 522 if (assets is Map) { |
| 523 assetList = assets.keys.map((asset) { |
| 524 var id = new AssetId.parse(asset); |
| 525 return new _MockAsset(id, assets[asset]); |
| 526 }); |
| 527 } else if (assets is Iterable) { |
| 528 assetList = assets.map((asset) { |
| 529 var id = new AssetId.parse(asset); |
| 530 var contents = pathos.basenameWithoutExtension(id.path); |
| 531 return new _MockAsset(id, contents); |
| 532 }); |
| 533 } |
| 534 |
| 535 var assetMap = mapMapValues(groupBy(assetList, (asset) => asset.id.package), |
| 536 (package, assets) => new AssetSet.from(assets)); |
| 537 |
| 538 // Make sure that packages that have transformers but no assets are |
| 539 // considered by MockProvider to exist. |
| 540 if (additionalPackages != null) { |
| 541 for (var package in additionalPackages) { |
| 542 assetMap.putIfAbsent(package, () => new AssetSet()); |
| 543 } |
| 544 } |
| 545 |
| 546 // If there are no assets or transformers, add a dummy package. This better |
| 547 // simulates the real world, where there'll always be at least the |
| 548 // entrypoint package. |
| 549 return assetMap.isEmpty ? {"app": new AssetSet()} : assetMap; |
| 550 } |
| 551 |
| 552 void _modifyAsset(String name, String contents) { |
| 553 var id = new AssetId.parse(name); |
| 554 _errors.remove(id); |
| 555 _syncErrors.remove(id); |
| 556 (_assets[id.package][id] as _MockAsset).contents = contents; |
| 557 } |
| 558 |
| 559 void _setAssetError(String name, bool async) { |
| 560 (async ? _errors : _syncErrors).add(new AssetId.parse(name)); |
| 561 } |
| 562 |
| 563 Stream<AssetId> getAllAssetIds(String package) => |
| 564 new Stream.fromIterable(_assets[package].map((asset) => asset.id)); |
| 565 |
| 566 Future<Asset> getAsset(AssetId id) { |
| 567 // Eagerly load the asset so we can test an asset's value changing between |
| 568 // when a load starts and when it finishes. |
| 569 var assets = _assets[id.package]; |
| 570 var asset; |
| 571 if (assets != null) asset = assets[id]; |
| 572 |
| 573 if (_syncErrors.contains(id)) throw new MockLoadException(id); |
| 574 var hasError = _errors.contains(id); |
| 575 |
| 576 var future; |
| 577 if (_pauseCompleter != null) { |
| 578 future = _pauseCompleter.future; |
| 579 } else { |
| 580 future = new Future.value(); |
| 581 } |
| 582 |
| 583 return future.then((_) { |
| 584 if (hasError) throw new MockLoadException(id); |
| 585 if (asset == null) throw new AssetNotFoundException(id); |
| 586 return asset; |
| 587 }); |
| 588 } |
| 589 } |
| 590 |
| 591 /// Error thrown for assets with [setAssetError] set. |
| 592 class MockLoadException implements Exception { |
| 593 final AssetId id; |
| 594 |
| 595 MockLoadException(this.id); |
| 596 |
| 597 String toString() => "Error loading $id."; |
| 598 } |
| 599 |
| 600 /// An implementation of [Asset] that never hits the file system. |
| 601 class _MockAsset implements Asset { |
| 602 final AssetId id; |
| 603 String contents; |
| 604 |
| 605 _MockAsset(this.id, this.contents); |
| 606 |
| 607 Future<String> readAsString({Encoding encoding}) => |
| 608 new Future.value(contents); |
| 609 |
| 610 Stream<List<int>> read() => throw new UnimplementedError(); |
| 611 |
| 612 String toString() => "MockAsset $id $contents"; |
| 613 } |
OLD | NEW |