| // Copyright (c) 2015, 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. | |
| import 'dart:async'; | |
| import 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'package:path/path.dart' as p; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_descriptor/test_descriptor.dart' as d; | |
| import 'package:test_process/test_process.dart'; | |
| /// The path to the root directory of the `test` package. | |
| final Future<String> packageDir = | |
| Isolate.resolvePackageUri(Uri(scheme: 'package', path: 'test/')).then(( | |
| uri, | |
| ) { | |
| var dir = p.dirname(uri!.path); | |
| // If it starts with a `/C:` or other drive letter, remove the leading `/`. | |
| if (dir[0] == '/' && dir[2] == ':') dir = dir.substring(1); | |
| return dir; | |
| }); | |
| /// The root directory of the Dart SDK. | |
| final String sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable)); | |
| final bool supportsCliCompiler = () { | |
| var current = Version.parse(Platform.version.split(' ').first); | |
| return current > Version.parse('3.13.0-159.0.dev'); | |
| }(); | |
| final bool supportsCliCompilerSanitizers = () { | |
| var current = Version.parse(Platform.version.split(' ').first); | |
| return current > Version.parse('3.14.0-62.0.dev'); | |
| }(); | |
| /// The platform-specific message emitted when a nonexistent file is loaded. | |
| final String noSuchFileMessage = Platform.isWindows | |
| ? 'The system cannot find the file specified' | |
| : 'No such file or directory'; | |
| /// An operating system name that's different than the current operating system. | |
| final otherOS = Platform.isWindows ? 'mac-os' : 'windows'; | |
| /// Expects that the entire stdout stream of [test] equals [expected]. | |
| void expectStdoutEquals(TestProcess test, String expected) => | |
| _expectStreamEquals(test.stdoutStream(), expected); | |
| /// Expects that the entire stderr stream of [test] equals [expected]. | |
| void expectStderrEquals(TestProcess test, String expected) => | |
| _expectStreamEquals(test.stderrStream(), expected); | |
| /// Expects that the entirety of the line stream [stream] equals [expected]. | |
| void _expectStreamEquals(Stream<String> stream, String expected) { | |
| expect( | |
| (() async { | |
| var lines = await stream.toList(); | |
| expect(lines.join('\n').trim(), equals(expected.trim())); | |
| })(), | |
| completes, | |
| ); | |
| } | |
| /// Returns a [StreamMatcher] that asserts that the stream emits strings | |
| /// containing each string in [strings] in order. | |
| /// | |
| /// This expects each string in [strings] to match a different string in the | |
| /// stream. | |
| StreamMatcher containsInOrder(Iterable<String> strings) => | |
| emitsInOrder(strings.map((string) => emitsThrough(contains(string)))); | |
| /// Lazily compile the test package to kernel and re-use that, initialized with | |
| /// [precompileTestExecutable]. | |
| String? _testExecutablePath; | |
| /// Must be invoked before any call to [runTests], should be invoked in a top | |
| /// level `setUpAll` for best caching results. | |
| Future<void> precompileTestExecutable() async { | |
| if (_testExecutablePath != null) { | |
| throw StateError('Test executable already precompiled'); | |
| } | |
| var tmpDirectory = await Directory.systemTemp.createTemp('test'); | |
| var precompiledPath = p.join(tmpDirectory.path, 'test_runner.dill'); | |
| var result = await Process.run(Platform.executable, [ | |
| 'compile', | |
| 'kernel', | |
| p.url.join(await packageDir, 'bin', 'test.dart'), | |
| '-o', | |
| precompiledPath, | |
| ]); | |
| if (result.exitCode != 0) { | |
| throw StateError( | |
| 'Failed to compile test runner:\n${result.stdout}\n${result.stderr}', | |
| ); | |
| } | |
| addTearDown(() async { | |
| await tmpDirectory.delete(recursive: true); | |
| }); | |
| _testExecutablePath = precompiledPath; | |
| } | |
| /// Runs the test executable with the package root set properly. | |
| /// | |
| /// You must invoke [precompileTestExecutable] before invoking this function. | |
| Future<TestProcess> runTest( | |
| Iterable<String> args, { | |
| String? reporter, | |
| String? fileReporter, | |
| int? concurrency, | |
| Map<String, String>? environment, | |
| bool forwardStdio = false, | |
| String? packageConfig, | |
| Iterable<String>? vmArgs, | |
| String? workingDirectory, | |
| }) async { | |
| concurrency ??= 1; | |
| var testExecutablePath = _testExecutablePath; | |
| if (testExecutablePath == null) { | |
| throw StateError( | |
| 'You must call `precompileTestExecutable` before calling `runTest`', | |
| ); | |
| } | |
| var allArgs = [ | |
| ...?vmArgs, | |
| testExecutablePath, | |
| '--concurrency=$concurrency', | |
| if (reporter != null) '--reporter=$reporter', | |
| if (fileReporter != null) '--file-reporter=$fileReporter', | |
| ...args, | |
| ]; | |
| environment ??= {}; | |
| environment.putIfAbsent('_DART_TEST_TESTING', () => 'true'); | |
| return await runDart( | |
| allArgs, | |
| environment: environment, | |
| description: 'dart bin/test.dart', | |
| forwardStdio: forwardStdio, | |
| packageConfig: packageConfig, | |
| workingDirectory: workingDirectory, | |
| ); | |
| } | |
| /// Runs Dart. | |
| /// | |
| /// If [packageConfig] is provided then that is passed for the `--packages` | |
| /// arg, otherwise the current isolate config is passed. | |
| Future<TestProcess> runDart( | |
| Iterable<String> args, { | |
| Map<String, String>? environment, | |
| String? description, | |
| bool forwardStdio = false, | |
| String? packageConfig, | |
| String? workingDirectory, | |
| }) async { | |
| var allArgs = <String>[ | |
| '--packages=${packageConfig ?? await Isolate.packageConfig}', | |
| ...args, | |
| ]; | |
| return await TestProcess.start( | |
| p.absolute(Platform.resolvedExecutable), | |
| allArgs, | |
| workingDirectory: workingDirectory ?? d.sandbox, | |
| environment: environment, | |
| description: description, | |
| forwardStdio: forwardStdio, | |
| ); | |
| } | |
| /// Runs Pub. | |
| Future<TestProcess> runPub( | |
| Iterable<String> args, { | |
| Map<String, String>? environment, | |
| String? workingDirectory, | |
| }) { | |
| return TestProcess.start( | |
| p.absolute(Platform.resolvedExecutable), | |
| ['pub', ...args], | |
| workingDirectory: workingDirectory ?? d.sandbox, | |
| environment: environment, | |
| description: 'pub ${args.first}', | |
| ); | |
| } |