| // 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. | |
| /// Model for a modular test. | |
| library; | |
| /// A modular test declares the structure of the test code: what files are | |
| /// grouped as a module and how modules depend on one another. | |
| class ModularTest { | |
| /// Modules that will be compiled by for modular test | |
| final List<Module> modules; | |
| /// The module containing the main entry method. | |
| final Module mainModule; | |
| /// Flags provided to tools that compile and execute the test. | |
| final List<String> flags; | |
| ModularTest(this.modules, this.mainModule, this.flags) { | |
| if (modules.isEmpty) throw ArgumentError("modules cannot be empty"); | |
| for (var module in modules) { | |
| module._validate(); | |
| } | |
| } | |
| String debugString() => modules.map((m) => m.debugString()).join('\n'); | |
| } | |
| /// A single module in a modular test. | |
| class Module { | |
| /// A short name to identify this module. | |
| final String name; | |
| /// Other modules that need to be compiled first and whose result may be | |
| /// necessary in order to compile this module. | |
| final List<Module> dependencies; | |
| /// Root under which all sources in the module can be found. | |
| final Uri rootUri; | |
| /// Source files that are part of this module only. Stored as a relative [Uri] | |
| /// from [rootUri]. | |
| final List<Uri> sources; | |
| /// The file containing the main entry method, if any. Stored as a relative | |
| /// [Uri] from [rootUri]. | |
| final Uri? mainSource; | |
| /// Whether this module is also available as a package import, where the | |
| /// package name matches the module name. | |
| bool isPackage; | |
| /// Whether this module represents part of the sdk. | |
| bool isSdk; | |
| /// When [isPackage], the base where all package URIs are resolved against. | |
| /// Stored as a relative [Uri] from [rootUri]. | |
| final Uri? packageBase; | |
| /// 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.isShared = false, | |
| this.isSdk = false, | |
| }) { | |
| if (!_validModuleName.hasMatch(name)) { | |
| throw ArgumentError("invalid module name: $name"); | |
| } | |
| } | |
| void _validate() { | |
| if (!isPackage && !isShared && !isSdk) return; | |
| // Note: we validate this now and not in the constructor because loader.dart | |
| // may update `isPackage` after the module is created. | |
| if (isSdk && isPackage) { | |
| throw InvalidModularTestError( | |
| "invalid module: $name is an sdk " | |
| "module but was also marked as a package module.", | |
| ); | |
| } | |
| for (var dependency in dependencies) { | |
| if (isPackage && !dependency.isPackage && !dependency.isSdk) { | |
| 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.", | |
| ); | |
| } | |
| if (isSdk) { | |
| // TODO(sigmund): we should allow to split sdk modules in smaller | |
| // pieces. This requires a bit of work: | |
| // - allow to compile subsets of the sdk (see #30957 regarding | |
| // extraRequiredLibraries in CFE) | |
| // - add logic to specify sdk dependencies. | |
| throw InvalidModularTestError( | |
| "invalid dependency: $name is an sdk module that depends on " | |
| "${dependency.name}, but sdk modules are not expected to " | |
| "have dependencies.", | |
| ); | |
| } | |
| } | |
| } | |
| @override | |
| String toString() => '[module $name]'; | |
| String debugString() { | |
| var buffer = StringBuffer(); | |
| buffer.write(' '); | |
| buffer.write(name); | |
| buffer.write(': '); | |
| buffer.write(isPackage ? 'package' : '(not package)'); | |
| buffer.write(', deps: {${dependencies.map((d) => d.name).join(", ")}}'); | |
| if (isSdk) { | |
| buffer.write(', sources: {...omitted ${sources.length} sources...}'); | |
| } else { | |
| buffer.write(', sources: {${sources.map((u) => "$u").join(', ')}}'); | |
| } | |
| return '$buffer'; | |
| } | |
| } | |
| final RegExp _validModuleName = RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$'); | |
| /// Helper to compute transitive dependencies from [module]. | |
| Set<Module> computeTransitiveDependencies(Module module) { | |
| Set<Module> deps = {}; | |
| helper(Module m) { | |
| if (deps.add(m)) m.dependencies.forEach(helper); | |
| } | |
| 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 { | |
| final 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); | |
| @override | |
| String toString() => "Invalid modular test: $message"; | |
| } |