| Index: pkg/barback/test/utils.dart
|
| diff --git a/pkg/barback/test/utils.dart b/pkg/barback/test/utils.dart
|
| index fcda96266b3e226e1f6c97f9b8d42f64fb9f8072..ab41a59f7e3518ed801633b481b9b06f40d0501a 100644
|
| --- a/pkg/barback/test/utils.dart
|
| +++ b/pkg/barback/test/utils.dart
|
| @@ -10,6 +10,8 @@ import 'dart:io';
|
|
|
| import 'package:barback/barback.dart';
|
| import 'package:barback/src/asset_cascade.dart';
|
| +import 'package:barback/src/asset_set.dart';
|
| +import 'package:barback/src/cancelable_future.dart';
|
| import 'package:barback/src/package_graph.dart';
|
| import 'package:barback/src/utils.dart';
|
| import 'package:path/path.dart' as pathos;
|
| @@ -17,6 +19,14 @@ import 'package:scheduled_test/scheduled_test.dart';
|
| import 'package:stack_trace/stack_trace.dart';
|
| import 'package:unittest/compact_vm_config.dart';
|
|
|
| +export 'transformer/bad.dart';
|
| +export 'transformer/check_content.dart';
|
| +export 'transformer/create_asset.dart';
|
| +export 'transformer/many_to_one.dart';
|
| +export 'transformer/mock.dart';
|
| +export 'transformer/one_to_many.dart';
|
| +export 'transformer/rewrite.dart';
|
| +
|
| var _configured = false;
|
|
|
| MockProvider _provider;
|
| @@ -96,6 +106,16 @@ void modifyAsset(String name, String 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.
|
| +void setAssetError(String name) {
|
| + schedule(() {
|
| + _provider._setAssetError(name);
|
| + }, "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
|
| @@ -117,31 +137,16 @@ void resumeProvider() {
|
| /// [buildShouldFail], so those can be used to validate build results before and
|
| /// after this.
|
| void buildShouldNotBeDone() {
|
| - var resultAllowed = false;
|
| - var trace = new Trace.current();
|
| - _graph.results.elementAt(_nextBuildResult).then((result) {
|
| - if (resultAllowed) return;
|
| -
|
| - currentSchedule.signalError(
|
| - new Exception("Expected build not to terminate "
|
| - "here, but it terminated with result: $result"), trace);
|
| - }).catchError((error) {
|
| - if (resultAllowed) return;
|
| - currentSchedule.signalError(error);
|
| - });
|
| -
|
| - schedule(() {
|
| - // Pump the event queue in case the build completes out-of-band after we get
|
| - // here. If it does, we want to signal an error.
|
| - return pumpEventQueue().then((_) {
|
| - resultAllowed = true;
|
| - });
|
| - }, "ensuring build doesn't terminate");
|
| + _futureShouldNotCompleteUntil(
|
| + _graph.results.elementAt(_nextBuildResult),
|
| + schedule(() => pumpEventQueue(), "build should not terminate"),
|
| + "build");
|
| }
|
|
|
| /// Expects that the next [BuildResult] is a build success.
|
| void buildShouldSucceed() {
|
| expect(_getNextBuildResult().then((result) {
|
| + result.errors.forEach(currentSchedule.signalError);
|
| expect(result.succeeded, isTrue);
|
| }), completes);
|
| }
|
| @@ -190,9 +195,8 @@ void expectAsset(String name, [String contents]) {
|
| schedule(() {
|
| return _graph.getAssetById(id).then((asset) {
|
| // TODO(rnystrom): Make an actual Matcher class for this.
|
| - expect(asset, new isInstanceOf<MockAsset>());
|
| expect(asset.id, equals(id));
|
| - expect(asset.contents, equals(contents));
|
| + expect(asset.readAsString(), completion(equals(contents)));
|
| });
|
| }, "get asset $name");
|
| }
|
| @@ -213,12 +217,25 @@ void expectNoAsset(String name) {
|
| }, "get asset $name");
|
| }
|
|
|
| +/// 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(
|
| + _graph.getAssetById(id),
|
| + pumpEventQueue(),
|
| + "asset $id");
|
| + }, "asset $id should not complete");
|
| +}
|
| +
|
| /// 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 is $name'));
|
| + predicate((error) => error.id == id, 'id == $name'));
|
| }
|
|
|
| /// Returns a matcher for an [AssetCollisionException] with the given [id].
|
| @@ -226,7 +243,7 @@ Matcher isAssetCollisionException(String name) {
|
| var id = new AssetId.parse(name);
|
| return allOf(
|
| new isInstanceOf<AssetCollisionException>(),
|
| - predicate((error) => error.id == id, 'id is $name'));
|
| + predicate((error) => error.id == id, 'id == $name'));
|
| }
|
|
|
| /// Returns a matcher for a [MissingInputException] with the given [id].
|
| @@ -234,7 +251,7 @@ Matcher isMissingInputException(String name) {
|
| var id = new AssetId.parse(name);
|
| return allOf(
|
| new isInstanceOf<MissingInputException>(),
|
| - predicate((error) => error.id == id, 'id is $name'));
|
| + predicate((error) => error.id == id, 'id == $name'));
|
| }
|
|
|
| /// Returns a matcher for an [InvalidOutputException] with the given id and
|
| @@ -244,7 +261,37 @@ Matcher isInvalidOutputException(String package, String name) {
|
| return allOf(
|
| new isInstanceOf<InvalidOutputException>(),
|
| predicate((error) => error.package == package, 'package is $package'),
|
| - predicate((error) => error.id == id, 'id is $name'));
|
| + predicate((error) => error.id == id, 'id == $name'));
|
| +}
|
| +
|
| +/// 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'));
|
| +}
|
| +
|
| +/// 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.
|
| @@ -253,6 +300,10 @@ class MockProvider implements PackageProvider {
|
|
|
| Map<String, _MockPackage> _packages;
|
|
|
| + /// The set of assets for which [MockLoadException]s should be emitted if
|
| + /// they're loaded.
|
| + final _errors = new Set<AssetId>();
|
| +
|
| /// The completer that [getAsset()] is waiting on to complete when paused.
|
| ///
|
| /// If `null` it will return the asset immediately.
|
| @@ -277,13 +328,13 @@ class MockProvider implements PackageProvider {
|
| if (assets is Map) {
|
| assetList = assets.keys.map((asset) {
|
| var id = new AssetId.parse(asset);
|
| - return new MockAsset(id, assets[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);
|
| + return new _MockAsset(id, contents);
|
| });
|
| }
|
|
|
| @@ -291,21 +342,26 @@ class MockProvider implements PackageProvider {
|
| (package, assets) {
|
| var packageTransformers = transformers[package];
|
| if (packageTransformers == null) packageTransformers = [];
|
| - return new _MockPackage(assets, packageTransformers.toList());
|
| + return new _MockPackage(
|
| + new AssetSet.from(assets), packageTransformers.toList());
|
| });
|
|
|
| // 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.
|
| - if (_packages.isEmpty) _packages = {"app": new _MockPackage([], [])};
|
| + if (_packages.isEmpty) {
|
| + _packages = {"app": new _MockPackage(new AssetSet(), [])};
|
| + }
|
| }
|
|
|
| void _modifyAsset(String name, String contents) {
|
| var id = new AssetId.parse(name);
|
| - var asset = _packages[id.package].assets.firstWhere((a) => a.id == id);
|
| - asset.contents = contents;
|
| + _errors.remove(id);
|
| + _packages[id.package].assets[id].contents = contents;
|
| }
|
|
|
| + void _setAssetError(String name) => _errors.add(new AssetId.parse(name));
|
| +
|
| List<AssetId> listAssets(String package, {String within}) {
|
| if (within != null) {
|
| throw new UnimplementedError("Doesn't handle 'within' yet.");
|
| @@ -323,6 +379,14 @@ class MockProvider implements PackageProvider {
|
| }
|
|
|
| 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 package = _packages[id.package];
|
| + var asset;
|
| + if (package != null) asset = package.assets[id];
|
| +
|
| + var hasError = _errors.contains(id);
|
| +
|
| var future;
|
| if (_pauseCompleter != null) {
|
| future = _pauseCompleter.future;
|
| @@ -331,225 +395,38 @@ class MockProvider implements PackageProvider {
|
| }
|
|
|
| return future.then((_) {
|
| - var package = _packages[id.package];
|
| - if (package == null) throw new AssetNotFoundException(id);
|
| -
|
| - return package.assets.firstWhere((asset) => asset.id == id,
|
| - orElse: () => throw new AssetNotFoundException(id));
|
| + 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.";
|
| +}
|
| +
|
| /// Used by [MockProvider] to keep track of which assets and transformers exist
|
| /// for each package.
|
| class _MockPackage {
|
| - final List<MockAsset> assets;
|
| + final AssetSet assets;
|
| final List<List<Transformer>> transformers;
|
|
|
| _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers)
|
| : transformers = transformers.map((phase) => phase.toList()).toList();
|
| }
|
|
|
| -/// A [Transformer] that takes assets ending with one extension and generates
|
| -/// assets with a given extension.
|
| -///
|
| -/// Appends the output extension to the contents of the input file.
|
| -class RewriteTransformer extends Transformer {
|
| - final String from;
|
| - final String to;
|
| -
|
| - /// The number of times the transformer has been applied.
|
| - int numRuns = 0;
|
| -
|
| - /// The number of currently running transforms.
|
| - int _runningTransforms = 0;
|
| -
|
| - /// The completer that the transform is waiting on to complete.
|
| - ///
|
| - /// If `null` the transform will complete immediately.
|
| - Completer _wait;
|
| -
|
| - /// A future that completes when the first apply of this transformer begins.
|
| - Future get started => _started.future;
|
| - final _started = new Completer();
|
| -
|
| - /// Creates a transformer that rewrites assets whose extension is [from] to
|
| - /// one whose extension is [to].
|
| - ///
|
| - /// [to] may be a space-separated list in which case multiple outputs will be
|
| - /// created for each input.
|
| - RewriteTransformer(this.from, this.to);
|
| -
|
| - /// `true` if any transforms are currently running.
|
| - bool get isRunning => _runningTransforms > 0;
|
| -
|
| - /// Tells the transform to wait during its transformation until [complete()]
|
| - /// is called.
|
| - ///
|
| - /// Lets you test the asynchronous behavior of transformers.
|
| - void wait() {
|
| - _wait = new Completer();
|
| - }
|
| -
|
| - void complete() {
|
| - _wait.complete();
|
| - _wait = null;
|
| - }
|
| -
|
| - Future<bool> isPrimary(Asset asset) {
|
| - return new Future.value(asset.id.extension == ".$from");
|
| - }
|
| -
|
| - Future apply(Transform transform) {
|
| - numRuns++;
|
| - if (!_started.isCompleted) _started.complete();
|
| - _runningTransforms++;
|
| - return transform.primaryInput.then((input) {
|
| - return Future.wait(to.split(" ").map((extension) {
|
| - var id = transform.primaryId.changeExtension(".$extension");
|
| - return input.readAsString().then((content) {
|
| - transform.addOutput(new MockAsset(id, "$content.$extension"));
|
| - });
|
| - })).then((_) {
|
| - if (_wait != null) return _wait.future;
|
| - });
|
| - }).whenComplete(() {
|
| - _runningTransforms--;
|
| - });
|
| - }
|
| -
|
| - String toString() => "$from->$to";
|
| -}
|
| -
|
| -/// A [Transformer] that takes an input asset that contains a comma-separated
|
| -/// list of paths and outputs a file for each path.
|
| -class OneToManyTransformer extends Transformer {
|
| - final String extension;
|
| -
|
| - /// The number of times the transformer has been applied.
|
| - int numRuns = 0;
|
| -
|
| - /// Creates a transformer that consumes assets with [extension].
|
| - ///
|
| - /// That file contains a comma-separated list of paths and it will output
|
| - /// files at each of those paths.
|
| - OneToManyTransformer(this.extension);
|
| -
|
| - Future<bool> isPrimary(Asset asset) {
|
| - return new Future.value(asset.id.extension == ".$extension");
|
| - }
|
| -
|
| - Future apply(Transform transform) {
|
| - numRuns++;
|
| - return transform.primaryInput.then((input) {
|
| - return input.readAsString().then((lines) {
|
| - for (var line in lines.split(",")) {
|
| - var id = new AssetId(transform.primaryId.package, line);
|
| - transform.addOutput(new MockAsset(id, "spread $extension"));
|
| - }
|
| - });
|
| - });
|
| - }
|
| -
|
| - String toString() => "1->many $extension";
|
| -}
|
| -
|
| -/// A transformer that uses the contents of a file to define the other inputs.
|
| -///
|
| -/// Outputs a file with the same name as the primary but with an "out"
|
| -/// extension containing the concatenated contents of all non-primary inputs.
|
| -class ManyToOneTransformer extends Transformer {
|
| - final String extension;
|
| -
|
| - /// The number of times the transformer has been applied.
|
| - int numRuns = 0;
|
| -
|
| - /// Creates a transformer that consumes assets with [extension].
|
| - ///
|
| - /// That file contains a comma-separated list of paths and it will input
|
| - /// files at each of those paths.
|
| - ManyToOneTransformer(this.extension);
|
| -
|
| - Future<bool> isPrimary(Asset asset) {
|
| - return new Future.value(asset.id.extension == ".$extension");
|
| - }
|
| -
|
| - Future apply(Transform transform) {
|
| - numRuns++;
|
| - return transform.primaryInput.then((primary) {
|
| - return primary.readAsString().then((contents) {
|
| - // Get all of the included inputs.
|
| - var inputs = contents.split(",").map((path) {
|
| - var id = new AssetId(transform.primaryId.package, path);
|
| - return transform.getInput(id);
|
| - });
|
| -
|
| - return Future.wait(inputs);
|
| - }).then((inputs) {
|
| - // Concatenate them to one output.
|
| - var output = "";
|
| - return Future.forEach(inputs, (input) {
|
| - return input.readAsString().then((contents) {
|
| - output += contents;
|
| - });
|
| - }).then((_) {
|
| - var id = transform.primaryId.changeExtension(".out");
|
| - transform.addOutput(new MockAsset(id, output));
|
| - });
|
| - });
|
| - });
|
| - }
|
| -
|
| - String toString() => "many->1 $extension";
|
| -}
|
| -
|
| -/// A transformer that throws an exception when run, after generating the
|
| -/// given outputs.
|
| -class BadTransformer extends Transformer {
|
| - /// The error it throws.
|
| - static const ERROR = "I am a bad transformer!";
|
| -
|
| - /// The list of asset names that it should output.
|
| - final List<String> outputs;
|
| -
|
| - BadTransformer(this.outputs);
|
| -
|
| - Future<bool> isPrimary(Asset asset) => new Future.value(true);
|
| - Future apply(Transform transform) {
|
| - return newFuture(() {
|
| - // Create the outputs first.
|
| - for (var output in outputs) {
|
| - var id = new AssetId.parse(output);
|
| - transform.addOutput(new MockAsset(id, output));
|
| - }
|
| -
|
| - // Then fail.
|
| - throw ERROR;
|
| - });
|
| - }
|
| -}
|
| -
|
| -/// A transformer that outputs an asset with the given id.
|
| -class CreateAssetTransformer extends Transformer {
|
| - final String output;
|
| -
|
| - CreateAssetTransformer(this.output);
|
| -
|
| - Future<bool> isPrimary(Asset asset) => new Future.value(true);
|
| -
|
| - Future apply(Transform transform) {
|
| - return newFuture(() {
|
| - transform.addOutput(new MockAsset(new AssetId.parse(output), output));
|
| - });
|
| - }
|
| -}
|
| -
|
| /// An implementation of [Asset] that never hits the file system.
|
| -class MockAsset implements Asset {
|
| +class _MockAsset implements Asset {
|
| final AssetId id;
|
| String contents;
|
|
|
| - MockAsset(this.id, this.contents);
|
| + _MockAsset(this.id, this.contents);
|
|
|
| Future<String> readAsString({Encoding encoding}) =>
|
| new Future.value(contents);
|
| @@ -557,4 +434,4 @@ class MockAsset implements Asset {
|
| Stream<List<int>> read() => throw new UnimplementedError();
|
|
|
| String toString() => "MockAsset $id $contents";
|
| -}
|
| +}
|
|
|