Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: pkg/barback/test/utils.dart

Issue 808713003: Remove barback from the repo. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/barback/test/transformer_test.dart ('k') | sdk/lib/_internal/pub/test/dependency_computer/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698