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 |