| // 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 file. | |
| library; | |
| 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; | |
| import 'analyze.dart' show Analyze; | |
| import 'log.dart' | |
| show enableVerboseOutput, isVerbose, Logger, splitLines, StdoutLogger; | |
| import 'suite.dart' show Suite; | |
| 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<void> runMe( | |
| List<String> arguments, | |
| CreateContext f, { | |
| String? configurationPath, | |
| Uri? me, | |
| int shards = 1, | |
| int shard = 0, | |
| int? limitTo, | |
| 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, | |
| Set<String>.from(cl.selectors), | |
| shards: shards, | |
| shard: shard, | |
| limitTo: limitTo, | |
| 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:expect/async_helper.dart' show asyncTest; | |
| /// | |
| /// import 'package:testing/testing.dart' show run; | |
| /// | |
| /// main(List<String> arguments) => asyncTest(run(arguments, ["my_suite"])); | |
| /// | |
| /// To 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<void> 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 = SuiteRunner( | |
| suites, | |
| <String, String>{}, | |
| const <String>[], | |
| <String>{}, | |
| <String>{}, | |
| ); | |
| String? program = await runner.generateDartProgram(); | |
| await runner.analyze(root.packages); | |
| if (program != null) { | |
| await runProgram(program, root.packages); | |
| } | |
| }); | |
| } | |
| Future<void> runProgram(String program, Uri packages) async { | |
| const StdoutLogger().logMessage("Running:"); | |
| const StdoutLogger().logNumberedLines(program); | |
| Uri dataUri = Uri.dataFromString(program); | |
| ReceivePort exitPort = 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(); | |
| } | |
| await subscription.cancel(); | |
| return error == null | |
| ? null | |
| : Future.error(error![0], 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>[]; | |
| new( | |
| 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 = StringBuffer(); | |
| StringBuffer dart = StringBuffer(); | |
| StringBuffer chain = StringBuffer(); | |
| bool hasRunnableTests = false; | |
| await for (Chain suite in listChainSuites()) { | |
| hasRunnableTests = true; | |
| suite.writeImportOn(imports); | |
| suite.writeClosureOn(chain); | |
| } | |
| if (!hasRunnableTests) return null; | |
| return """ | |
| 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<Chain> listChainSuites() async* { | |
| for (Chain suite in suites.whereType<Chain>()) { | |
| testUris.add((await Isolate.resolvePackageUri(suite.source))!); | |
| if (shouldRunSuite(suite)) { | |
| yield suite; | |
| } | |
| } | |
| } | |
| Iterable<Analyze> listAnalyzerSuites() { | |
| return suites.whereType<Analyze>(); | |
| } | |
| } |