[modular_test]: migrate modular_test to null safety

Change-Id: I516d05469dd2c2bf57e4ec079fc1007ccf4b4783
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/240653
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Sigmund Cherem 2022-04-22 03:13:19 +00:00 committed by Commit Bot
parent 73935c1d83
commit 02cf9ea69c
17 changed files with 197 additions and 200 deletions

View file

@ -60,7 +60,7 @@ Future<void> writePackageConfig(
}
String _packageConfigEntry(String name, Uri root,
{Uri packageRoot, LanguageVersion version}) {
{Uri? packageRoot, LanguageVersion? version}) {
var fields = [
'"name": "${name}"',
'"rootUri": "$root"',

View file

@ -15,9 +15,8 @@ Future<Uri> findRoot() async {
var segments = current.pathSegments;
var index = segments.lastIndexOf('sdk');
if (index == -1) {
print("error: cannot find the root of the Dart SDK");
exitCode = 1;
return null;
throw "error: cannot find the root of the Dart SDK";
}
current = current.resolve("../" * (segments.length - index - 1));
if (await File.fromUri(current.resolve("sdk/DEPS")).exists()) {

View file

@ -24,29 +24,29 @@ abstract class Test {
class RunnerOptions {
/// Name of the test suite being run.
String suiteName;
final String suiteName;
/// Configuration name to use when writing result logs.
String configurationName;
final String? configurationName;
/// Filter used to only run tests that match the filter name.
String filter;
final String? filter;
/// Where log files are emitted.
///
/// Note that all shards currently emit the same filenames, so two shards
/// shouldn't be given the same [logDir] otherwise they will overwrite each
/// other's log files.
Uri logDir;
final Uri? logDir;
/// Of [shards], which shard is currently being executed.
int shard;
final int shard;
/// How many shards will be used to run a suite.
int shards;
final int shards;
/// Whether to print verbose information.
bool verbose;
final bool verbose;
/// Template used to help developers reproduce the issue.
///
@ -54,23 +54,34 @@ class RunnerOptions {
/// * %executable is replaced with `Platform.executable`
/// * %script is replaced with the current `Platform.script`
/// * %name is replaced with the test name.
String reproTemplate;
final String reproTemplate;
RunnerOptions(
{required this.suiteName,
this.configurationName,
this.filter,
this.logDir,
required this.shard,
required this.shards,
required this.verbose,
required this.reproTemplate});
}
class _TestOutcome {
/// Unique test name.
String name;
final String name;
/// Whether, after running the test, the test matches its expectations. Null
/// before the test is executed.
bool matchedExpectations;
/// Whether, after running the test, the test matches its expectations.
late bool matchedExpectations;
/// Additional output emitted by the test, only used when expectations don't
/// match and more details need to be provided.
String output;
String? output;
/// Time used to run the test.
Duration elapsedTime;
late Duration elapsedTime;
_TestOutcome(this.name);
}
Future<void> runSuite<T>(List<Test> tests, RunnerOptions options) async {
@ -91,13 +102,13 @@ Future<void> runSuite<T>(List<Test> tests, RunnerOptions options) async {
var test = sortedTests[i];
var name = test.name;
if (options.verbose) stdout.write('$name: ');
if (options.filter != null && !name.contains(options.filter)) {
if (options.filter != null && !name.contains(options.filter!)) {
if (options.verbose) stdout.write('skipped\n');
continue;
}
var watch = new Stopwatch()..start();
var outcome = new _TestOutcome()..name = test.name;
var outcome = new _TestOutcome(test.name);
try {
await test.run();
if (options.verbose) stdout.write('pass\n');
@ -153,7 +164,7 @@ Future<void> runSuite<T>(List<Test> tests, RunnerOptions options) async {
}
// Ensure the directory URI ends with a path separator.
var logDir = Directory.fromUri(options.logDir).uri;
var logDir = Directory.fromUri(options.logDir!).uri;
var resultJsonUri = logDir.resolve('results.json');
var logsJsonUri = logDir.resolve('logs.json');
File.fromUri(resultJsonUri)

View file

@ -41,17 +41,17 @@ class IOPipeline extends Pipeline<IOModularStep> {
/// 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;
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;
int? _currentConfiguration;
final ConfigurationRegistry _registry;
final ConfigurationRegistry? _registry;
/// Whether to keep alive the temporary folder used to store intermediate
/// results in order to inspect it later in test.
@ -65,13 +65,13 @@ class IOPipeline extends Pipeline<IOModularStep> {
@override
Future<void> run(ModularTest test) async {
var resultsDir = null;
Directory? resultsDir;
if (_resultsFolderUri == null) {
resultsDir = await Directory.systemTemp.createTemp('modular_test_res-');
_resultsFolderUri = resultsDir.uri;
}
if (cacheSharedModules) {
_currentConfiguration = _registry.computeConfigurationId(test);
_currentConfiguration = _registry!.computeConfigurationId(test);
}
await super.run(test);
if (resultsDir != null &&
@ -90,7 +90,7 @@ class IOPipeline extends Pipeline<IOModularStep> {
Future<void> cleanup() async {
if (_resultsFolderUri == null) return;
if (saveIntermediateResultsForTesting || cacheSharedModules) {
await Directory.fromUri(_resultsFolderUri).delete(recursive: true);
await Directory.fromUri(_resultsFolderUri!).delete(recursive: true);
_resultsFolderUri = null;
}
}
@ -98,11 +98,12 @@ class IOPipeline extends Pipeline<IOModularStep> {
@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
var cachedFile = File.fromUri(resultsFolderUri
.resolve(_toFileName(module, dataId, configSpecific: true)));
if (!await cachedFile.exists()) {
allCachedResultsFound = false;
@ -121,8 +122,8 @@ class IOPipeline extends Pipeline<IOModularStep> {
var stepFolder =
await Directory.systemTemp.createTemp('modular_test_${stepId}-');
for (var module in visibleData.keys) {
for (var dataId in visibleData[module]) {
var assetUri = _resultsFolderUri
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());
@ -147,7 +148,7 @@ class IOPipeline extends Pipeline<IOModularStep> {
throw StateError(
"Step '${step.runtimeType}' didn't produce an output file");
}
await outputFile.copy(_resultsFolderUri
await outputFile.copy(resultsFolderUri
.resolve(_toFileName(module, dataId, configSpecific: true))
.toFilePath());
}

View file

@ -32,8 +32,8 @@ Future<ModularTest> loadTest(Uri uri) async {
Set<String> defaultPackages = defaultTestSpecification.packages.keys.toSet();
Module sdkModule = await _createSdkModule(root);
Map<String, Module> modules = {'sdk': sdkModule};
String specString;
Module mainModule;
String? specString;
Module? mainModule;
var entries = folder.listSync(recursive: false).toList()
// Sort to avoid dependency on file system order.
..sort(_compareFileSystemEntity);
@ -54,7 +54,7 @@ Future<ModularTest> loadTest(Uri uri) async {
"that is provided by default.");
}
if (modules.containsKey(moduleName)) {
return _moduleConflict(fileName, modules[moduleName], testUri);
return _moduleConflict(fileName, modules[moduleName]!, testUri);
}
var relativeUri = Uri.parse(fileName);
var isMain = moduleName == 'main';
@ -82,7 +82,7 @@ Future<ModularTest> loadTest(Uri uri) async {
"that is provided by default.");
}
if (modules.containsKey(moduleName)) {
return _moduleConflict(moduleName, modules[moduleName], testUri);
return _moduleConflict(moduleName, modules[moduleName]!, testUri);
}
var sources = await _listModuleSources(entryUri);
modules[moduleName] = Module(moduleName, [], testUri, sources,
@ -133,7 +133,7 @@ Future<List<Uri>> _listModuleSources(Uri root) async {
void _attachDependencies(
Map<String, List<String>> dependencies, Map<String, Module> modules) {
dependencies.forEach((name, moduleDependencies) {
var module = modules[name];
final module = modules[name];
if (module == null) {
_invalidTest(
"declared dependencies for a non existing module named '$name'");
@ -142,7 +142,7 @@ void _attachDependencies(
_invalidTest("Module dependencies have already been declared on $name.");
}
moduleDependencies.forEach((dependencyName) {
var moduleDependency = modules[dependencyName];
final moduleDependency = modules[dependencyName];
if (moduleDependency == null) {
_invalidTest("'$name' declares a dependency on a non existing module "
"named '$dependencyName'");
@ -169,7 +169,7 @@ Future<void> _addModulePerPackage(Map<String, String> packages, Uri configRoot,
if (module != null) {
module.isPackage = true;
} else {
var packageLibUri = configRoot.resolve(packages[packageName]);
var packageLibUri = configRoot.resolve(packages[packageName]!);
var packageRootUri = Directory.fromUri(packageLibUri).parent.uri;
var sources = await _listModuleSources(packageLibUri);
// TODO(sigmund): validate that we don't use a different alias for a
@ -254,7 +254,7 @@ _moduleConflict(String name, Module existing, Uri root) {
var entryType = isFile ? 'file' : 'folder';
var existingIsFile =
existing.packageBase.path == './' && existing.sources.length == 1;
existing.packageBase!.path == './' && existing.sources.length == 1;
var existingEntryType = existingIsFile ? 'file' : 'folder';
var existingName = existingIsFile
@ -266,7 +266,7 @@ _moduleConflict(String name, Module existing, Uri root) {
"'$existingName'.");
}
_invalidTest(String message) {
Never _invalidTest(String message) {
throw new InvalidTestError(message);
}

View file

@ -9,8 +9,8 @@ import 'pipeline.dart';
import 'suite.dart';
/// A hook to fetch data previously computed for a dependency.
typedef ModuleDataProvider = Object Function(Module, DataId);
typedef SourceProvider = String Function(Uri);
typedef ModuleDataProvider = Object? Function(Module, DataId);
typedef SourceProvider = String? Function(Uri);
abstract class MemoryModularStep extends ModularStep {
Future<Map<DataId, Object>> execute(
@ -25,12 +25,12 @@ class MemoryPipeline extends Pipeline<MemoryModularStep> {
/// Internal state to hold the current results as they are computed by the
/// pipeline. Expected to be null before and after the pipeline runs.
Map<Module, Map<DataId, Object>> _results;
Map<Module, Map<DataId, Object>>? _results;
/// A copy of [_result] at the time the pipeline last finished running.
Map<Module, Map<DataId, Object>> resultsForTesting;
Map<Module, Map<DataId, Object>>? resultsForTesting;
final ConfigurationRegistry _registry;
final ConfigurationRegistry? _registry;
/// Cache of results when [cacheSharedModules] is true
final List<Map<Module, Map<DataId, Object>>> _resultCache;
@ -38,29 +38,29 @@ class MemoryPipeline extends Pipeline<MemoryModularStep> {
MemoryPipeline(this._sources, List<MemoryModularStep> steps,
{bool cacheSharedModules: false})
: _registry = cacheSharedModules ? new ConfigurationRegistry() : null,
_resultCache = cacheSharedModules ? [] : null,
_resultCache = cacheSharedModules ? [] : const [],
super(steps, cacheSharedModules);
@override
Future<void> run(ModularTest test) async {
_results = {};
Map<Module, Map<DataId, Object>> cache = null;
var results = _results = {};
Map<Module, Map<DataId, Object>>? cache = null;
if (cacheSharedModules) {
int id = _registry.computeConfigurationId(test);
int id = _registry!.computeConfigurationId(test);
if (id < _resultCache.length) {
cache = _resultCache[id];
} else {
assert(id == _resultCache.length);
_resultCache.add(cache = {});
}
_results.addAll(cache);
results.addAll(cache);
}
await super.run(test);
resultsForTesting = _results;
resultsForTesting = results;
if (cacheSharedModules) {
for (var module in _results.keys) {
for (var module in results.keys) {
if (module.isShared) {
cache[module] = _results[module];
cache![module] = results[module]!;
}
}
}
@ -70,10 +70,11 @@ class MemoryPipeline extends Pipeline<MemoryModularStep> {
@override
Future<void> runStep(MemoryModularStep step, Module module,
Map<Module, Set<DataId>> visibleData, List<String> flags) async {
final results = _results!;
if (cacheSharedModules && module.isShared) {
bool allCachedResultsFound = true;
for (var dataId in step.resultData) {
if (_results[module] == null || _results[module][dataId] == null) {
if (results[module] == null || results[module]![dataId] == null) {
allCachedResultsFound = false;
break;
}
@ -88,23 +89,23 @@ class MemoryPipeline extends Pipeline<MemoryModularStep> {
visibleData.forEach((module, dataIdSet) {
inputData[module] = {};
for (var dataId in dataIdSet) {
inputData[module][dataId] = _results[module][dataId];
inputData[module]![dataId] = results[module]![dataId]!;
}
});
Map<Uri, String> inputSources = {};
if (step.needsSources) {
module.sources.forEach((relativeUri) {
var uri = module.rootUri.resolveUri(relativeUri);
inputSources[uri] = _sources[uri];
inputSources[uri] = _sources[uri]!;
});
}
Map<DataId, Object> result = await step.execute(
module,
(Uri uri) => inputSources[uri],
(Module m, DataId id) => inputData[m][id],
(Module m, DataId id) => inputData[m]![id],
flags);
for (var dataId in step.resultData) {
(_results[module] ??= {})[dataId] = result[dataId];
(results[module] ??= {})[dataId] = result[dataId]!;
}
}
}

View file

@ -52,7 +52,7 @@ class ModularStep {
{this.needsSources: true,
this.dependencyDataNeeded: const [],
this.moduleDataNeeded: const [],
this.resultData,
this.resultData: const [],
this.onlyOnMain: false,
this.onlyOnSdk: false,
this.notOnSdk: false});
@ -94,13 +94,13 @@ abstract class Pipeline<S extends ModularStep> {
// or by the same step on a dependency.
Map<DataId, S> previousKinds = {};
for (var step in steps) {
if (step.resultData == null || step.resultData.isEmpty) {
if (step.resultData.isEmpty) {
_validationError(
"'${step.runtimeType}' needs to declare what data it produces.");
}
for (var resultKind in step.resultData) {
if (previousKinds.containsKey(resultKind) &&
!areMutuallyExclusive(step, previousKinds[resultKind])) {
!areMutuallyExclusive(step, previousKinds[resultKind]!)) {
_validationError("Cannot produce the same data on two modular steps."
" '$resultKind' was previously produced by "
"'${previousKinds[resultKind].runtimeType}' but "
@ -151,7 +151,7 @@ abstract class Pipeline<S extends ModularStep> {
await _recursiveRun(
step, dependency, computedData, transitiveDependencies, flags);
deps.add(dependency);
deps.addAll(transitiveDependencies[dependency]);
deps.addAll(transitiveDependencies[dependency]!);
}
if ((step.onlyOnMain && !module.isMain) ||
@ -163,15 +163,15 @@ abstract class Pipeline<S extends ModularStep> {
deps.forEach((dep) {
visibleData[dep] = {};
for (var dataId in step.dependencyDataNeeded) {
if (computedData[dep].contains(dataId)) {
visibleData[dep].add(dataId);
if (computedData[dep]!.contains(dataId)) {
visibleData[dep]!.add(dataId);
}
}
});
visibleData[module] = {};
for (var dataId in step.moduleDataNeeded) {
if (computedData[module].contains(dataId)) {
visibleData[module].add(dataId);
if (computedData[module]!.contains(dataId)) {
visibleData[module]!.add(dataId);
}
}
await runStep(step, module, visibleData, flags);

View file

@ -26,15 +26,15 @@ Future<void> runSuite(Uri suiteFolder, String suiteName, Options options,
await generic.runSuite(
entries,
new generic.RunnerOptions()
..suiteName = suiteName
..configurationName = options.configurationName
..filter = options.filter
..logDir = options.outputDirectory
..shard = options.shard
..shards = options.shards
..verbose = options.verbose
..reproTemplate = '%executable %script --verbose --filter %name');
new generic.RunnerOptions(
suiteName: suiteName,
configurationName: options.configurationName,
filter: options.filter,
logDir: options.outputDirectory,
shard: options.shard,
shards: options.shards,
verbose: options.verbose,
reproTemplate: '%executable %script --verbose --filter %name'));
await pipeline.cleanup();
}
@ -59,11 +59,11 @@ class _PipelineTest implements generic.Test {
class Options {
bool showSkipped = false;
bool verbose = false;
String filter = null;
String? filter;
int shards = 1;
int shard = 1;
String configurationName;
Uri outputDirectory;
String? configurationName;
Uri? outputDirectory;
bool useSdk = false;
static Options parse(List<String> args) {
@ -92,7 +92,7 @@ class Options {
help: 'configuration name to use for emitting jsonl result files.');
ArgResults argResults = parser.parse(args);
int shards = int.tryParse(argResults['shards']) ?? 1;
int shard;
int shard = 1;
if (shards > 1) {
shard = int.tryParse(argResults['shard']) ?? 1;
if (shard <= 0 || shard >= shards) {
@ -101,7 +101,7 @@ class Options {
exit(1);
}
}
Uri toUri(s) => s == null ? null : Uri.base.resolveUri(Uri.file(s));
Uri? toUri(s) => s == null ? null : Uri.base.resolveUri(Uri.file(s));
return Options()
..showSkipped = argResults['show-skipped']
..verbose = argResults['verbose']

View file

@ -17,15 +17,7 @@ class ModularTest {
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");
}
if (modules.isEmpty) throw ArgumentError("modules cannot be empty");
for (var module in modules) {
module._validate();
}
@ -52,7 +44,7 @@ class Module {
/// The file containing the main entry method, if any. Stored as a relative
/// [Uri] from [rootUri].
final Uri mainSource;
final Uri? mainSource;
/// Whether this module is also available as a package import, where the
/// package name matches the module name.
@ -63,7 +55,7 @@ class Module {
/// When [isPackage], the base where all package URIs are resolved against.
/// Stored as a relative [Uri] from [rootUri].
final Uri packageBase;
final Uri? packageBase;
/// Whether this is the main entry module of a test.
bool isMain;

View file

@ -72,15 +72,15 @@ TestSpecification parseTestSpecification(String contents) {
if (key is! String) {
_invalidSpecification("key: '$key' is not a string");
}
normalizedMap[key] = [];
final values = normalizedMap[key] = [];
if (value is String) {
normalizedMap[key].add(value);
values.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);
values.add(entry);
});
} else {
_invalidSpecification(

View file

@ -6,7 +6,7 @@ description: >
This is used within the Dart SDK to define and validate modular tests, and to
execute them using the modular pipeline of different SDK tools.
environment:
sdk: ">=2.10.0 <3.0.0"
sdk: '>=2.16.0 <3.0.0'
dependencies:
args: any

View file

@ -35,7 +35,7 @@ class IOPipelineTestStrategy implements PipelineTestStrategy<IOModularStep> {
for (var uri in sources.keys) {
var file = new File.fromUri(uri);
await file.create(recursive: true);
await file.writeAsStringSync(sources[uri]);
file.writeAsStringSync(sources[uri]!);
}
return new IOPipeline(steps,
saveIntermediateResultsForTesting: true,
@ -44,49 +44,49 @@ class IOPipelineTestStrategy implements PipelineTestStrategy<IOModularStep> {
@override
IOModularStep createSourceOnlyStep(
{String Function(Map<Uri, String>) action,
DataId resultId,
{required String Function(Map<Uri, String?>) action,
required DataId resultId,
bool requestSources: true}) =>
SourceOnlyStep(action, resultId, requestSources);
@override
IOModularStep createModuleDataStep(
{String Function(String) action,
DataId inputId,
DataId resultId,
{required String Function(String) action,
required DataId inputId,
required DataId resultId,
bool requestModuleData: true}) =>
ModuleDataStep(action, inputId, resultId, requestModuleData);
@override
IOModularStep createLinkStep(
{String Function(String, List<String>) action,
DataId inputId,
DataId depId,
DataId resultId,
{required String Function(String, List<String?>) action,
required DataId inputId,
required DataId depId,
required DataId resultId,
bool requestDependenciesData: true}) =>
LinkStep(action, inputId, depId, resultId, requestDependenciesData);
@override
IOModularStep createMainOnlyStep(
{String Function(String, List<String>) action,
DataId inputId,
DataId depId,
DataId resultId,
{required String Function(String, List<String?>) action,
required DataId inputId,
required DataId depId,
required DataId resultId,
bool requestDependenciesData: true}) =>
MainOnlyStep(action, inputId, depId, resultId, requestDependenciesData);
@override
IOModularStep createTwoOutputStep(
{String Function(String) action1,
String Function(String) action2,
DataId inputId,
DataId result1Id,
DataId result2Id}) =>
{required String Function(String) action1,
required String Function(String) action2,
required DataId inputId,
required DataId result1Id,
required DataId result2Id}) =>
TwoOutputStep(action1, action2, inputId, result1Id, result2Id);
@override
String getResult(covariant IOPipeline pipeline, Module m, DataId dataId) {
var folderUri = pipeline.resultFolderUriForTesting;
String? getResult(covariant IOPipeline pipeline, Module m, DataId dataId) {
var folderUri = pipeline.resultFolderUriForTesting!;
var file = File.fromUri(folderUri
.resolve(pipeline.configSpecificResultFileNameForTesting(m, dataId)));
return file.existsSync() ? file.readAsStringSync() : null;
@ -100,7 +100,7 @@ class IOPipelineTestStrategy implements PipelineTestStrategy<IOModularStep> {
}
class SourceOnlyStep implements IOModularStep {
final String Function(Map<Uri, String>) action;
final String Function(Map<Uri, String?>) action;
final DataId resultId;
final bool needsSources;
List<DataId> get dependencyDataNeeded => const [];
@ -115,12 +115,11 @@ class SourceOnlyStep implements IOModularStep {
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
Map<Uri, String> sources = {};
Map<Uri, String?> sources = {};
for (var uri in module.sources) {
var file = File.fromUri(root.resolveUri(uri));
String data = await file.exists() ? await file.readAsString() : null;
sources[uri] = data;
sources[uri] = await file.exists() ? await file.readAsString() : null;
}
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(action(sources));
@ -199,7 +198,7 @@ class LinkStep implements IOModularStep {
final List<DataId> dependencyDataNeeded;
List<DataId> get moduleDataNeeded => [inputId];
List<DataId> get resultData => [resultId];
final String Function(String, List<String>) action;
final String Function(String, List<String?>) action;
final DataId inputId;
final DataId depId;
final DataId resultId;
@ -214,14 +213,14 @@ class LinkStep implements IOModularStep {
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
List<String> depsData = [];
List<String?> depsData = [];
for (var dependency in module.dependencies) {
var depData = await _readHelper(dependency, root, depId, toUri);
depsData.add(depData);
}
var inputData = await _readHelper(module, root, inputId, toUri);
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(action(inputData, depsData));
.writeAsString(action(inputData!, depsData));
}
@override
@ -233,7 +232,7 @@ class MainOnlyStep implements IOModularStep {
final List<DataId> dependencyDataNeeded;
List<DataId> get moduleDataNeeded => [inputId];
List<DataId> get resultData => [resultId];
final String Function(String, List<String>) action;
final String Function(String, List<String?>) action;
final DataId inputId;
final DataId depId;
final DataId resultId;
@ -248,21 +247,21 @@ class MainOnlyStep implements IOModularStep {
@override
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
List<String> flags) async {
List<String> depsData = [];
List<String?> depsData = [];
for (var dependency in computeTransitiveDependencies(module)) {
var depData = await _readHelper(dependency, root, depId, toUri);
depsData.add(depData);
}
var inputData = await _readHelper(module, root, inputId, toUri);
await File.fromUri(root.resolveUri(toUri(module, resultId)))
.writeAsString(action(inputData, depsData));
.writeAsString(action(inputData!, depsData));
}
@override
void notifyCached(Module module) {}
}
Future<String> _readHelper(Module module, Uri root, DataId dataId,
Future<String?> _readHelper(Module module, Uri root, DataId dataId,
ModuleDataToRelativeUri toUri) async {
var file = File.fromUri(root.resolveUri(toUri(module, dataId)));
if (await file.exists()) {

View file

@ -19,7 +19,7 @@ main(List<String> args) async {
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));
skip: options.filter != null && !dirName.contains(options.filter!));
}
}
}
@ -46,7 +46,7 @@ Future<void> _runTest(Uri uri, String dirName, _Options options) async {
" ${Platform.executable} ${Platform.script} "
"--update --show-update --filter $dirName");
}
expect(expectation, result);
expect(result, expectation);
} else if (await file.exists() && (await file.readAsString() == result)) {
print(" expectation matches result and was up to date.");
} else {
@ -100,7 +100,7 @@ String _dumpAsText(ModularTest test) {
class _Options {
bool updateExpectations = false;
bool showResultOnUpdate = false;
String filter = null;
String? filter;
static _Options parse(List<String> args) {
var parser = new ArgParser()

View file

@ -30,56 +30,57 @@ class MemoryPipelineTestStrategy
@override
MemoryModularStep createSourceOnlyStep(
{String Function(Map<Uri, String>) action,
DataId resultId,
{required String Function(Map<Uri, String?>) action,
required DataId resultId,
bool requestSources: true}) =>
SourceOnlyStep(action, resultId, requestSources);
@override
MemoryModularStep createModuleDataStep(
{String Function(String) action,
DataId inputId,
DataId resultId,
{required String Function(String) action,
required DataId inputId,
required DataId resultId,
bool requestModuleData: true}) =>
ModuleDataStep(action, inputId, resultId, requestModuleData);
@override
MemoryModularStep createLinkStep(
{String Function(String, List<String>) action,
DataId inputId,
DataId depId,
DataId resultId,
{required String Function(String, List<String?>) action,
required DataId inputId,
required DataId depId,
required DataId resultId,
bool requestDependenciesData: true}) =>
LinkStep(action, inputId, depId, resultId, requestDependenciesData);
@override
MemoryModularStep createMainOnlyStep(
{String Function(String, List<String>) action,
DataId inputId,
DataId depId,
DataId resultId,
{required String Function(String, List<String?>) action,
required DataId inputId,
required DataId depId,
required DataId resultId,
bool requestDependenciesData: true}) =>
MainOnlyStep(action, inputId, depId, resultId, requestDependenciesData);
@override
MemoryModularStep createTwoOutputStep(
{String Function(String) action1,
String Function(String) action2,
DataId inputId,
DataId result1Id,
DataId result2Id}) =>
{required String Function(String) action1,
required String Function(String) action2,
required DataId inputId,
required DataId result1Id,
required DataId result2Id}) =>
TwoOutputStep(action1, action2, inputId, result1Id, result2Id);
@override
String getResult(covariant MemoryPipeline pipeline, Module m, DataId dataId) {
return pipeline.resultsForTesting[m][dataId];
String? getResult(
covariant MemoryPipeline pipeline, Module m, DataId dataId) {
return pipeline.resultsForTesting![m]![dataId] as String?;
}
FutureOr<void> cleanup(Pipeline<MemoryModularStep> pipeline) => null;
}
class SourceOnlyStep implements MemoryModularStep {
final String Function(Map<Uri, String>) action;
final String Function(Map<Uri, String?>) action;
final DataId resultId;
final bool needsSources;
List<DataId> get dependencyDataNeeded => const [];
@ -96,7 +97,7 @@ class SourceOnlyStep implements MemoryModularStep {
SourceProvider sourceProvider,
ModuleDataProvider dataProvider,
List<String> flags) {
Map<Uri, String> sources = {};
Map<Uri, String?> sources = {};
for (var uri in module.sources) {
sources[uri] = sourceProvider(module.rootUri.resolveUri(uri));
}
@ -127,9 +128,10 @@ class ModuleDataStep implements MemoryModularStep {
SourceProvider sourceProvider,
ModuleDataProvider dataProvider,
List<String> flags) {
var inputData = dataProvider(module, inputId) as String;
if (inputData == null)
var inputData = dataProvider(module, inputId) as String?;
if (inputData == null) {
return Future.value({resultId: "data for $module was null"});
}
return Future.value({resultId: action(inputData)});
}
@ -159,12 +161,13 @@ class TwoOutputStep implements MemoryModularStep {
SourceProvider sourceProvider,
ModuleDataProvider dataProvider,
List<String> flags) {
var inputData = dataProvider(module, inputId) as String;
if (inputData == null)
var inputData = dataProvider(module, inputId) as String?;
if (inputData == null) {
return Future.value({
result1Id: "data for $module was null",
result2Id: "data for $module was null",
result1Id: "result for $module was null",
result2Id: "result for $module was null",
});
}
return Future.value(
{result1Id: action1(inputData), result2Id: action2(inputData)});
}
@ -177,7 +180,7 @@ class LinkStep implements MemoryModularStep {
bool get needsSources => false;
final List<DataId> dependencyDataNeeded;
List<DataId> get moduleDataNeeded => [inputId];
final String Function(String, List<String>) action;
final String Function(String, List<String?>) action;
final DataId inputId;
final DataId depId;
final DataId resultId;
@ -196,9 +199,9 @@ class LinkStep implements MemoryModularStep {
ModuleDataProvider dataProvider,
List<String> flags) {
List<String> depsData = module.dependencies
.map((d) => dataProvider(d, depId) as String)
.map((d) => dataProvider(d, depId).toString())
.toList();
var inputData = dataProvider(module, inputId) as String;
var inputData = dataProvider(module, inputId).toString();
return Future.value({resultId: action(inputData, depsData)});
}
@ -210,7 +213,7 @@ class MainOnlyStep implements MemoryModularStep {
bool get needsSources => false;
final List<DataId> dependencyDataNeeded;
List<DataId> get moduleDataNeeded => [inputId];
final String Function(String, List<String>) action;
final String Function(String, List<String?>) action;
final DataId inputId;
final DataId depId;
final DataId resultId;
@ -228,11 +231,11 @@ class MainOnlyStep implements MemoryModularStep {
SourceProvider sourceProvider,
ModuleDataProvider dataProvider,
List<String> flags) {
List<String> depsData = computeTransitiveDependencies(module)
.map((d) => dataProvider(d, depId) as String)
List<String?> depsData = computeTransitiveDependencies(module)
.map((d) => dataProvider(d, depId) as String?)
.toList();
var inputData = dataProvider(module, inputId) as String;
return Future.value({resultId: action(inputData, depsData)});
var inputData = dataProvider(module, inputId) as String?;
return Future.value({resultId: action(inputData!, depsData)});
}
@override

View file

@ -34,16 +34,16 @@ abstract class PipelineTestStrategy<S extends ModularStep> {
/// Create a step that applies [action] on all input files of the module, and
/// emits a result with the given [id]
S createSourceOnlyStep(
{String Function(Map<Uri, String>) action,
DataId resultId,
{required String Function(Map<Uri, String?>) action,
required DataId resultId,
bool requestSources: true});
/// Create a step that applies [action] on the module [inputId] data, and
/// emits a result with the given [resultId].
S createModuleDataStep(
{String Function(String) action,
DataId inputId,
DataId resultId,
{required String Function(String) action,
required DataId inputId,
required DataId resultId,
bool requestModuleData: true});
/// Create a step that applies [action] on the module [inputId] data and the
@ -52,10 +52,10 @@ abstract class PipelineTestStrategy<S extends ModularStep> {
///
/// [depId] may be the same as [resultId] or [inputId].
S createLinkStep(
{String Function(String, List<String>) action,
DataId inputId,
DataId depId,
DataId resultId,
{required String Function(String, List<String?>) action,
required DataId inputId,
required DataId depId,
required DataId resultId,
bool requestDependenciesData: true});
/// Create a step that applies [action] only on the main module [inputId] data
@ -65,23 +65,23 @@ abstract class PipelineTestStrategy<S extends ModularStep> {
/// [depId] may be the same as [inputId] but not [resultId] since this action
/// is only applied on the main module.
S createMainOnlyStep(
{String Function(String, List<String>) action,
DataId inputId,
DataId depId,
DataId resultId,
{required String Function(String, List<String?>) action,
required DataId inputId,
required DataId depId,
required DataId resultId,
bool requestDependenciesData: true});
/// Create a step that applies [action1] and [action2] on the module [inputId]
/// data, and emits two results with the given [result1Id] and [result2Id].
S createTwoOutputStep(
{String Function(String) action1,
String Function(String) action2,
DataId inputId,
DataId result1Id,
DataId result2Id});
{required String Function(String) action1,
required String Function(String) action2,
required DataId inputId,
required DataId result1Id,
required DataId result2Id});
/// Return the result data produced by a modular step.
String getResult(Pipeline<S> pipeline, Module m, DataId dataId);
String? getResult(Pipeline<S> pipeline, Module m, DataId dataId);
/// Do any cleanup work needed after pipeline is completed. Needed because
/// some implementations retain data around to be able to answer [getResult]
@ -322,7 +322,7 @@ runPipelineTest<S extends ModularStep>(PipelineTestStrategy<S> testStrategy) {
var counterStep = testStrategy.createSourceOnlyStep(
action: (_) => '${i++}', resultId: counterId);
var linkStep = testStrategy.createLinkStep(
action: (String m, List<String> deps) => "${deps.join(',')},$m",
action: (String m, List<String?> deps) => "${deps.join(',')},$m",
inputId: counterId,
depId: counterId,
resultId: linkId,
@ -352,7 +352,7 @@ runPipelineTest<S extends ModularStep>(PipelineTestStrategy<S> testStrategy) {
var counterStep = testStrategy.createSourceOnlyStep(
action: (_) => '${i++}', resultId: counterId);
var linkStep = testStrategy.createLinkStep(
action: (String m, List<String> deps) => "${deps.join(',')},$m",
action: (String m, List<String?> deps) => "${deps.join(',')},$m",
inputId: counterId,
depId: counterId,
resultId: linkId,
@ -384,7 +384,7 @@ runPipelineTest<S extends ModularStep>(PipelineTestStrategy<S> testStrategy) {
var counterStep = testStrategy.createSourceOnlyStep(
action: (_) => '${i++}', resultId: counterId);
var linkStep = testStrategy.createLinkStep(
action: (String m, List<String> deps) => "${deps.join(',')},$m",
action: (String m, List<String?> deps) => "${deps.join(',')},$m",
inputId: counterId,
depId: counterId,
resultId: linkId,
@ -419,7 +419,7 @@ DataId _lowercaseId = const DataId("lowercase");
DataId _uppercaseId = const DataId("uppercase");
DataId _joinId = const DataId("join");
String _concat(Map<Uri, String> sources) {
String _concat(Map<Uri, String?> sources) {
var buffer = new StringBuffer();
sources.forEach((uri, contents) {
buffer.write("$uri: $contents\n");
@ -430,7 +430,7 @@ String _concat(Map<Uri, String> sources) {
String _lowercase(String contents) => contents.toLowerCase();
String _uppercase(String contents) => contents.toUpperCase();
String _replaceAndJoin(String moduleData, List<String> depContents) {
String _replaceAndJoin(String moduleData, List<String?> depContents) {
var buffer = new StringBuffer();
depContents.forEach(buffer.writeln);
buffer.write(moduleData.replaceAll(".dart:", ""));

View file

@ -83,5 +83,5 @@ class _NoopPipeline extends Pipeline {
@override
Future<void> runStep(ModularStep step, Module module,
Map<Module, Set<DataId>> visibleData, List<String> flags) =>
null;
Future.value(null);
}

View file

@ -8,19 +8,10 @@ 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")],