| // Copyright (c) 2016, 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.md file. | |
| library testing.run; | |
| import 'dart:async' show Future, Stream; | |
| import 'dart:convert' show json; | |
| import 'dart:io' show Platform; | |
| import 'dart:isolate' show Isolate, ReceivePort; | |
| import 'test_root.dart' show TestRoot; | |
| import 'error_handling.dart' show withErrorHandling; | |
| import 'chain.dart' show CreateContext; | |
| import '../testing.dart' | |
| show Chain, ChainContext, FileBasedTestDescription, listTests; | |
| import 'analyze.dart' show Analyze; | |
| import 'log.dart' | |
| show enableVerboseOutput, isVerbose, Logger, splitLines, StdoutLogger; | |
| import 'suite.dart' show Dart, Suite; | |
| import 'test_dart.dart' show TestDart; | |
| import 'zone_helper.dart' show acknowledgeControlMessages; | |
| import 'run_tests.dart' show CommandLine; | |
| Future<TestRoot> computeTestRoot(String? configurationPath, Uri? base) { | |
| Uri configuration = configurationPath == null | |
| ? Uri.base.resolve("testing.json") | |
| : base!.resolve(configurationPath); | |
| return TestRoot.fromUri(configuration); | |
| } | |
| /// This is called from a Chain suite, and helps implement main. In most cases, | |
| /// main will look like this: | |
| /// | |
| /// main(List<String> arguments) => runMe(arguments, createContext); | |
| /// | |
| /// The optional argument [configurationPath] should be used when | |
| /// `testing.json` isn't located in the current working directory and is a path | |
| /// relative to [me] which defaults to `Platform.script`. | |
| Future<Null> runMe(List<String> arguments, CreateContext f, | |
| {String? configurationPath, | |
| Uri? me, | |
| int shards = 1, | |
| int shard = 0, | |
| Logger logger: const StdoutLogger()}) { | |
| me ??= Platform.script; | |
| return withErrorHandling(() async { | |
| TestRoot testRoot = await computeTestRoot(configurationPath, me); | |
| CommandLine cl = CommandLine.parse(arguments); | |
| if (cl.verbose) enableVerboseOutput(); | |
| for (Chain suite in testRoot.toolChains) { | |
| if (me == suite.source) { | |
| ChainContext context = await f(suite, cl.environment); | |
| await context.run(suite, new Set<String>.from(cl.selectors), | |
| shards: shards, shard: shard, logger: logger); | |
| } | |
| } | |
| }, logger: logger); | |
| } | |
| /// This is called from a `_test.dart` file, and helps integration in other | |
| /// test runner frameworks. | |
| /// | |
| /// For example, to run the suite `my_suite` from `test.dart`, create a file | |
| /// with this content: | |
| /// | |
| /// import 'package:async_helper/async_helper.dart' show asyncTest; | |
| /// | |
| /// import 'package:testing/testing.dart' show run; | |
| /// | |
| /// main(List<String> arguments) => asyncTest(run(arguments, ["my_suite"])); | |
| /// | |
| /// To run run the same suite from `package:test`, create a file with this | |
| /// content: | |
| /// | |
| /// import 'package:test/test.dart' show Timeout, test; | |
| /// | |
| /// import 'package:testing/testing.dart' show run; | |
| /// | |
| /// main() { | |
| /// test("my_suite", () => run([], ["my_suite"]), | |
| /// timeout: new Timeout(new Duration(minutes: 5))); | |
| /// } | |
| /// | |
| /// The optional argument [configurationPath] should be used when | |
| /// `testing.json` isn't located in the current working directory and is a path | |
| /// relative to `Uri.base`. | |
| Future<Null> run(List<String> arguments, List<String> suiteNames, | |
| [String? configurationPath]) { | |
| return withErrorHandling(() async { | |
| TestRoot root = await computeTestRoot(configurationPath, Uri.base); | |
| List<Suite> suites = root.suites | |
| .where((Suite suite) => suiteNames.contains(suite.name)) | |
| .toList(); | |
| SuiteRunner runner = new SuiteRunner(suites, <String, String>{}, | |
| const <String>[], new Set<String>(), new Set<String>()); | |
| String? program = await runner.generateDartProgram(); | |
| await runner.analyze(root.packages); | |
| if (program != null) { | |
| await runProgram(program, root.packages); | |
| } | |
| }); | |
| } | |
| Future<Null> runProgram(String program, Uri packages) async { | |
| const StdoutLogger().logMessage("Running:"); | |
| const StdoutLogger().logNumberedLines(program); | |
| Uri dataUri = new Uri.dataFromString(program); | |
| ReceivePort exitPort = new ReceivePort(); | |
| Isolate isolate = await Isolate.spawnUri(dataUri, <String>[], null, | |
| paused: true, | |
| onExit: exitPort.sendPort, | |
| errorsAreFatal: false, | |
| checked: true, | |
| packageConfig: packages); | |
| List? error; | |
| var subscription = isolate.errors.listen((data) { | |
| error = data; | |
| exitPort.close(); | |
| }); | |
| await acknowledgeControlMessages(isolate, resume: isolate.pauseCapability); | |
| await for (var _ in exitPort) { | |
| exitPort.close(); | |
| } | |
| subscription.cancel(); | |
| return error == null | |
| ? null | |
| : new Future<Null>.error(error![0], new StackTrace.fromString(error![1])); | |
| } | |
| class SuiteRunner { | |
| final List<Suite> suites; | |
| final Map<String, String> environment; | |
| final List<String> selectors; | |
| final Set<String> selectedSuites; | |
| final Set<String> skippedSuites; | |
| final List<Uri> testUris = <Uri>[]; | |
| SuiteRunner(this.suites, this.environment, Iterable<String> selectors, | |
| this.selectedSuites, this.skippedSuites) | |
| : selectors = selectors.toList(growable: false); | |
| bool shouldRunSuite(Suite suite) { | |
| return !skippedSuites.contains(suite.name) && | |
| (selectedSuites.isEmpty || selectedSuites.contains(suite.name)); | |
| } | |
| Future<String?> generateDartProgram() async { | |
| testUris.clear(); | |
| StringBuffer imports = new StringBuffer(); | |
| StringBuffer dart = new StringBuffer(); | |
| StringBuffer chain = new StringBuffer(); | |
| bool hasRunnableTests = false; | |
| await for (FileBasedTestDescription description in listDescriptions()) { | |
| hasRunnableTests = true; | |
| description.writeImportOn(imports); | |
| description.writeClosureOn(dart); | |
| } | |
| await for (Chain suite in listChainSuites()) { | |
| hasRunnableTests = true; | |
| suite.writeImportOn(imports); | |
| suite.writeClosureOn(chain); | |
| } | |
| bool isFirstTestDartSuite = true; | |
| for (TestDart suite in listTestDartSuites()) { | |
| if (shouldRunSuite(suite)) { | |
| hasRunnableTests = true; | |
| if (isFirstTestDartSuite) { | |
| suite.writeFirstImportOn(imports); | |
| } | |
| isFirstTestDartSuite = false; | |
| suite.writeRunCommandOn(chain); | |
| } | |
| } | |
| if (!hasRunnableTests) return null; | |
| return """ | |
| // @dart=2.9 | |
| library testing.generated; | |
| import 'dart:async' show Future; | |
| import 'dart:convert' show json; | |
| import 'package:testing/src/run_tests.dart' show runTests; | |
| import 'package:testing/src/chain.dart' show runChain; | |
| import 'package:testing/src/log.dart' show enableVerboseOutput, isVerbose; | |
| ${imports.toString().trim()} | |
| Future<Null> main() async { | |
| if ($isVerbose) enableVerboseOutput(); | |
| Map<String, String> environment = | |
| new Map<String, String>.from(json.decode('${json.encode(environment)}')); | |
| Set<String> selectors = | |
| new Set<String>.from(json.decode('${json.encode(selectors)}')); | |
| await runTests(<String, Function> { | |
| ${splitLines(dart.toString().trim()).join(' ')} | |
| }); | |
| ${splitLines(chain.toString().trim()).join(' ')} | |
| } | |
| """; | |
| } | |
| Future<bool> analyze(Uri packages) async { | |
| bool hasAnalyzerSuites = false; | |
| for (Analyze suite in listAnalyzerSuites()) { | |
| if (shouldRunSuite(suite)) { | |
| hasAnalyzerSuites = true; | |
| await suite.run(packages, testUris); | |
| } | |
| } | |
| return hasAnalyzerSuites; | |
| } | |
| Stream<FileBasedTestDescription> listDescriptions() async* { | |
| for (Dart suite in suites.whereType<Dart>()) { | |
| await for (FileBasedTestDescription description | |
| in listTests(<Uri>[suite.uri], pattern: "")) { | |
| testUris.add((await Isolate.resolvePackageUri(description.uri))!); | |
| if (shouldRunSuite(suite)) { | |
| String path = description.file.uri.path; | |
| if (suite.exclude.any((RegExp r) => path.contains(r))) continue; | |
| if (suite.pattern.any((RegExp r) => path.contains(r))) { | |
| yield description; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Stream<Chain> listChainSuites() async* { | |
| for (Chain suite in suites.whereType<Chain>()) { | |
| testUris.add((await Isolate.resolvePackageUri(suite.source))!); | |
| if (shouldRunSuite(suite)) { | |
| yield suite; | |
| } | |
| } | |
| } | |
| Iterable<TestDart> listTestDartSuites() { | |
| return suites.whereType<TestDart>(); | |
| } | |
| Iterable<Analyze> listAnalyzerSuites() { | |
| return suites.whereType<Analyze>(); | |
| } | |
| } |