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

Unified Diff: packages/barback/test/utils.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « packages/barback/test/transformer_test.dart ('k') | packages/browser/LICENSE » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/barback/test/utils.dart
diff --git a/packages/barback/test/utils.dart b/packages/barback/test/utils.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c24b58c3e58e488a5b360453d673731c47d88b6e
--- /dev/null
+++ b/packages/barback/test/utils.dart
@@ -0,0 +1,613 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.test.utils;
+
+import 'dart:async';
+import 'dart:convert' show Encoding;
+
+import 'package:barback/barback.dart';
+import 'package:barback/src/utils.dart';
+import 'package:barback/src/utils/cancelable_future.dart';
+import 'package:path/path.dart' as pathos;
+import 'package:scheduled_test/scheduled_test.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:unittest/compact_vm_config.dart';
+
+export 'transformer/aggregate_many_to_many.dart';
+export 'transformer/aggregate_many_to_one.dart';
+export 'transformer/bad.dart';
+export 'transformer/bad_log.dart';
+export 'transformer/catch_asset_not_found.dart';
+export 'transformer/check_content.dart';
+export 'transformer/check_content_and_rename.dart';
+export 'transformer/conditionally_consume_primary.dart';
+export 'transformer/create_asset.dart';
+export 'transformer/declare_assets.dart';
+export 'transformer/declaring_aggregate_many_to_many.dart';
+export 'transformer/declaring_aggregate_many_to_one.dart';
+export 'transformer/declaring_bad.dart';
+export 'transformer/declaring_check_content_and_rename.dart';
+export 'transformer/declaring_rewrite.dart';
+export 'transformer/emit_nothing.dart';
+export 'transformer/has_input.dart';
+export 'transformer/lazy_aggregate_many_to_many.dart';
+export 'transformer/lazy_aggregate_many_to_one.dart';
+export 'transformer/lazy_assets.dart';
+export 'transformer/lazy_bad.dart';
+export 'transformer/lazy_check_content_and_rename.dart';
+export 'transformer/lazy_many_to_one.dart';
+export 'transformer/lazy_rewrite.dart';
+export 'transformer/many_to_one.dart';
+export 'transformer/mock.dart';
+export 'transformer/mock_aggregate.dart';
+export 'transformer/one_to_many.dart';
+export 'transformer/rewrite.dart';
+export 'transformer/sync_rewrite.dart';
+
+var _configured = false;
+
+MockProvider _provider;
+Barback _barback;
+
+/// Calls to [buildShouldSucceed] and [buildShouldFail] set expectations on
+/// successive [BuildResult]s from [_barback]. This keeps track of how many
+/// calls have already been made so later calls know which result to look for.
+int _nextBuildResult;
+
+/// Calls to [buildShouldLog] set expectations on successive log entries from
+/// [_barback]. This keeps track of how many calls have already been made so
+/// later calls know which result to look for.
+int _nextLog;
+
+void initConfig() {
+ if (_configured) return;
+ _configured = true;
+ useCompactVMConfiguration();
+ filterStacks = true;
+}
+
+/// Creates a new [PackageProvider] and [PackageGraph] with the given [assets]
+/// and [transformers].
+///
+/// This graph is used internally by most of the other functions in this
+/// library so you must call it in the test before calling any of the other
+/// functions.
+///
+/// [assets] may either be an [Iterable] or a [Map]. If it's an [Iterable],
+/// each element may either be an [AssetId] or a string that can be parsed to
+/// one. If it's a [Map], each key should be a string that can be parsed to an
+/// [AssetId] and the value should be a string defining the contents of that
+/// asset.
+///
+/// [transformers] is a map from package names to the transformers for each
+/// package.
+void initGraph([assets,
+ Map<String, Iterable<Iterable<Transformer>>> transformers]) =>
+ initStaticGraph(assets, transformers: transformers);
+
+void initStaticGraph(assets, {Iterable<String> staticPackages,
+ Map<String, Iterable<Iterable<Transformer>>> transformers}) {
+ if (assets == null) assets = [];
+ if (staticPackages == null) staticPackages = [];
+ if (transformers == null) transformers = {};
+
+ _provider = new MockProvider(assets,
+ staticPackages: staticPackages,
+ additionalPackages: transformers.keys);
+ _barback = new Barback(_provider);
+ // Add a dummy listener to the log so it doesn't print to stdout.
+ _barback.log.listen((_) {});
+ _nextBuildResult = 0;
+ _nextLog = 0;
+
+ schedule(() => transformers.forEach(_barback.updateTransformers));
+
+ // There should be one successful build after adding all the transformers but
+ // before adding any sources.
+ if (!transformers.isEmpty) buildShouldSucceed();
+}
+
+/// Updates [assets] in the current [PackageProvider].
+///
+/// Each item in the list may either be an [AssetId] or a string that can be
+/// parsed as one.
+void updateSources(Iterable assets) {
+ assets = _parseAssets(assets);
+ schedule(() => _barback.updateSources(assets),
+ "updating ${assets.join(', ')}");
+}
+
+/// Updates [assets] in the current [PackageProvider].
+///
+/// Each item in the list may either be an [AssetId] or a string that can be
+/// parsed as one. Unlike [updateSources], this is not automatically scheduled
+/// and will be run synchronously when called.
+void updateSourcesSync(Iterable assets) =>
+ _barback.updateSources(_parseAssets(assets));
+
+/// Removes [assets] from the current [PackageProvider].
+///
+/// Each item in the list may either be an [AssetId] or a string that can be
+/// parsed as one.
+void removeSources(Iterable assets) {
+ assets = _parseAssets(assets);
+ schedule(() => _barback.removeSources(assets),
+ "removing ${assets.join(', ')}");
+}
+
+/// Removes [assets] from the current [PackageProvider].
+///
+/// Each item in the list may either be an [AssetId] or a string that can be
+/// parsed as one. Unlike [removeSources], this is not automatically scheduled
+/// and will be run synchronously when called.
+void removeSourcesSync(Iterable assets) =>
+ _barback.removeSources(_parseAssets(assets));
+
+/// Sets the transformers for [package] to [transformers].
+void updateTransformers(String package, Iterable<Iterable> transformers) {
+ schedule(() => _barback.updateTransformers(package, transformers),
+ "updating transformers for $package");
+}
+
+/// Parse a list of strings or [AssetId]s into a list of [AssetId]s.
+List<AssetId> _parseAssets(Iterable assets) {
+ return assets.map((asset) {
+ if (asset is String) return new AssetId.parse(asset);
+ return asset;
+ }).toList();
+}
+
+/// Schedules a change to the contents of an asset identified by [name] to
+/// [contents].
+///
+/// Does not update it in the graph.
+void modifyAsset(String name, String contents) {
+ schedule(() {
+ _provider._modifyAsset(name, contents);
+ }, "modify asset $name");
+}
+
+/// Schedules an error to be generated when loading the asset identified by
+/// [name].
+///
+/// Does not update the asset in the graph. If [async] is true, the error is
+/// thrown asynchronously.
+void setAssetError(String name, {bool async: true}) {
+ schedule(() {
+ _provider._setAssetError(name, async);
+ }, "set error for asset $name");
+}
+
+/// Schedules a pause of the internally created [PackageProvider].
+///
+/// All asset requests that the [PackageGraph] makes to the provider after this
+/// will not complete until [resumeProvider] is called.
+void pauseProvider() {
+ schedule(() => _provider._pause(), "pause provider");
+}
+
+/// Schedules an unpause of the provider after a call to [pauseProvider] and
+/// allows all pending asset loads to finish.
+void resumeProvider() {
+ schedule(() => _provider._resume(), "resume provider");
+}
+
+/// Asserts that the current build step shouldn't have finished by this point in
+/// the schedule.
+///
+/// This uses the same build counter as [buildShouldSucceed] and
+/// [buildShouldFail], so those can be used to validate build results before and
+/// after this.
+void buildShouldNotBeDone() {
+ _futureShouldNotCompleteUntil(
+ _barback.results.elementAt(_nextBuildResult),
+ schedule(() => pumpEventQueue(), "build should not terminate"),
+ "build");
+}
+
+/// Expects that the next [BuildResult] is a build success.
+void buildShouldSucceed() {
+ expect(_getNextBuildResult("build should succeed").then((result) {
+ result.errors.forEach(currentSchedule.signalError);
+ expect(result.succeeded, isTrue);
+ }), completes);
+}
+
+/// Expects that the next [BuildResult] emitted is a failure.
+///
+/// [matchers] is a list of matchers to match against the errors that caused the
+/// build to fail. Every matcher is expected to match an error, but the order of
+/// matchers is unimportant.
+void buildShouldFail(List matchers) {
+ expect(_getNextBuildResult("build should fail").then((result) {
+ expect(result.succeeded, isFalse);
+ expect(result.errors.length, equals(matchers.length));
+ for (var matcher in matchers) {
+ expect(result.errors, contains(matcher));
+ }
+ }), completes);
+}
+
+/// Expects that the nexted logged [LogEntry] matches [matcher] which may be
+/// either a [Matcher] or a string to match a literal string.
+void buildShouldLog(LogLevel level, matcher) {
+ expect(_getNextLog("build should log").then((log) {
+ expect(log.level, equals(level));
+ expect(log.message, matcher);
+ }), completes);
+}
+
+Future<BuildResult> _getNextBuildResult(String description) {
+ var result = currentSchedule.wrapFuture(
+ _barback.results.elementAt(_nextBuildResult++));
+ return schedule(() => result, description);
+}
+
+Future<LogEntry> _getNextLog(String description) {
+ var result = currentSchedule.wrapFuture(
+ _barback.log.elementAt(_nextLog++));
+ return schedule(() => result, description);
+}
+
+/// Schedules an expectation that the graph will deliver an asset matching
+/// [name] and [contents].
+///
+/// [contents] may be a [String] or a [Matcher] that matches a string. If
+/// [contents] is omitted, defaults to the asset's filename without an extension
+/// (which is the same default that [initGraph] uses).
+void expectAsset(String name, [contents]) {
+ var id = new AssetId.parse(name);
+
+ if (contents == null) {
+ contents = pathos.basenameWithoutExtension(id.path);
+ }
+
+ schedule(() {
+ return _barback.getAssetById(id).then((asset) {
+ // TODO(rnystrom): Make an actual Matcher class for this.
+ expect(asset.id, equals(id));
+ expect(asset.readAsString(), completion(contents));
+ });
+ }, "get asset $name");
+}
+
+/// Schedules an expectation that the graph will not find an asset matching
+/// [name].
+void expectNoAsset(String name) {
+ var id = new AssetId.parse(name);
+
+ // Make sure the future gets the error.
+ schedule(() {
+ return _barback.getAssetById(id).then((asset) {
+ fail("Should have thrown error but got $asset.");
+ }).catchError((error) {
+ expect(error, new isInstanceOf<AssetNotFoundException>());
+ expect(error.id, equals(id));
+ });
+ }, "get asset $name");
+}
+
+/// Schedules an expectation that the graph will output all of the given
+/// assets, and no others.
+///
+/// [assets] may be an iterable of asset id strings, in which case this asserts
+/// that the graph outputs exactly the assets with those ids. It may also be a
+/// map from asset id strings to asset contents, in which case the contents must
+/// also match.
+void expectAllAssets(assets) {
+ var expected;
+ var expectedString;
+ if (assets is Map) {
+ expected = mapMapKeys(assets, (key, _) => new AssetId.parse(key));
+ expectedString = expected.toString();
+ } else {
+ expected = assets.map((asset) => new AssetId.parse(asset));
+ expectedString = expected.join(', ');
+ }
+
+ schedule(() {
+ return _barback.getAllAssets().then((actualAssets) {
+ var actualIds = actualAssets.map((asset) => asset.id).toSet();
+
+ if (expected is Map) {
+ expected.forEach((id, contents) {
+ expect(actualIds, contains(id));
+ actualIds.remove(id);
+ expect(actualAssets[id].readAsString(), completion(equals(contents)));
+ });
+ } else {
+ for (var id in expected) {
+ expect(actualIds, contains(id));
+ actualIds.remove(id);
+ }
+ }
+
+ expect(actualIds, isEmpty);
+ });
+ }, "get all assets, expecting $expectedString");
+}
+
+/// Schedules an expectation that [Barback.getAllAssets] will return a [Future]
+/// that completes to a error that matches [matcher].
+///
+/// If [match] is a [List], then it expects the completed error to be an
+/// [AggregateException] whose errors match each matcher in the list. Otherwise,
+/// [match] should be a single matcher that the error should match.
+void expectAllAssetsShouldFail(Matcher matcher) {
+ schedule(() {
+ expect(_barback.getAllAssets(), throwsA(matcher));
+ }, "get all assets should fail");
+}
+
+/// Schedules an expectation that a [getAssetById] call for the given asset
+/// won't terminate at this point in the schedule.
+void expectAssetDoesNotComplete(String name) {
+ var id = new AssetId.parse(name);
+
+ schedule(() {
+ return _futureShouldNotCompleteUntil(
+ _barback.getAssetById(id),
+ pumpEventQueue(),
+ "asset $id");
+ }, "asset $id should not complete");
+}
+
+/// Returns a matcher for an [AggregateException] containing errors that match
+/// [matchers].
+Matcher isAggregateException(Iterable<Matcher> errors) {
+ // Match the aggregate error itself.
+ var matchers = [
+ new isInstanceOf<AggregateException>(),
+ transform((error) => error.errors, hasLength(errors.length),
+ 'errors.length == ${errors.length}')
+ ];
+
+ // Make sure its contained errors match the matchers.
+ for (var error in errors) {
+ matchers.add(transform((error) => error.errors, contains(error),
+ error.toString()));
+ }
+
+ return allOf(matchers);
+}
+
+/// Returns a matcher for an [AssetNotFoundException] with the given [id].
+Matcher isAssetNotFoundException(String name) {
+ var id = new AssetId.parse(name);
+ return allOf(
+ new isInstanceOf<AssetNotFoundException>(),
+ predicate((error) => error.id == id, 'id == $name'));
+}
+
+/// Returns a matcher for an [AssetCollisionException] with the given [id].
+Matcher isAssetCollisionException(String name) {
+ var id = new AssetId.parse(name);
+ return allOf(
+ new isInstanceOf<AssetCollisionException>(),
+ predicate((error) => error.id == id, 'id == $name'));
+}
+
+/// Returns a matcher for a [MissingInputException] with the given [id].
+Matcher isMissingInputException(String name) {
+ var id = new AssetId.parse(name);
+ return allOf(
+ new isInstanceOf<MissingInputException>(),
+ predicate((error) => error.id == id, 'id == $name'));
+}
+
+/// Returns a matcher for an [InvalidOutputException] with the given id.
+Matcher isInvalidOutputException(String name) {
+ var id = new AssetId.parse(name);
+ return allOf(
+ new isInstanceOf<InvalidOutputException>(),
+ predicate((error) => error.id == id, 'id == $name'));
+}
+
+/// Returns a matcher for an [AssetLoadException] with the given id and a
+/// wrapped error that matches [error].
+Matcher isAssetLoadException(String name, error) {
+ var id = new AssetId.parse(name);
+ return allOf(
+ new isInstanceOf<AssetLoadException>(),
+ transform((error) => error.id, equals(id), 'id'),
+ transform((error) => error.error, wrapMatcher(error), 'error'));
+}
+
+/// Returns a matcher for a [TransformerException] with a wrapped error that
+/// matches [error].
+Matcher isTransformerException(error) {
+ return allOf(
+ new isInstanceOf<TransformerException>(),
+ transform((error) => error.error, wrapMatcher(error), 'error'));
+}
+
+/// Returns a matcher for a [MockLoadException] with the given [id].
+Matcher isMockLoadException(String name) {
+ var id = new AssetId.parse(name);
+ return allOf(
+ new isInstanceOf<MockLoadException>(),
+ predicate((error) => error.id == id, 'id == $name'));
+}
+
+/// Returns a matcher that runs [transformation] on its input, then matches
+/// the output against [matcher].
+///
+/// [description] should be a noun phrase that describes the relation of the
+/// output of [transformation] to its input.
+Matcher transform(transformation(value), matcher, String description) =>
+ new _TransformMatcher(transformation, wrapMatcher(matcher), description);
+
+class _TransformMatcher extends Matcher {
+ final Function _transformation;
+ final Matcher _matcher;
+ final String _description;
+
+ _TransformMatcher(this._transformation, this._matcher, this._description);
+
+ bool matches(item, Map matchState) =>
+ _matcher.matches(_transformation(item), matchState);
+
+ Description describe(Description description) =>
+ description.add(_description).add(' ').addDescriptionOf(_matcher);
+}
+
+/// Asserts that [future] shouldn't complete until after [delay] completes.
+///
+/// Once [delay] completes, the output of [future] is ignored, even if it's an
+/// error.
+///
+/// [description] should describe [future].
+Future _futureShouldNotCompleteUntil(Future future, Future delay,
+ String description) {
+ var trace = new Trace.current();
+ var cancelable = new CancelableFuture(future);
+ cancelable.then((result) {
+ currentSchedule.signalError(
+ new Exception("Expected $description not to complete here, but it "
+ "completed with result: $result"),
+ trace);
+ }).catchError((error) {
+ currentSchedule.signalError(error);
+ });
+
+ return delay.then((_) => cancelable.cancel());
+}
+
+/// An [AssetProvider] that provides the given set of assets.
+class MockProvider implements StaticPackageProvider {
+ Iterable<String> get packages =>
+ _assets.keys.toSet().difference(staticPackages);
+
+ final Set<String> staticPackages;
+
+ final Map<String, AssetSet> _assets;
+
+ /// The set of assets for which [MockLoadException]s should be emitted if
+ /// they're loaded.
+ final _errors = new Set<AssetId>();
+
+ /// The set of assets for which synchronous [MockLoadException]s should be
+ /// emitted if they're loaded.
+ final _syncErrors = new Set<AssetId>();
+
+ /// The completer that [getAsset()] is waiting on to complete when paused.
+ ///
+ /// If `null` it will return the asset immediately.
+ Completer _pauseCompleter;
+
+ /// Tells the provider to wait during [getAsset] until [complete()]
+ /// is called.
+ ///
+ /// Lets you test the asynchronous behavior of loading.
+ void _pause() {
+ _pauseCompleter = new Completer();
+ }
+
+ void _resume() {
+ _pauseCompleter.complete();
+ _pauseCompleter = null;
+ }
+
+ MockProvider(assets, {Iterable<String> staticPackages,
+ Iterable<String> additionalPackages})
+ : staticPackages = staticPackages == null ? new Set() :
+ staticPackages.toSet(),
+ _assets = _normalizeAssets(assets, additionalPackages);
+
+ static Map<String, AssetSet> _normalizeAssets(assets,
+ Iterable<String> additionalPackages) {
+ var assetList;
+ if (assets is Map) {
+ assetList = assets.keys.map((asset) {
+ var id = new AssetId.parse(asset);
+ return new _MockAsset(id, assets[asset]);
+ });
+ } else if (assets is Iterable) {
+ assetList = assets.map((asset) {
+ var id = new AssetId.parse(asset);
+ var contents = pathos.basenameWithoutExtension(id.path);
+ return new _MockAsset(id, contents);
+ });
+ }
+
+ var assetMap = mapMapValues(groupBy(assetList, (asset) => asset.id.package),
+ (package, assets) => new AssetSet.from(assets));
+
+ // Make sure that packages that have transformers but no assets are
+ // considered by MockProvider to exist.
+ if (additionalPackages != null) {
+ for (var package in additionalPackages) {
+ assetMap.putIfAbsent(package, () => new AssetSet());
+ }
+ }
+
+ // If there are no assets or transformers, add a dummy package. This better
+ // simulates the real world, where there'll always be at least the
+ // entrypoint package.
+ return assetMap.isEmpty ? {"app": new AssetSet()} : assetMap;
+ }
+
+ void _modifyAsset(String name, String contents) {
+ var id = new AssetId.parse(name);
+ _errors.remove(id);
+ _syncErrors.remove(id);
+ (_assets[id.package][id] as _MockAsset).contents = contents;
+ }
+
+ void _setAssetError(String name, bool async) {
+ (async ? _errors : _syncErrors).add(new AssetId.parse(name));
+ }
+
+ Stream<AssetId> getAllAssetIds(String package) =>
+ new Stream.fromIterable(_assets[package].map((asset) => asset.id));
+
+ Future<Asset> getAsset(AssetId id) {
+ // Eagerly load the asset so we can test an asset's value changing between
+ // when a load starts and when it finishes.
+ var assets = _assets[id.package];
+ var asset;
+ if (assets != null) asset = assets[id];
+
+ if (_syncErrors.contains(id)) throw new MockLoadException(id);
+ var hasError = _errors.contains(id);
+
+ var future;
+ if (_pauseCompleter != null) {
+ future = _pauseCompleter.future;
+ } else {
+ future = new Future.value();
+ }
+
+ return future.then((_) {
+ if (hasError) throw new MockLoadException(id);
+ if (asset == null) throw new AssetNotFoundException(id);
+ return asset;
+ });
+ }
+}
+
+/// Error thrown for assets with [setAssetError] set.
+class MockLoadException implements Exception {
+ final AssetId id;
+
+ MockLoadException(this.id);
+
+ String toString() => "Error loading $id.";
+}
+
+/// An implementation of [Asset] that never hits the file system.
+class _MockAsset implements Asset {
+ final AssetId id;
+ String contents;
+
+ _MockAsset(this.id, this.contents);
+
+ Future<String> readAsString({Encoding encoding}) =>
+ new Future.value(contents);
+
+ Stream<List<int>> read() => throw new UnimplementedError();
+
+ String toString() => "MockAsset $id $contents";
+}
« no previous file with comments | « packages/barback/test/transformer_test.dart ('k') | packages/browser/LICENSE » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698