Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit edadc9c

Browse files
sigmundchcommit-bot@chromium.org
authored andcommitted
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 <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent 8208e87 commit edadc9c

File tree

22 files changed

+578
-123
lines changed

22 files changed

+578
-123
lines changed

pkg/modular_test/lib/src/io_pipeline.dart

Lines changed: 98 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,49 +26,103 @@ abstract class IOModularStep extends ModularStep {
2626
}
2727

2828
class IOPipeline extends Pipeline<IOModularStep> {
29-
/// A folder per step. The key is the data id produced by a specific step.
29+
/// Folder that holds the results of each step during the run of the pipeline.
3030
///
31-
/// This contains internal state used during the run of the pipeline, but is
32-
/// expected to be null before and after the pipeline is executed.
33-
Map<DataId, Uri> _tmpFolders;
34-
Map<DataId, Uri> get tmpFoldersForTesting => _tmpFolders;
35-
bool saveFoldersForTesting;
31+
/// This value is usually null before and after the pipeline runs, but will be
32+
/// non-null in two cases:
33+
///
34+
/// * for testing purposes when using [saveIntermediateResultsForTesting].
35+
///
36+
/// * to share results across pipeline runs when using [cacheSharedModules].
37+
///
38+
/// When using [cacheSharedModules] the pipeline will only reuse data for
39+
/// modules that are known to be shared (e.g. shared packages and sdk
40+
/// libraries), and not modules that are test specific. File names will be
41+
/// specific enough so that we can keep separate the artifacts created from
42+
/// running tools under different configurations (with different flags).
43+
Uri _resultsFolderUri;
44+
Uri get resultFolderUriForTesting => _resultsFolderUri;
45+
46+
/// A unique number to denote the current modular test configuration.
47+
///
48+
/// When using [cacheSharedModules], a test can resuse the output of a
49+
/// previous run of this pipeline if that output was generated with the same
50+
/// configuration.
51+
int _currentConfiguration;
52+
53+
final ConfigurationRegistry _registry;
3654

37-
IOPipeline(List<IOModularStep> steps, {this.saveFoldersForTesting: false})
38-
: super(steps);
55+
/// Whether to keep alive the temporary folder used to store intermediate
56+
/// results in order to inspect it later in test.
57+
final bool saveIntermediateResultsForTesting;
58+
59+
IOPipeline(List<IOModularStep> steps,
60+
{this.saveIntermediateResultsForTesting: false,
61+
bool cacheSharedModules: false})
62+
: _registry = cacheSharedModules ? new ConfigurationRegistry() : null,
63+
super(steps, cacheSharedModules);
3964

4065
@override
4166
Future<void> run(ModularTest test) async {
42-
assert(_tmpFolders == null);
43-
_tmpFolders = {};
67+
var resultsDir = null;
68+
if (_resultsFolderUri == null) {
69+
resultsDir = await Directory.systemTemp.createTemp('modular_test_res-');
70+
_resultsFolderUri = resultsDir.uri;
71+
}
72+
if (cacheSharedModules) {
73+
_currentConfiguration = _registry.computeConfigurationId(test);
74+
}
4475
await super.run(test);
45-
if (!saveFoldersForTesting) {
46-
for (var folder in _tmpFolders.values) {
47-
await Directory.fromUri(folder).delete(recursive: true);
48-
}
49-
_tmpFolders = null;
76+
if (resultsDir != null &&
77+
!saveIntermediateResultsForTesting &&
78+
!cacheSharedModules) {
79+
await resultsDir.delete(recursive: true);
80+
_resultsFolderUri = null;
81+
}
82+
if (!saveIntermediateResultsForTesting) {
83+
_currentConfiguration = null;
84+
}
85+
}
86+
87+
/// Delete folders that were kept around either because of
88+
/// [saveIntermediateResultsForTesting] or because of [cacheSharedModules].
89+
Future<void> cleanup() async {
90+
if (saveIntermediateResultsForTesting || cacheSharedModules) {
91+
await Directory.fromUri(_resultsFolderUri).delete(recursive: true);
5092
}
5193
}
5294

5395
@override
5496
Future<void> runStep(IOModularStep step, Module module,
5597
Map<Module, Set<DataId>> visibleData) async {
56-
// Since data ids are unique throughout the pipeline, we use the first
57-
// result data id as a hint for the name of the temporary folder of a step.
58-
var stepFolder;
59-
for (var dataId in step.resultData) {
60-
stepFolder ??=
61-
await Directory.systemTemp.createTemp('modular_test_${dataId}-');
62-
_tmpFolders[dataId] ??=
63-
(await Directory.systemTemp.createTemp('modular_test_${dataId}_res-'))
64-
.uri;
98+
if (cacheSharedModules && module.isShared) {
99+
// If all expected outputs are already available, skip the step.
100+
bool allCachedResultsFound = true;
101+
for (var dataId in step.resultData) {
102+
var cachedFile = File.fromUri(_resultsFolderUri
103+
.resolve(_toFileName(module, dataId, configSpecific: true)));
104+
if (!await cachedFile.exists()) {
105+
allCachedResultsFound = false;
106+
break;
107+
}
108+
}
109+
if (allCachedResultsFound) {
110+
step.notifyCached(module);
111+
return;
112+
}
65113
}
114+
115+
// Each step is executed in a separate folder. To make it easier to debug
116+
// issues, we include one of the step data ids in the name of the folder.
117+
var stepId = step.resultData.first;
118+
var stepFolder =
119+
await Directory.systemTemp.createTemp('modular_test_${stepId}-');
66120
for (var module in visibleData.keys) {
67121
for (var dataId in visibleData[module]) {
68-
var filename = "${module.name}.${dataId.name}";
69-
var assetUri = _tmpFolders[dataId].resolve(filename);
70-
await File.fromUri(assetUri)
71-
.copy(stepFolder.uri.resolve(filename).toFilePath());
122+
var assetUri = _resultsFolderUri
123+
.resolve(_toFileName(module, dataId, configSpecific: true));
124+
await File.fromUri(assetUri).copy(
125+
stepFolder.uri.resolve(_toFileName(module, dataId)).toFilePath());
72126
}
73127
}
74128
if (step.needsSources) {
@@ -81,19 +135,31 @@ class IOPipeline extends Pipeline<IOModularStep> {
81135
}
82136

83137
await step.execute(module, stepFolder.uri,
84-
(Module m, DataId id) => Uri.parse("${m.name}.${id.name}"));
138+
(Module m, DataId id) => Uri.parse(_toFileName(m, id)));
85139

86140
for (var dataId in step.resultData) {
87141
var outputFile =
88-
File.fromUri(stepFolder.uri.resolve("${module.name}.${dataId.name}"));
142+
File.fromUri(stepFolder.uri.resolve(_toFileName(module, dataId)));
89143
if (!await outputFile.exists()) {
90144
throw StateError(
91145
"Step '${step.runtimeType}' didn't produce an output file");
92146
}
93-
await outputFile.copy(_tmpFolders[dataId]
94-
.resolve("${module.name}.${dataId.name}")
147+
await outputFile.copy(_resultsFolderUri
148+
.resolve(_toFileName(module, dataId, configSpecific: true))
95149
.toFilePath());
96150
}
97151
await stepFolder.delete(recursive: true);
98152
}
153+
154+
String _toFileName(Module module, DataId dataId,
155+
{bool configSpecific: false}) {
156+
var prefix =
157+
cacheSharedModules && configSpecific && _currentConfiguration != null
158+
? _currentConfiguration
159+
: '';
160+
return "$prefix${module.name}.${dataId.name}";
161+
}
162+
163+
String configSpecificResultFileNameForTesting(Module module, DataId dataId) =>
164+
_toFileName(module, dataId, configSpecific: true);
99165
}

pkg/modular_test/lib/src/loader.dart

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
/// [defaultPackagesInput]. The list of packages provided is expected to
1818
/// be disjoint with those in [defaultPackagesInput].
1919
/// * a modules.yaml file: a specification of dependencies between modules.
20-
/// The format is described in `dependencies_parser.dart`.
20+
/// The format is described in `test_specification_parser.dart`.
2121
import 'dart:io';
2222
import 'dart:convert';
2323
import 'suite.dart';
24-
import 'dependency_parser.dart';
24+
import 'test_specification_parser.dart';
2525
import 'find_sdk_root.dart';
2626

2727
import 'package:package_config/packages_file.dart' as package_config;
@@ -40,7 +40,7 @@ Future<ModularTest> loadTest(Uri uri) async {
4040
Map<String, Uri> defaultPackages =
4141
package_config.parse(_defaultPackagesInput, root);
4242
Map<String, Module> modules = {};
43-
String spec;
43+
String specString;
4444
Module mainModule;
4545
Map<String, Uri> packages = {};
4646
await for (var entry in folder.list(recursive: false)) {
@@ -69,7 +69,7 @@ Future<ModularTest> loadTest(Uri uri) async {
6969
List<int> packagesBytes = await entry.readAsBytes();
7070
packages = package_config.parse(packagesBytes, entryUri);
7171
} else if (fileName == 'modules.yaml') {
72-
spec = await entry.readAsString();
72+
specString = await entry.readAsString();
7373
}
7474
} else {
7575
assert(entry is Directory);
@@ -88,7 +88,7 @@ Future<ModularTest> loadTest(Uri uri) async {
8888
packageBase: Uri.parse('$moduleName/'));
8989
}
9090
}
91-
if (spec == null) {
91+
if (specString == null) {
9292
return _invalidTest("modules.yaml file is missing");
9393
}
9494
if (mainModule == null) {
@@ -97,12 +97,15 @@ Future<ModularTest> loadTest(Uri uri) async {
9797

9898
_addDefaultPackageEntries(packages, defaultPackages);
9999
await _addModulePerPackage(packages, modules);
100-
_attachDependencies(parseDependencyMap(spec), modules);
101-
_attachDependencies(parseDependencyMap(_defaultPackagesSpec), modules);
100+
TestSpecification spec = parseTestSpecification(specString);
101+
_attachDependencies(spec.dependencies, modules);
102+
_attachDependencies(
103+
parseTestSpecification(_defaultPackagesSpec).dependencies, modules);
102104
_detectCyclesAndRemoveUnreachable(modules, mainModule);
103105
var sortedModules = modules.values.toList()
104106
..sort((a, b) => a.name.compareTo(b.name));
105-
return new ModularTest(sortedModules, mainModule);
107+
var sortedFlags = spec.flags.toList()..sort();
108+
return new ModularTest(sortedModules, mainModule, sortedFlags);
106109
}
107110

108111
/// Returns all source files recursively found in a folder as relative URIs.
@@ -169,7 +172,7 @@ Future<void> _addModulePerPackage(
169172
// module that is part of the test (package name and module name should
170173
// match).
171174
modules[packageName] = Module(packageName, [], rootUri, sources,
172-
isPackage: true, packageBase: Uri.parse('lib/'));
175+
isPackage: true, packageBase: Uri.parse('lib/'), isShared: true);
173176
}
174177
}
175178
}

pkg/modular_test/lib/src/memory_pipeline.dart

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,60 @@ class MemoryPipeline extends Pipeline<MemoryModularStep> {
2727
/// A copy of [_result] at the time the pipeline last finished running.
2828
Map<Module, Map<DataId, Object>> resultsForTesting;
2929

30-
MemoryPipeline(this._sources, List<MemoryModularStep> steps) : super(steps);
30+
final ConfigurationRegistry _registry;
31+
32+
/// Cache of results when [cacheSharedModules] is true
33+
final List<Map<Module, Map<DataId, Object>>> _resultCache;
34+
35+
MemoryPipeline(this._sources, List<MemoryModularStep> steps,
36+
{bool cacheSharedModules: false})
37+
: _registry = cacheSharedModules ? new ConfigurationRegistry() : null,
38+
_resultCache = cacheSharedModules ? [] : null,
39+
super(steps, cacheSharedModules);
3140

3241
@override
3342
Future<void> run(ModularTest test) async {
34-
assert(_results == null);
3543
_results = {};
44+
Map<Module, Map<DataId, Object>> cache = null;
45+
if (cacheSharedModules) {
46+
int id = _registry.computeConfigurationId(test);
47+
if (id < _resultCache.length) {
48+
cache = _resultCache[id];
49+
} else {
50+
assert(id == _resultCache.length);
51+
_resultCache.add(cache = {});
52+
}
53+
_results.addAll(cache);
54+
}
3655
await super.run(test);
3756
resultsForTesting = _results;
57+
if (cacheSharedModules) {
58+
for (var module in _results.keys) {
59+
if (module.isShared) {
60+
cache[module] = _results[module];
61+
}
62+
}
63+
}
3864
_results = null;
3965
}
4066

4167
@override
4268
Future<void> runStep(MemoryModularStep step, Module module,
4369
Map<Module, Set<DataId>> visibleData) async {
70+
if (cacheSharedModules && module.isShared) {
71+
bool allCachedResultsFound = true;
72+
for (var dataId in step.resultData) {
73+
if (_results[module] == null || _results[module][dataId] == null) {
74+
allCachedResultsFound = false;
75+
break;
76+
}
77+
}
78+
if (allCachedResultsFound) {
79+
step.notifyCached(module);
80+
return;
81+
}
82+
}
83+
4484
Map<Module, Map<DataId, Object>> inputData = {};
4585
visibleData.forEach((module, dataIdSet) {
4686
inputData[module] = {};

pkg/modular_test/lib/src/pipeline.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class ModularStep {
4848
this.moduleDataNeeded: const [],
4949
this.resultData,
5050
this.onlyOnMain: false});
51+
52+
/// Notifies that the step was not executed, but cached instead.
53+
void notifyCached(Module module) {}
5154
}
5255

5356
/// An object to uniquely identify modular data produced by a modular step.
@@ -63,9 +66,13 @@ class DataId {
6366
}
6467

6568
abstract class Pipeline<S extends ModularStep> {
69+
/// Whether to cache the result of shared modules (e.g. shard packages and sdk
70+
/// libraries) when multiple tests are run by this pipeline.
71+
final bool cacheSharedModules;
72+
6673
final List<S> steps;
6774

68-
Pipeline(this.steps) {
75+
Pipeline(this.steps, this.cacheSharedModules) {
6976
_validate();
7077
}
7178

0 commit comments

Comments
 (0)