| // 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. |
| |
| /// An implementation of [Pipeline] that runs using IO. |
| /// |
| /// To define a step, implement [IOModularStep]. |
| import 'dart:io'; |
| |
| import 'pipeline.dart'; |
| import 'suite.dart'; |
| |
| /// Indicates where to read and write data produced by the pipeline. |
| typedef ModuleDataToRelativeUri = Uri Function(Module, DataId); |
| |
| abstract class IOModularStep extends ModularStep { |
| /// Execute the step under [root]. |
| /// |
| /// The [root] folder will hold all inputs and will be used to emit the output |
| /// of this step. |
| /// |
| /// Assets created on previous steps of the pipeline should be available under |
| /// `root.resolveUri(toUri(module, dataId))` and the output of this step |
| /// should be stored under `root.resolveUri(toUri(module, resultKind))`. |
| Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri, |
| List<String> flags); |
| } |
| |
| class IOPipeline extends Pipeline<IOModularStep> { |
| /// Folder that holds the results of each step during the run of the pipeline. |
| /// |
| /// 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; |
| |
| /// 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 { |
| Directory? resultsDir; |
| 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 (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 (_resultsFolderUri == null) return; |
| if (saveIntermediateResultsForTesting || cacheSharedModules) { |
| await Directory.fromUri(_resultsFolderUri!).delete(recursive: true); |
| _resultsFolderUri = null; |
| } |
| } |
| |
| @override |
| Future<void> runStep(IOModularStep step, Module module, |
| Map<Module, Set<DataId>> visibleData, List<String> flags) async { |
| final resultsFolderUri = _resultsFolderUri!; |
| 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 assetUri = resultsFolderUri |
| .resolve(_toFileName(module, dataId, configSpecific: true)); |
| await File.fromUri(assetUri).copy( |
| stepFolder.uri.resolve(_toFileName(module, dataId)).toFilePath()); |
| } |
| } |
| if (step.needsSources) { |
| for (var uri in module.sources) { |
| var originalUri = module.rootUri.resolveUri(uri); |
| var copyUri = stepFolder.uri.resolveUri(uri); |
| await File.fromUri(copyUri).create(recursive: true); |
| await File.fromUri(originalUri).copy(copyUri.toFilePath()); |
| } |
| } |
| |
| await step.execute(module, stepFolder.uri, |
| (Module m, DataId id) => Uri.parse(_toFileName(m, id)), flags); |
| |
| for (var dataId in step.resultData) { |
| var outputFile = |
| 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(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); |
| } |