Add support for caching results of shared modules.
This is important as we will soon add support for compiling the sdk as a
module and we would like to only compile it once when running a suite of
tests.
+ also enable caching in the dart2js pipeline test.
Change-Id: Ic9043f868123164f3ab425ba73f7428416b05fc0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103485
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/modular_test/lib/src/dependency_parser.dart b/pkg/modular_test/lib/src/dependency_parser.dart
deleted file mode 100644
index 51f2460..0000000
--- a/pkg/modular_test/lib/src/dependency_parser.dart
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2019, 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.
-
-/// This library defines how to read module dependencies from a Yaml
-/// specification. We expect to find specifications written in this format:
-///
-/// dependencies:
-/// b: a
-/// main: [b, expect]
-///
-/// Where:
-/// - Each name corresponds to a module.
-/// - Module names correlate to either a file, a folder, or a package.
-/// - A map entry contains all the dependencies of a module, if any.
-/// - If a module has a single dependency, it can be written as a single
-/// value.
-///
-/// The logic in this library mostly treats these names as strings, separately
-/// `loader.dart` is responsible for validating and attaching this dependency
-/// information to a set of module definitions.
-import 'package:yaml/yaml.dart';
-
-/// Parses [contents] containing a module dependencies specification written in
-/// yaml, and returns a normalized dependency map.
-///
-/// Note: some values in the map may not have a corresponding key. That may be
-/// the case for modules that have no dependencies and modules that are not
-/// specified in [contents] (e.g. modules that are supported by default).
-Map<String, List<String>> parseDependencyMap(String contents) {
- var spec = loadYaml(contents);
- if (spec is! YamlMap) {
- return _invalidSpecification("spec is not a map");
- }
- var dependencies = spec['dependencies'];
- if (dependencies == null) {
- return _invalidSpecification("no dependencies section");
- }
- if (dependencies is! YamlMap) {
- return _invalidSpecification("dependencies is not a map");
- }
-
- Map<String, List<String>> normalizedMap = {};
- dependencies.forEach((key, value) {
- if (key is! String) {
- _invalidSpecification("key: '$key' is not a string");
- }
- normalizedMap[key] = [];
- if (value is String) {
- normalizedMap[key].add(value);
- } else if (value is List) {
- value.forEach((entry) {
- if (entry is! String) {
- _invalidSpecification("entry: '$entry' is not a string");
- }
- normalizedMap[key].add(entry);
- });
- } else {
- _invalidSpecification(
- "entry: '$value' is not a string or a list of strings");
- }
- });
- return normalizedMap;
-}
-
-_invalidSpecification(String message) {
- throw new InvalidSpecificationError(message);
-}
-
-class InvalidSpecificationError extends Error {
- final String message;
- InvalidSpecificationError(this.message);
- String toString() => "Invalid specification: $message";
-}
diff --git a/pkg/modular_test/lib/src/io_pipeline.dart b/pkg/modular_test/lib/src/io_pipeline.dart
index ed77f2d..910eb3d 100644
--- a/pkg/modular_test/lib/src/io_pipeline.dart
+++ b/pkg/modular_test/lib/src/io_pipeline.dart
@@ -26,49 +26,103 @@
}
class IOPipeline extends Pipeline<IOModularStep> {
- /// A folder per step. The key is the data id produced by a specific step.
+ /// Folder that holds the results of each step during the run of the pipeline.
///
- /// This contains internal state used during the run of the pipeline, but is
- /// expected to be null before and after the pipeline is executed.
- Map<DataId, Uri> _tmpFolders;
- Map<DataId, Uri> get tmpFoldersForTesting => _tmpFolders;
- bool saveFoldersForTesting;
+ /// This value is usually null before and after the pipeline runs, but will be
+ /// non-null in two cases:
+ ///
+ /// * for testing purposes when using [saveIntermediateResultsForTesting].
+ ///
+ /// * to share results across pipeline runs when using [cacheSharedModules].
+ ///
+ /// When using [cacheSharedModules] the pipeline will only reuse data for
+ /// modules that are known to be shared (e.g. shared packages and sdk
+ /// libraries), and not modules that are test specific. File names will be
+ /// specific enough so that we can keep separate the artifacts created from
+ /// running tools under different configurations (with different flags).
+ Uri _resultsFolderUri;
+ Uri get resultFolderUriForTesting => _resultsFolderUri;
- IOPipeline(List<IOModularStep> steps, {this.saveFoldersForTesting: false})
- : super(steps);
+ /// A unique number to denote the current modular test configuration.
+ ///
+ /// When using [cacheSharedModules], a test can resuse the output of a
+ /// previous run of this pipeline if that output was generated with the same
+ /// configuration.
+ int _currentConfiguration;
+
+ final ConfigurationRegistry _registry;
+
+ /// Whether to keep alive the temporary folder used to store intermediate
+ /// results in order to inspect it later in test.
+ final bool saveIntermediateResultsForTesting;
+
+ IOPipeline(List<IOModularStep> steps,
+ {this.saveIntermediateResultsForTesting: false,
+ bool cacheSharedModules: false})
+ : _registry = cacheSharedModules ? new ConfigurationRegistry() : null,
+ super(steps, cacheSharedModules);
@override
Future<void> run(ModularTest test) async {
- assert(_tmpFolders == null);
- _tmpFolders = {};
+ var resultsDir = null;
+ if (_resultsFolderUri == null) {
+ resultsDir = await Directory.systemTemp.createTemp('modular_test_res-');
+ _resultsFolderUri = resultsDir.uri;
+ }
+ if (cacheSharedModules) {
+ _currentConfiguration = _registry.computeConfigurationId(test);
+ }
await super.run(test);
- if (!saveFoldersForTesting) {
- for (var folder in _tmpFolders.values) {
- await Directory.fromUri(folder).delete(recursive: true);
- }
- _tmpFolders = null;
+ if (resultsDir != null &&
+ !saveIntermediateResultsForTesting &&
+ !cacheSharedModules) {
+ await resultsDir.delete(recursive: true);
+ _resultsFolderUri = null;
+ }
+ if (!saveIntermediateResultsForTesting) {
+ _currentConfiguration = null;
+ }
+ }
+
+ /// Delete folders that were kept around either because of
+ /// [saveIntermediateResultsForTesting] or because of [cacheSharedModules].
+ Future<void> cleanup() async {
+ if (saveIntermediateResultsForTesting || cacheSharedModules) {
+ await Directory.fromUri(_resultsFolderUri).delete(recursive: true);
}
}
@override
Future<void> runStep(IOModularStep step, Module module,
Map<Module, Set<DataId>> visibleData) async {
- // Since data ids are unique throughout the pipeline, we use the first
- // result data id as a hint for the name of the temporary folder of a step.
- var stepFolder;
- for (var dataId in step.resultData) {
- stepFolder ??=
- await Directory.systemTemp.createTemp('modular_test_${dataId}-');
- _tmpFolders[dataId] ??=
- (await Directory.systemTemp.createTemp('modular_test_${dataId}_res-'))
- .uri;
+ if (cacheSharedModules && module.isShared) {
+ // If all expected outputs are already available, skip the step.
+ bool allCachedResultsFound = true;
+ for (var dataId in step.resultData) {
+ var cachedFile = File.fromUri(_resultsFolderUri
+ .resolve(_toFileName(module, dataId, configSpecific: true)));
+ if (!await cachedFile.exists()) {
+ allCachedResultsFound = false;
+ break;
+ }
+ }
+ if (allCachedResultsFound) {
+ step.notifyCached(module);
+ return;
+ }
}
+
+ // Each step is executed in a separate folder. To make it easier to debug
+ // issues, we include one of the step data ids in the name of the folder.
+ var stepId = step.resultData.first;
+ var stepFolder =
+ await Directory.systemTemp.createTemp('modular_test_${stepId}-');
for (var module in visibleData.keys) {
for (var dataId in visibleData[module]) {
- var filename = "${module.name}.${dataId.name}";
- var assetUri = _tmpFolders[dataId].resolve(filename);
- await File.fromUri(assetUri)
- .copy(stepFolder.uri.resolve(filename).toFilePath());
+ var assetUri = _resultsFolderUri
+ .resolve(_toFileName(module, dataId, configSpecific: true));
+ await File.fromUri(assetUri).copy(
+ stepFolder.uri.resolve(_toFileName(module, dataId)).toFilePath());
}
}
if (step.needsSources) {
@@ -81,19 +135,31 @@
}
await step.execute(module, stepFolder.uri,
- (Module m, DataId id) => Uri.parse("${m.name}.${id.name}"));
+ (Module m, DataId id) => Uri.parse(_toFileName(m, id)));
for (var dataId in step.resultData) {
var outputFile =
- File.fromUri(stepFolder.uri.resolve("${module.name}.${dataId.name}"));
+ File.fromUri(stepFolder.uri.resolve(_toFileName(module, dataId)));
if (!await outputFile.exists()) {
throw StateError(
"Step '${step.runtimeType}' didn't produce an output file");
}
- await outputFile.copy(_tmpFolders[dataId]
- .resolve("${module.name}.${dataId.name}")
+ await outputFile.copy(_resultsFolderUri
+ .resolve(_toFileName(module, dataId, configSpecific: true))
.toFilePath());
}
await stepFolder.delete(recursive: true);
}
+
+ String _toFileName(Module module, DataId dataId,
+ {bool configSpecific: false}) {
+ var prefix =
+ cacheSharedModules && configSpecific && _currentConfiguration != null
+ ? _currentConfiguration
+ : '';
+ return "$prefix${module.name}.${dataId.name}";
+ }
+
+ String configSpecificResultFileNameForTesting(Module module, DataId dataId) =>
+ _toFileName(module, dataId, configSpecific: true);
}
diff --git a/pkg/modular_test/lib/src/loader.dart b/pkg/modular_test/lib/src/loader.dart
index ce00e49..61b8969 100644
--- a/pkg/modular_test/lib/src/loader.dart
+++ b/pkg/modular_test/lib/src/loader.dart
@@ -17,11 +17,11 @@
/// [defaultPackagesInput]. The list of packages provided is expected to
/// be disjoint with those in [defaultPackagesInput].
/// * a modules.yaml file: a specification of dependencies between modules.
-/// The format is described in `dependencies_parser.dart`.
+/// The format is described in `test_specification_parser.dart`.
import 'dart:io';
import 'dart:convert';
import 'suite.dart';
-import 'dependency_parser.dart';
+import 'test_specification_parser.dart';
import 'find_sdk_root.dart';
import 'package:package_config/packages_file.dart' as package_config;
@@ -40,7 +40,7 @@
Map<String, Uri> defaultPackages =
package_config.parse(_defaultPackagesInput, root);
Map<String, Module> modules = {};
- String spec;
+ String specString;
Module mainModule;
Map<String, Uri> packages = {};
await for (var entry in folder.list(recursive: false)) {
@@ -69,7 +69,7 @@
List<int> packagesBytes = await entry.readAsBytes();
packages = package_config.parse(packagesBytes, entryUri);
} else if (fileName == 'modules.yaml') {
- spec = await entry.readAsString();
+ specString = await entry.readAsString();
}
} else {
assert(entry is Directory);
@@ -88,7 +88,7 @@
packageBase: Uri.parse('$moduleName/'));
}
}
- if (spec == null) {
+ if (specString == null) {
return _invalidTest("modules.yaml file is missing");
}
if (mainModule == null) {
@@ -97,12 +97,15 @@
_addDefaultPackageEntries(packages, defaultPackages);
await _addModulePerPackage(packages, modules);
- _attachDependencies(parseDependencyMap(spec), modules);
- _attachDependencies(parseDependencyMap(_defaultPackagesSpec), modules);
+ TestSpecification spec = parseTestSpecification(specString);
+ _attachDependencies(spec.dependencies, modules);
+ _attachDependencies(
+ parseTestSpecification(_defaultPackagesSpec).dependencies, modules);
_detectCyclesAndRemoveUnreachable(modules, mainModule);
var sortedModules = modules.values.toList()
..sort((a, b) => a.name.compareTo(b.name));
- return new ModularTest(sortedModules, mainModule);
+ var sortedFlags = spec.flags.toList()..sort();
+ return new ModularTest(sortedModules, mainModule, sortedFlags);
}
/// Returns all source files recursively found in a folder as relative URIs.
@@ -169,7 +172,7 @@
// module that is part of the test (package name and module name should
// match).
modules[packageName] = Module(packageName, [], rootUri, sources,
- isPackage: true, packageBase: Uri.parse('lib/'));
+ isPackage: true, packageBase: Uri.parse('lib/'), isShared: true);
}
}
}
diff --git a/pkg/modular_test/lib/src/memory_pipeline.dart b/pkg/modular_test/lib/src/memory_pipeline.dart
index 9ad5c43..a107af4 100644
--- a/pkg/modular_test/lib/src/memory_pipeline.dart
+++ b/pkg/modular_test/lib/src/memory_pipeline.dart
@@ -27,20 +27,60 @@
/// A copy of [_result] at the time the pipeline last finished running.
Map<Module, Map<DataId, Object>> resultsForTesting;
- MemoryPipeline(this._sources, List<MemoryModularStep> steps) : super(steps);
+ final ConfigurationRegistry _registry;
+
+ /// Cache of results when [cacheSharedModules] is true
+ final List<Map<Module, Map<DataId, Object>>> _resultCache;
+
+ MemoryPipeline(this._sources, List<MemoryModularStep> steps,
+ {bool cacheSharedModules: false})
+ : _registry = cacheSharedModules ? new ConfigurationRegistry() : null,
+ _resultCache = cacheSharedModules ? [] : null,
+ super(steps, cacheSharedModules);
@override
Future<void> run(ModularTest test) async {
- assert(_results == null);
_results = {};
+ Map<Module, Map<DataId, Object>> cache = null;
+ if (cacheSharedModules) {
+ int id = _registry.computeConfigurationId(test);
+ if (id < _resultCache.length) {
+ cache = _resultCache[id];
+ } else {
+ assert(id == _resultCache.length);
+ _resultCache.add(cache = {});
+ }
+ _results.addAll(cache);
+ }
await super.run(test);
resultsForTesting = _results;
+ if (cacheSharedModules) {
+ for (var module in _results.keys) {
+ if (module.isShared) {
+ cache[module] = _results[module];
+ }
+ }
+ }
_results = null;
}
@override
Future<void> runStep(MemoryModularStep step, Module module,
Map<Module, Set<DataId>> visibleData) async {
+ if (cacheSharedModules && module.isShared) {
+ bool allCachedResultsFound = true;
+ for (var dataId in step.resultData) {
+ if (_results[module] == null || _results[module][dataId] == null) {
+ allCachedResultsFound = false;
+ break;
+ }
+ }
+ if (allCachedResultsFound) {
+ step.notifyCached(module);
+ return;
+ }
+ }
+
Map<Module, Map<DataId, Object>> inputData = {};
visibleData.forEach((module, dataIdSet) {
inputData[module] = {};
diff --git a/pkg/modular_test/lib/src/pipeline.dart b/pkg/modular_test/lib/src/pipeline.dart
index 64e08a7..029e5b2 100644
--- a/pkg/modular_test/lib/src/pipeline.dart
+++ b/pkg/modular_test/lib/src/pipeline.dart
@@ -48,6 +48,9 @@
this.moduleDataNeeded: const [],
this.resultData,
this.onlyOnMain: false});
+
+ /// Notifies that the step was not executed, but cached instead.
+ void notifyCached(Module module) {}
}
/// An object to uniquely identify modular data produced by a modular step.
@@ -63,9 +66,13 @@
}
abstract class Pipeline<S extends ModularStep> {
+ /// Whether to cache the result of shared modules (e.g. shard packages and sdk
+ /// libraries) when multiple tests are run by this pipeline.
+ final bool cacheSharedModules;
+
final List<S> steps;
- Pipeline(this.steps) {
+ Pipeline(this.steps, this.cacheSharedModules) {
_validate();
}
diff --git a/pkg/modular_test/lib/src/suite.dart b/pkg/modular_test/lib/src/suite.dart
index 506349b..88aca4c 100644
--- a/pkg/modular_test/lib/src/suite.dart
+++ b/pkg/modular_test/lib/src/suite.dart
@@ -13,8 +13,23 @@
/// The module containing the main entry method.
final Module mainModule;
- ModularTest(this.modules, this.mainModule)
- : assert(mainModule != null && modules.length > 0);
+ /// Flags provided to tools that compile and execute the test.
+ final List<String> flags;
+
+ ModularTest(this.modules, this.mainModule, this.flags) {
+ if (mainModule == null) {
+ throw ArgumentError("main module was null");
+ }
+ if (flags == null) {
+ throw ArgumentError("flags was null");
+ }
+ if (modules == null || modules.length == 0) {
+ throw ArgumentError("modules cannot be null or empty");
+ }
+ for (var module in modules) {
+ module._validateDependencies();
+ }
+ }
String debugString() => modules.map((m) => m.debugString()).join('\n');
}
@@ -50,16 +65,36 @@
/// Whether this is the main entry module of a test.
bool isMain;
+ /// Whether this module is test specific or shared across tests. Usually this
+ /// will be true only for the SDK and shared packages like `package:expect`.
+ bool isShared;
+
Module(this.name, this.dependencies, this.rootUri, this.sources,
{this.mainSource,
this.isPackage: false,
this.isMain: false,
- this.packageBase}) {
+ this.packageBase,
+ this.isShared: false}) {
if (!_validModuleName.hasMatch(name)) {
throw ArgumentError("invalid module name: $name");
}
}
+ void _validateDependencies() {
+ if (!isPackage && !isShared) return;
+ for (var dependency in dependencies) {
+ if (isPackage && !dependency.isPackage) {
+ throw InvalidModularTestError("invalid dependency: $name is a package "
+ "but it depends on ${dependency.name}, which is not.");
+ }
+ if (isShared && !dependency.isShared) {
+ throw InvalidModularTestError(
+ "invalid dependency: $name is a shared module "
+ "but it depends on ${dependency.name}, which is not.");
+ }
+ }
+ }
+
@override
String toString() => '[module $name]';
@@ -87,3 +122,28 @@
module.dependencies.forEach(helper);
return deps;
}
+
+/// A registry that can map a test configuration to a simple id.
+///
+/// This is used to help determine whether two tests are run with the same set
+/// of flags (the same configuration), and thus pipelines could reuse the
+/// results of shared modules from the first test when running the second test.
+class ConfigurationRegistry {
+ Map<String, int> _configurationId = {};
+
+ /// Compute an id to identify the configuration of a modular test.
+ ///
+ /// A configuration is defined in terms of the set of flags provided to a
+ /// test. If two test provided to this registry share the same set of flags,
+ /// the resulting ids are the same. Similarly, if the flags are different,
+ /// their ids will be different as well.
+ int computeConfigurationId(ModularTest test) {
+ return _configurationId[test.flags.join(' ')] ??= _configurationId.length;
+ }
+}
+
+class InvalidModularTestError extends Error {
+ final String message;
+ InvalidModularTestError(this.message);
+ String toString() => "Invalid modular test: $message";
+}
diff --git a/pkg/modular_test/lib/src/test_specification_parser.dart b/pkg/modular_test/lib/src/test_specification_parser.dart
new file mode 100644
index 0000000..92f81ec
--- /dev/null
+++ b/pkg/modular_test/lib/src/test_specification_parser.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2019, 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.
+
+/// This library defines how to read a test specification from a Yaml
+/// file. We expect specifications written in this format:
+///
+/// dependencies:
+/// b: a
+/// main: [b, expect]
+/// flags:
+/// - "--enable-experiment=constant-update-2018"
+///
+/// Where the dependencies section describe how modules depend on one another,
+/// and the flags section show what flags are needed to run that specific test.
+///
+/// When defining dependencies:
+/// - Each name corresponds to a module.
+/// - Module names correlate to either a file, a folder, or a package.
+/// - A map entry contains all the dependencies of a module, if any.
+/// - If a module has a single dependency, it can be written as a single
+/// value.
+///
+/// The logic in this library mostly treats these names as strings, separately
+/// `loader.dart` is responsible for validating and attaching this dependency
+/// information to a set of module definitions.
+import 'package:yaml/yaml.dart';
+
+/// Parses [contents] containing a module dependencies specification written in
+/// yaml, and returns a [TestSpecification].
+TestSpecification parseTestSpecification(String contents) {
+ var spec = loadYaml(contents);
+ if (spec is! YamlMap) {
+ return _invalidSpecification("spec is not a map");
+ }
+ var dependencies = spec['dependencies'];
+ if (dependencies == null) {
+ return _invalidSpecification("no dependencies section");
+ }
+ if (dependencies is! YamlMap) {
+ return _invalidSpecification("dependencies is not a map");
+ }
+
+ Map<String, List<String>> normalizedMap = {};
+ dependencies.forEach((key, value) {
+ if (key is! String) {
+ _invalidSpecification("key: '$key' is not a string");
+ }
+ normalizedMap[key] = [];
+ if (value is String) {
+ normalizedMap[key].add(value);
+ } else if (value is List) {
+ value.forEach((entry) {
+ if (entry is! String) {
+ _invalidSpecification("entry: '$entry' is not a string");
+ }
+ normalizedMap[key].add(entry);
+ });
+ } else {
+ _invalidSpecification(
+ "entry: '$value' is not a string or a list of strings");
+ }
+ });
+
+ List<String> normalizedFlags = [];
+ dynamic flags = spec['flags'];
+ if (flags is String) {
+ normalizedFlags.add(flags);
+ } else if (flags is List) {
+ normalizedFlags.addAll(flags.cast<String>());
+ } else if (flags != null) {
+ _invalidSpecification(
+ "flags: '$flags' expected to be string or list of strings");
+ }
+ return new TestSpecification(normalizedFlags, normalizedMap);
+}
+
+/// Data specifying details about a modular test including dependencies and
+/// flags that are necessary in order to properly run a test.
+///
+class TestSpecification {
+ /// Set of flags necessary to properly run a test.
+ ///
+ /// Usually this contains flags enabling language experiments.
+ final List<String> flags;
+
+ /// Dependencies of the modules that are expected to exist on the test.
+ ///
+ /// Note: some values in the map may not have a corresponding key. That may be
+ /// the case for modules that have no dependencies and modules that are not
+ /// specified explicitly because they are added automatically by the framework
+ /// (for instance, the module of `package:expect` or the sdk itself).
+ final Map<String, List<String>> dependencies;
+
+ TestSpecification(this.flags, this.dependencies);
+}
+
+_invalidSpecification(String message) {
+ throw new InvalidSpecificationError(message);
+}
+
+class InvalidSpecificationError extends Error {
+ final String message;
+ InvalidSpecificationError(this.message);
+ String toString() => "Invalid specification: $message";
+}
diff --git a/pkg/modular_test/test/io_pipeline_test.dart b/pkg/modular_test/test/io_pipeline_test.dart
index dd3d01f..0ae59ae 100644
--- a/pkg/modular_test/test/io_pipeline_test.dart
+++ b/pkg/modular_test/test/io_pipeline_test.dart
@@ -29,14 +29,17 @@
@override
Future<Pipeline<IOModularStep>> createPipeline(
- Map<Uri, String> sources, List<IOModularStep> steps) async {
+ Map<Uri, String> sources, List<IOModularStep> steps,
+ {bool cacheSharedModules: false}) async {
await Directory.fromUri(testRootUri).create();
for (var uri in sources.keys) {
var file = new File.fromUri(uri);
await file.create(recursive: true);
await file.writeAsStringSync(sources[uri]);
}
- return new IOPipeline(steps, saveFoldersForTesting: true);
+ return new IOPipeline(steps,
+ saveIntermediateResultsForTesting: true,
+ cacheSharedModules: cacheSharedModules);
}
@override
@@ -83,17 +86,15 @@
@override
String getResult(covariant IOPipeline pipeline, Module m, DataId dataId) {
- var folderUri = pipeline.tmpFoldersForTesting[dataId];
- var file = File.fromUri(folderUri.resolve("${m.name}.${dataId.name}"));
+ var folderUri = pipeline.resultFolderUriForTesting;
+ var file = File.fromUri(folderUri
+ .resolve(pipeline.configSpecificResultFileNameForTesting(m, dataId)));
return file.existsSync() ? file.readAsStringSync() : null;
}
@override
- Future<void> cleanup(Pipeline<IOModularStep> pipeline) async {
- var folders = (pipeline as IOPipeline).tmpFoldersForTesting.values;
- for (var folder in folders) {
- await Directory.fromUri(folder).delete(recursive: true);
- }
+ Future<void> cleanup(covariant IOPipeline pipeline) async {
+ pipeline.cleanup();
await Directory.fromUri(testRootUri).delete(recursive: true);
}
}
@@ -122,6 +123,9 @@
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(action(sources));
}
+
+ @override
+ void notifyCached(Module module) {}
}
class ModuleDataStep implements IOModularStep {
@@ -146,6 +150,9 @@
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(result);
}
+
+ @override
+ void notifyCached(Module module) {}
}
class TwoOutputStep implements IOModularStep {
@@ -176,6 +183,9 @@
await File.fromUri(root.resolveUri(toUri(module, result2Id)))
.writeAsString(result2);
}
+
+ @override
+ void notifyCached(Module module) {}
}
class LinkStep implements IOModularStep {
@@ -205,6 +215,9 @@
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(action(inputData, depsData));
}
+
+ @override
+ void notifyCached(Module module) {}
}
class MainOnlyStep implements IOModularStep {
@@ -234,6 +247,9 @@
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(action(inputData, depsData));
}
+
+ @override
+ void notifyCached(Module module) {}
}
Future<String> _readHelper(Module module, Uri root, DataId dataId,
diff --git a/pkg/modular_test/test/loader/dag/expectation.txt b/pkg/modular_test/test/loader/dag/expectation.txt
index 885c41f..188ee70 100644
--- a/pkg/modular_test/test/loader/dag/expectation.txt
+++ b/pkg/modular_test/test/loader/dag/expectation.txt
@@ -2,21 +2,25 @@
a
is package? no
+ is shared? no
dependencies: b, c
a.dart
b
is package? no
+ is shared? no
dependencies: d
b.dart
c
is package? no
+ is shared? no
(no dependencies)
c.dart
d
is package? no
+ is shared? no
(no dependencies)
d/d.dart
d/e.dart
@@ -24,5 +28,6 @@
main
**main module**
is package? no
+ is shared? no
dependencies: a, b
main.dart
diff --git a/pkg/modular_test/test/loader/dag_with_packages/.packages b/pkg/modular_test/test/loader/dag_with_packages/.packages
index 5f1a721..194aba9 100644
--- a/pkg/modular_test/test/loader/dag_with_packages/.packages
+++ b/pkg/modular_test/test/loader/dag_with_packages/.packages
@@ -1,2 +1,4 @@
a:a/
+b:b/
+d:d/
c:c/
diff --git a/pkg/modular_test/test/loader/dag_with_packages/expectation.txt b/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
index df35ba5..ddb9f1e 100644
--- a/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
+++ b/pkg/modular_test/test/loader/dag_with_packages/expectation.txt
@@ -2,21 +2,25 @@
a
is package? yes
+ is shared? no
dependencies: b, c
a.dart
b
- is package? no
+ is package? yes
+ is shared? no
dependencies: d
b.dart
c
is package? yes
+ is shared? no
(no dependencies)
c.dart
d
- is package? no
+ is package? yes
+ is shared? no
(no dependencies)
d/d.dart
d/e.dart
@@ -24,5 +28,6 @@
main
**main module**
is package? no
+ is shared? no
dependencies: a, b
main.dart
diff --git a/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt b/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
index cf449f3..29f45c1 100644
--- a/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
+++ b/pkg/modular_test/test/loader/default_package_dependency_error/expectation.txt
@@ -2,6 +2,7 @@
expect
is package? yes
+ is shared? yes
dependencies: meta
lib/expect.dart
lib/matchers_lite.dart
@@ -10,11 +11,13 @@
main
**main module**
is package? no
+ is shared? no
dependencies: expect
main.dart
meta
is package? yes
+ is shared? yes
(no dependencies)
lib/dart2js.dart
lib/meta.dart
diff --git a/pkg/modular_test/test/loader/loader_test.dart b/pkg/modular_test/test/loader/loader_test.dart
index 4f5f59c..6faf716 100644
--- a/pkg/modular_test/test/loader/loader_test.dart
+++ b/pkg/modular_test/test/loader/loader_test.dart
@@ -4,34 +4,27 @@
/// Tests that the logic to load, parse, and validate modular tests.
import 'dart:io';
-import 'package:async_helper/async_helper.dart';
-import 'package:expect/expect.dart';
+
+import 'package:test/test.dart';
import 'package:modular_test/src/loader.dart';
import 'package:modular_test/src/suite.dart';
import 'package:args/args.dart';
-main(List<String> args) {
+main(List<String> args) async {
var options = _Options.parse(args);
- asyncTest(() async {
- var baseUri = Platform.script.resolve('./');
- var baseDir = Directory.fromUri(baseUri);
- await for (var entry in baseDir.list(recursive: false)) {
- if (entry is Directory) {
- await _runTest(entry.uri, baseUri, options);
- }
+ var baseUri = Platform.script.resolve('./');
+ var baseDir = Directory.fromUri(baseUri);
+ await for (var entry in baseDir.list(recursive: false)) {
+ if (entry is Directory) {
+ var dirName = entry.uri.path.substring(baseDir.path.length);
+ test(dirName, () => _runTest(entry.uri, dirName, options),
+ skip: options.filter != null && !dirName.contains(options.filter));
}
- });
+ }
}
-Future<void> _runTest(Uri uri, Uri baseDir, _Options options) async {
- var dirName = uri.path.substring(baseDir.path.length);
- if (options.filter != null && !dirName.contains(options.filter)) {
- if (options.showSkipped) print("skipped: $dirName");
- return;
- }
-
- print("testing: $dirName");
+Future<void> _runTest(Uri uri, String dirName, _Options options) async {
String result;
String header =
"# This expectation file is generated by loader_test.dart\n\n";
@@ -44,7 +37,8 @@
var file = File.fromUri(uri.resolve('expectation.txt'));
if (!options.updateExpectations) {
- Expect.isTrue(await file.exists(), "expectation.txt file is missing");
+ expect(await file.exists(), isTrue,
+ reason: "expectation.txt file is missing");
var expectation = await file.readAsString();
if (expectation != result) {
print("expectation.txt doesn't match the result of the test. "
@@ -52,8 +46,7 @@
" ${Platform.executable} ${Platform.script} "
"--update --show-update --filter $dirName");
}
- Expect.equals(expectation, result);
- print(" expectation matches result.");
+ expect(expectation, result);
} else if (await file.exists() && (await file.readAsString() == result)) {
print(" expectation matches result and was up to date.");
} else {
@@ -76,10 +69,11 @@
}
buffer.write(module.name);
if (module.isMain) {
- Expect.equals(test.mainModule, module);
+ expect(test.mainModule, module);
buffer.write('\n **main module**');
}
buffer.write('\n is package? ${module.isPackage ? 'yes' : 'no'}');
+ buffer.write('\n is shared? ${module.isShared ? 'yes' : 'no'}');
if (module.dependencies.isEmpty) {
buffer.write('\n (no dependencies)');
} else {
@@ -103,7 +97,6 @@
class _Options {
bool updateExpectations = false;
bool showResultOnUpdate = false;
- bool showSkipped = false;
String filter = null;
static _Options parse(List<String> args) {
@@ -124,7 +117,6 @@
return _Options()
..updateExpectations = argResults['update']
..showResultOnUpdate = argResults['show-update']
- ..showSkipped = argResults['show-skipped']
..filter = argResults['filter'];
}
}
diff --git a/pkg/modular_test/test/loader/no_dependencies/expectation.txt b/pkg/modular_test/test/loader/no_dependencies/expectation.txt
index f19449b..21b8165 100644
--- a/pkg/modular_test/test/loader/no_dependencies/expectation.txt
+++ b/pkg/modular_test/test/loader/no_dependencies/expectation.txt
@@ -3,5 +3,6 @@
main
**main module**
is package? no
+ is shared? no
(no dependencies)
main.dart
diff --git a/pkg/modular_test/test/loader/tree/expectation.txt b/pkg/modular_test/test/loader/tree/expectation.txt
index f438e22..285b4d7 100644
--- a/pkg/modular_test/test/loader/tree/expectation.txt
+++ b/pkg/modular_test/test/loader/tree/expectation.txt
@@ -2,21 +2,25 @@
a
is package? no
+ is shared? no
dependencies: b, c
a.dart
b
is package? no
+ is shared? no
dependencies: d
b.dart
c
is package? no
+ is shared? no
(no dependencies)
c.dart
d
is package? no
+ is shared? no
(no dependencies)
d/d.dart
d/e.dart
@@ -24,5 +28,6 @@
main
**main module**
is package? no
+ is shared? no
dependencies: a
main.dart
diff --git a/pkg/modular_test/test/loader/valid_packages/expectation.txt b/pkg/modular_test/test/loader/valid_packages/expectation.txt
index dab4163..be86423 100644
--- a/pkg/modular_test/test/loader/valid_packages/expectation.txt
+++ b/pkg/modular_test/test/loader/valid_packages/expectation.txt
@@ -2,6 +2,7 @@
expect
is package? yes
+ is shared? yes
dependencies: meta
lib/expect.dart
lib/matchers_lite.dart
@@ -9,6 +10,7 @@
js
is package? yes
+ is shared? yes
(no dependencies)
lib/js.dart
lib/js_util.dart
@@ -17,11 +19,13 @@
main
**main module**
is package? no
+ is shared? no
dependencies: js, expect
main.dart
meta
is package? yes
+ is shared? yes
(no dependencies)
lib/dart2js.dart
lib/meta.dart
diff --git a/pkg/modular_test/test/memory_pipeline_test.dart b/pkg/modular_test/test/memory_pipeline_test.dart
index ba406e1..124bda7 100644
--- a/pkg/modular_test/test/memory_pipeline_test.dart
+++ b/pkg/modular_test/test/memory_pipeline_test.dart
@@ -22,8 +22,10 @@
@override
FutureOr<Pipeline<MemoryModularStep>> createPipeline(
- Map<Uri, String> sources, List<MemoryModularStep> steps) {
- return new MemoryPipeline(sources, steps);
+ Map<Uri, String> sources, List<MemoryModularStep> steps,
+ {bool cacheSharedModules: false}) {
+ return new MemoryPipeline(sources, steps,
+ cacheSharedModules: cacheSharedModules);
}
@override
@@ -95,6 +97,9 @@
}
return Future.value({resultId: action(sources)});
}
+
+ @override
+ void notifyCached(Module module) {}
}
class ModuleDataStep implements MemoryModularStep {
@@ -117,6 +122,9 @@
return Future.value({resultId: "data for $module was null"});
return Future.value({resultId: action(inputData)});
}
+
+ @override
+ void notifyCached(Module module) {}
}
class TwoOutputStep implements MemoryModularStep {
@@ -145,6 +153,9 @@
return Future.value(
{result1Id: action1(inputData), result2Id: action2(inputData)});
}
+
+ @override
+ void notifyCached(Module module) {}
}
class LinkStep implements MemoryModularStep {
@@ -170,6 +181,9 @@
var inputData = dataProvider(module, inputId) as String;
return Future.value({resultId: action(inputData, depsData)});
}
+
+ @override
+ void notifyCached(Module module) {}
}
class MainOnlyStep implements MemoryModularStep {
@@ -195,4 +209,7 @@
var inputData = dataProvider(module, inputId) as String;
return Future.value({resultId: action(inputData, depsData)});
}
+
+ @override
+ void notifyCached(Module module) {}
}
diff --git a/pkg/modular_test/test/pipeline_common.dart b/pkg/modular_test/test/pipeline_common.dart
index b09e611..7bfbf77 100644
--- a/pkg/modular_test/test/pipeline_common.dart
+++ b/pkg/modular_test/test/pipeline_common.dart
@@ -28,7 +28,8 @@
/// Creates a pipeline with the given sources and steps. Steps will be created
/// by other methods in this strategy to ensure they are compatible with to
/// the pipeline created here.
- FutureOr<Pipeline<S>> createPipeline(Map<Uri, String> sources, List<S> steps);
+ FutureOr<Pipeline<S>> createPipeline(Map<Uri, String> sources, List<S> steps,
+ {bool cacheSharedModules: false});
/// Create a step that applies [action] on all input files of the module, and
/// emits a result with the given [id]
@@ -98,15 +99,16 @@
};
var m1 = Module("a", const [], testStrategy.testRootUri,
- [Uri.parse("a1.dart"), Uri.parse("a2.dart")]);
+ [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+ isShared: true);
var m2 = Module("b", [m1], testStrategy.testRootUri,
[Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")]);
var m3 = Module("c", [m2], testStrategy.testRootUri, [Uri.parse("c.dart")],
isMain: true);
- var singleModuleInput = ModularTest([m1], m1);
- var twoModuleInput = ModularTest([m1, m2], m2);
- var threeModuleInput = ModularTest([m1, m2, m3], m3);
+ var singleModuleInput = ModularTest([m1], m1, []);
+ var twoModuleInput = ModularTest([m1, m2], m2, []);
+ var threeModuleInput = ModularTest([m1, m2, m3], m3, []);
test('can read source data if requested', () async {
var concatStep =
@@ -310,6 +312,106 @@
expect(testStrategy.getResult(pipeline, m3, _joinId), "null\nnull\nc c0\n");
await testStrategy.cleanup(pipeline);
});
+
+ test('no reuse of existing results if not caching', () async {
+ int i = 1;
+ const counterId = const DataId("counter");
+ const linkId = const DataId("link");
+ // This step is not idempotent, we do this purposely to test whether caching
+ // is taking place.
+ var counterStep = testStrategy.createSourceOnlyStep(
+ action: (_) => '${i++}', resultId: counterId);
+ var linkStep = testStrategy.createLinkStep(
+ action: (String m, List<String> deps) => "${deps.join(',')},$m",
+ inputId: counterId,
+ depId: counterId,
+ resultId: linkId,
+ requestDependenciesData: true);
+ var pipeline = await testStrategy.createPipeline(
+ sources, <S>[counterStep, linkStep],
+ cacheSharedModules: false);
+ await pipeline.run(twoModuleInput);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "1");
+ expect(testStrategy.getResult(pipeline, m2, counterId), "2");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "1,2");
+
+ await pipeline.run(threeModuleInput);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "3");
+ expect(testStrategy.getResult(pipeline, m2, counterId), "4");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "3,4");
+ expect(testStrategy.getResult(pipeline, m3, counterId), "5");
+ expect(testStrategy.getResult(pipeline, m3, linkId), "4,5");
+
+ await testStrategy.cleanup(pipeline);
+ });
+
+ test('caching reuses existing results for the same configuration', () async {
+ int i = 1;
+ const counterId = const DataId("counter");
+ const linkId = const DataId("link");
+ var counterStep = testStrategy.createSourceOnlyStep(
+ action: (_) => '${i++}', resultId: counterId);
+ var linkStep = testStrategy.createLinkStep(
+ action: (String m, List<String> deps) => "${deps.join(',')},$m",
+ inputId: counterId,
+ depId: counterId,
+ resultId: linkId,
+ requestDependenciesData: true);
+ var pipeline = await testStrategy.createPipeline(
+ sources, <S>[counterStep, linkStep],
+ cacheSharedModules: true);
+ await pipeline.run(twoModuleInput);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "1");
+ expect(testStrategy.getResult(pipeline, m2, counterId), "2");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "1,2");
+
+ await pipeline.run(threeModuleInput);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "1"); // cached!
+ expect(testStrategy.getResult(pipeline, m2, counterId), "3");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "1,3");
+ expect(testStrategy.getResult(pipeline, m3, counterId), "4");
+ expect(testStrategy.getResult(pipeline, m3, linkId), "3,4");
+
+ await testStrategy.cleanup(pipeline);
+ });
+
+ test('no reuse of existing results on different configurations', () async {
+ int i = 1;
+ const counterId = const DataId("counter");
+ const linkId = const DataId("link");
+ // This step is not idempotent, we do this purposely to test whether caching
+ // is taking place.
+ var counterStep = testStrategy.createSourceOnlyStep(
+ action: (_) => '${i++}', resultId: counterId);
+ var linkStep = testStrategy.createLinkStep(
+ action: (String m, List<String> deps) => "${deps.join(',')},$m",
+ inputId: counterId,
+ depId: counterId,
+ resultId: linkId,
+ requestDependenciesData: true);
+ var pipeline = await testStrategy.createPipeline(
+ sources, <S>[counterStep, linkStep],
+ cacheSharedModules: true);
+ var input1 = ModularTest([m1, m2], m2, []);
+ var input2 = ModularTest([m1, m2], m2, ['--foo']);
+ var input3 = ModularTest([m1, m2], m2, ['--foo']);
+ await pipeline.run(input1);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "1");
+ expect(testStrategy.getResult(pipeline, m2, counterId), "2");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "1,2");
+
+ await pipeline.run(input2);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "3"); // no cache!
+ expect(testStrategy.getResult(pipeline, m2, counterId), "4");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "3,4");
+
+ await pipeline.run(input3);
+ expect(testStrategy.getResult(pipeline, m1, counterId), "3"); // same config
+ expect(testStrategy.getResult(pipeline, m2, counterId), "5");
+ expect(testStrategy.getResult(pipeline, m2, linkId), "3,5");
+
+ await testStrategy.cleanup(pipeline);
+ });
}
DataId _concatId = const DataId("concat");
diff --git a/pkg/modular_test/test/dependency_parser_test.dart b/pkg/modular_test/test/specification_parser_test.dart
similarity index 63%
rename from pkg/modular_test/test/dependency_parser_test.dart
rename to pkg/modular_test/test/specification_parser_test.dart
index f536282..6940d5e9 100644
--- a/pkg/modular_test/test/dependency_parser_test.dart
+++ b/pkg/modular_test/test/specification_parser_test.dart
@@ -3,46 +3,46 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:test/test.dart';
-import 'package:modular_test/src/dependency_parser.dart';
+import 'package:modular_test/src/test_specification_parser.dart';
main() {
test('require dependencies section', () {
- expect(() => parseDependencyMap(""),
+ expect(() => parseTestSpecification(""),
throwsA(TypeMatcher<InvalidSpecificationError>()));
});
test('dependencies is a map', () {
- expect(() => parseDependencyMap("dependencies: []"),
+ expect(() => parseTestSpecification("dependencies: []"),
throwsA(TypeMatcher<InvalidSpecificationError>()));
});
test('dependencies can be a string or list of strings', () {
- parseDependencyMap('''
+ parseTestSpecification('''
dependencies:
a: b
''');
- parseDependencyMap('''
+ parseTestSpecification('''
dependencies:
a: [b, c]
''');
- expect(() => parseDependencyMap('''
+ expect(() => parseTestSpecification('''
dependencies:
a: 1
'''), throwsA(TypeMatcher<InvalidSpecificationError>()));
- expect(() => parseDependencyMap('''
+ expect(() => parseTestSpecification('''
dependencies:
a: true
'''), throwsA(TypeMatcher<InvalidSpecificationError>()));
- expect(() => parseDependencyMap('''
+ expect(() => parseTestSpecification('''
dependencies:
a: [false]
'''), throwsA(TypeMatcher<InvalidSpecificationError>()));
- expect(() => parseDependencyMap('''
+ expect(() => parseTestSpecification('''
dependencies:
a:
c: d
@@ -51,14 +51,26 @@
test('result map is normalized', () {
expect(
- parseDependencyMap('''
+ parseTestSpecification('''
dependencies:
a: [b, c]
b: d
- '''),
+ ''').dependencies,
equals({
'a': ['b', 'c'],
'b': ['d'],
}));
});
+
+ test('flags are normalized', () {
+ expect(parseTestSpecification('''
+ dependencies: {}
+ flags: "a"
+ ''').flags, equals(["a"]));
+
+ expect(parseTestSpecification('''
+ dependencies: {}
+ flags: ["a"]
+ ''').flags, equals(["a"]));
+ });
}
diff --git a/pkg/modular_test/test/validate_test.dart b/pkg/modular_test/test/validate_pipeline_test.dart
similarity index 95%
rename from pkg/modular_test/test/validate_test.dart
rename to pkg/modular_test/test/validate_pipeline_test.dart
index 8394dfe..7666333 100644
--- a/pkg/modular_test/test/validate_test.dart
+++ b/pkg/modular_test/test/validate_pipeline_test.dart
@@ -27,7 +27,7 @@
]);
});
- test('circular dependency is not allowed', () {
+ test('circular step dependency is not allowed', () {
var id1 = DataId("data_a");
expect(
() => validateSteps([
@@ -78,7 +78,7 @@
/// An implementation of [Pipeline] that simply validates the steps, but doesn't
/// do anything else.
class _NoopPipeline extends Pipeline {
- _NoopPipeline(List<ModularStep> steps) : super(steps);
+ _NoopPipeline(List<ModularStep> steps) : super(steps, false);
@override
Future<void> runStep(ModularStep step, Module module,
diff --git a/pkg/modular_test/test/validate_suite_test.dart b/pkg/modular_test/test/validate_suite_test.dart
new file mode 100644
index 0000000..e0fad1e
--- /dev/null
+++ b/pkg/modular_test/test/validate_suite_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2019, 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.
+
+/// Unit test for validation of modular steps in a pipeline.
+import 'package:test/test.dart';
+import 'package:modular_test/src/suite.dart';
+
+main() {
+ test('module test is not empty', () {
+ expect(
+ () => ModularTest([], null, []), throwsA(TypeMatcher<ArgumentError>()));
+
+ var m = Module("a", [], Uri.parse("app:/"), []);
+ expect(() => ModularTest([], m, []), throwsA(TypeMatcher<ArgumentError>()));
+ });
+
+ test('module test must have a main module', () {
+ var m = Module("a", [], Uri.parse("app:/"), []);
+ expect(() => ModularTest([m], null, []),
+ throwsA(TypeMatcher<ArgumentError>()));
+ });
+
+ test('package must depend on package', () {
+ var m1a = Module("a", const [], Uri.parse("app:/"),
+ [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+ isPackage: false);
+ var m1b = Module("a", const [], Uri.parse("app:/"),
+ [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+ isPackage: true);
+
+ var m2a = Module("b", [m1a], Uri.parse("app:/"),
+ [Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")],
+ isPackage: true);
+ var m2b = Module("b", [m1b], Uri.parse("app:/"),
+ [Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")],
+ isPackage: true);
+ expect(() => ModularTest([m1a, m2a], m2a, []),
+ throwsA(TypeMatcher<InvalidModularTestError>()));
+ expect(ModularTest([m1b, m2b], m2b, []), isNotNull);
+ });
+
+ test('shared module must depend on shared modules', () {
+ var m1a = Module("a", const [], Uri.parse("app:/"),
+ [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+ isShared: false);
+ var m1b = Module("a", const [], Uri.parse("app:/"),
+ [Uri.parse("a1.dart"), Uri.parse("a2.dart")],
+ isShared: true);
+
+ var m2a = Module("b", [m1a], Uri.parse("app:/"),
+ [Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")],
+ isShared: true);
+ var m2b = Module("b", [m1b], Uri.parse("app:/"),
+ [Uri.parse("b/b1.dart"), Uri.parse("b/b2.dart")],
+ isShared: true);
+ expect(() => ModularTest([m1a, m2a], m2a, []),
+ throwsA(TypeMatcher<InvalidModularTestError>()));
+ expect(ModularTest([m1b, m2b], m2b, []), isNotNull);
+ });
+}
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 881f093..607d1b1 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -178,10 +178,10 @@
[ $compiler != dart2analyzer && $runtime != vm ]
dev_compiler/test/*: Skip
-modular_test/test/dependency_parser_test: SkipByDesign
modular_test/test/find_sdk_root1_test: SkipByDesign
modular_test/test/io_pipeline_test: SkipByDesign
modular_test/test/loader/loader_test: SkipByDesign
+modular_test/test/specification_parser_test: SkipByDesign
modular_test/test/src/find_sdk_root2_test: SkipByDesign
[ $compiler == dart2js && $runtime == chrome && $system == macos ]
diff --git a/tests/compiler/dart2js/modular/modular_test.dart b/tests/compiler/dart2js/modular/modular_test.dart
index 80aa857..2e37025 100644
--- a/tests/compiler/dart2js/modular/modular_test.dart
+++ b/tests/compiler/dart2js/modular/modular_test.dart
@@ -23,15 +23,23 @@
asyncTest(() async {
var baseUri = Platform.script.resolve('data/');
var baseDir = Directory.fromUri(baseUri);
+ var pipeline = new IOPipeline([
+ SourceToDillStep(),
+ GlobalAnalysisStep(),
+ Dart2jsBackendStep(),
+ RunD8(),
+ ], cacheSharedModules: true);
await for (var entry in baseDir.list(recursive: false)) {
if (entry is Directory) {
- await _runTest(entry.uri, baseUri);
+ await _runTest(pipeline, entry.uri, baseUri);
}
}
+
+ await pipeline.cleanup();
});
}
-Future<void> _runTest(Uri uri, Uri baseDir) async {
+Future<void> _runTest(IOPipeline pipeline, Uri uri, Uri baseDir) async {
var dirName = uri.path.substring(baseDir.path.length);
if (_options.filter != null && !dirName.contains(_options.filter)) {
if (_options.showSkipped) print("skipped: $dirName");
@@ -41,13 +49,6 @@
print("testing: $dirName");
ModularTest test = await loadTest(uri);
if (_options.verbose) print(test.debugString());
- var pipeline = new IOPipeline([
- SourceToDillStep(),
- GlobalAnalysisStep(),
- Dart2jsBackendStep(),
- RunD8(),
- ]);
-
await pipeline.run(test);
}
@@ -153,6 +154,11 @@
Platform.resolvedExecutable, workerArgs, root.toFilePath());
_checkExitCode(result, this, module);
}
+
+ @override
+ void notifyCached(Module module) {
+ if (_options.verbose) print("cached step: source-to-dill on $module");
+ }
}
// Step that invokes the dart2js global analysis on the main module by providing
@@ -194,6 +200,12 @@
_checkExitCode(result, this, module);
}
+
+ @override
+ void notifyCached(Module module) {
+ if (_options.verbose)
+ print("cached step: dart2js global analysis on $module");
+ }
}
// Step that invokes the dart2js backend on the main module given the results of
@@ -231,6 +243,11 @@
_checkExitCode(result, this, module);
}
+
+ @override
+ void notifyCached(Module module) {
+ if (_options.verbose) print("cached step: dart2js backend on $module");
+ }
}
/// Step that runs the output of dart2js in d8 and saves the output.
@@ -269,6 +286,11 @@
await File.fromUri(root.resolveUri(toUri(module, txtId)))
.writeAsString(result.stdout);
}
+
+ @override
+ void notifyCached(Module module) {
+ if (_options.verbose) print("cached step: d8 on $module");
+ }
}
void _checkExitCode(ProcessResult result, IOModularStep step, Module module) {