Ben Konyi 33b402d24c
Reland "Launch DDS from Dart SDK and prepare to serve DevTools from DDS (#146593)" (#152386)
This reverts commit 7cdc23b3e1bae2bc7bc2d1f34773eaa3629d4fcc.

The failure in the `native_assets_test` integration test on Windows was caused by the DevTools process not being shutdown by the `ColdRunner` when running the profile mode portion of the test. This resulted in the test being unable to clean up the project created by the test as DevTools was still holding onto a handle within the directory. This PR adds back the mistakenly removed DevTools shutdown logic in the `ColdRunner`.
2024-07-26 20:51:19 +00:00

851 lines
29 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:package_config/package_config.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../build_info.dart';
import '../cache.dart';
import '../compile.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../native_assets.dart';
import '../project.dart';
import '../web/chrome.dart';
import '../web/memory_fs.dart';
import 'flutter_platform.dart' as loader;
import 'flutter_web_platform.dart';
import 'font_config_manager.dart';
import 'test_config.dart';
import 'test_time_recorder.dart';
import 'test_wrapper.dart';
import 'watcher.dart';
import 'web_test_compiler.dart';
/// A class that abstracts launching the test process from the test runner.
abstract class FlutterTestRunner {
const factory FlutterTestRunner() = _FlutterTestRunnerImpl;
/// Runs tests using package:test and the Flutter engine.
Future<int> runTests(
TestWrapper testWrapper,
List<Uri> testFiles, {
required DebuggingOptions debuggingOptions,
List<String> names = const <String>[],
List<String> plainNames = const <String>[],
String? tags,
String? excludeTags,
bool enableVmService = false,
bool machine = false,
String? precompiledDillPath,
Map<String, String>? precompiledDillFiles,
bool updateGoldens = false,
TestWatcher? watcher,
required int? concurrency,
String? testAssetDirectory,
FlutterProject? flutterProject,
String? icudtlPath,
Directory? coverageDirectory,
bool web = false,
String? randomSeed,
String? reporter,
String? fileReporter,
String? timeout,
bool failFast = false,
bool runSkipped = false,
int? shardIndex,
int? totalShards,
Device? integrationTestDevice,
String? integrationTestUserIdentifier,
TestTimeRecorder? testTimeRecorder,
TestCompilerNativeAssetsBuilder? nativeAssetsBuilder,
BuildInfo? buildInfo,
});
/// Runs tests using the experimental strategy of spawning each test in a
/// separate lightweight Engine.
Future<int> runTestsBySpawningLightweightEngines(
List<Uri> testFiles, {
required DebuggingOptions debuggingOptions,
List<String> names = const <String>[],
List<String> plainNames = const <String>[],
String? tags,
String? excludeTags,
bool machine = false,
bool updateGoldens = false,
required int? concurrency,
String? testAssetDirectory,
FlutterProject? flutterProject,
String? icudtlPath,
String? randomSeed,
String? reporter,
String? fileReporter,
String? timeout,
bool failFast = false,
bool runSkipped = false,
int? shardIndex,
int? totalShards,
TestTimeRecorder? testTimeRecorder,
TestCompilerNativeAssetsBuilder? nativeAssetsBuilder,
});
}
class _FlutterTestRunnerImpl implements FlutterTestRunner {
const _FlutterTestRunnerImpl();
@override
Future<int> runTests(
TestWrapper testWrapper,
List<Uri> testFiles, {
required DebuggingOptions debuggingOptions,
List<String> names = const <String>[],
List<String> plainNames = const <String>[],
String? tags,
String? excludeTags,
bool enableVmService = false,
bool machine = false,
String? precompiledDillPath,
Map<String, String>? precompiledDillFiles,
bool updateGoldens = false,
TestWatcher? watcher,
required int? concurrency,
String? testAssetDirectory,
FlutterProject? flutterProject,
String? icudtlPath,
Directory? coverageDirectory,
bool web = false,
String? randomSeed,
String? reporter,
String? fileReporter,
String? timeout,
bool failFast = false,
bool runSkipped = false,
int? shardIndex,
int? totalShards,
Device? integrationTestDevice,
String? integrationTestUserIdentifier,
TestTimeRecorder? testTimeRecorder,
TestCompilerNativeAssetsBuilder? nativeAssetsBuilder,
BuildInfo? buildInfo,
}) async {
// Configure package:test to use the Flutter engine for child processes.
final String shellPath = globals.artifacts!.getArtifactPath(Artifact.flutterTester);
// Compute the command-line arguments for package:test.
final List<String> testArgs = <String>[
if (!globals.terminal.supportsColor)
'--no-color',
if (debuggingOptions.startPaused)
'--pause-after-load',
if (machine)
...<String>['-r', 'json']
else if (reporter != null)
...<String>['-r', reporter],
if (fileReporter != null)
'--file-reporter=$fileReporter',
if (timeout != null)
...<String>['--timeout', timeout],
if (concurrency != null)
'--concurrency=$concurrency',
for (final String name in names)
...<String>['--name', name],
for (final String plainName in plainNames)
...<String>['--plain-name', plainName],
if (randomSeed != null)
'--test-randomize-ordering-seed=$randomSeed',
if (tags != null)
...<String>['--tags', tags],
if (excludeTags != null)
...<String>['--exclude-tags', excludeTags],
if (failFast)
'--fail-fast',
if (runSkipped)
'--run-skipped',
if (totalShards != null)
'--total-shards=$totalShards',
if (shardIndex != null)
'--shard-index=$shardIndex',
'--chain-stack-traces',
];
if (web) {
final String tempBuildDir = globals.fs.systemTempDirectory
.createTempSync('flutter_test.')
.absolute
.uri
.toFilePath();
final WebMemoryFS result = await WebTestCompiler(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
artifacts: globals.artifacts!,
processManager: globals.processManager,
config: globals.config,
).initialize(
projectDirectory: flutterProject!.directory,
testOutputDir: tempBuildDir,
testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(),
buildInfo: debuggingOptions.buildInfo,
webRenderer: debuggingOptions.webRenderer,
useWasm: debuggingOptions.webUseWasm,
);
testArgs
..add('--platform=chrome')
..add('--')
..addAll(testFiles.map((Uri uri) => uri.toString()));
testWrapper.registerPlatformPlugin(
<Runtime>[Runtime.chrome],
() {
return FlutterWebPlatform.start(
flutterProject.directory.path,
updateGoldens: updateGoldens,
shellPath: shellPath,
flutterProject: flutterProject,
pauseAfterLoad: debuggingOptions.startPaused,
nullAssertions: debuggingOptions.nullAssertions,
buildInfo: debuggingOptions.buildInfo,
webMemoryFS: result,
logger: globals.logger,
fileSystem: globals.fs,
buildDirectory: globals.fs.directory(tempBuildDir),
artifacts: globals.artifacts,
processManager: globals.processManager,
chromiumLauncher: ChromiumLauncher(
fileSystem: globals.fs,
platform: globals.platform,
processManager: globals.processManager,
operatingSystemUtils: globals.os,
browserFinder: findChromeExecutable,
logger: globals.logger,
),
testTimeRecorder: testTimeRecorder,
webRenderer: debuggingOptions.webRenderer,
useWasm: debuggingOptions.webUseWasm,
);
},
);
await testWrapper.main(testArgs);
return exitCode;
}
testArgs
..add('--')
..addAll(testFiles.map((Uri uri) => uri.toString()));
final InternetAddressType serverType =
debuggingOptions.ipv6 ? InternetAddressType.IPv6 : InternetAddressType.IPv4;
final loader.FlutterPlatform platform = loader.installHook(
testWrapper: testWrapper,
shellPath: shellPath,
debuggingOptions: debuggingOptions,
watcher: watcher,
enableVmService: enableVmService,
machine: machine,
serverType: serverType,
precompiledDillPath: precompiledDillPath,
precompiledDillFiles: precompiledDillFiles,
updateGoldens: updateGoldens,
testAssetDirectory: testAssetDirectory,
projectRootDirectory: globals.fs.currentDirectory.uri,
flutterProject: flutterProject,
icudtlPath: icudtlPath,
integrationTestDevice: integrationTestDevice,
integrationTestUserIdentifier: integrationTestUserIdentifier,
testTimeRecorder: testTimeRecorder,
nativeAssetsBuilder: nativeAssetsBuilder,
buildInfo: buildInfo,
);
try {
globals.printTrace('running test package with arguments: $testArgs');
await testWrapper.main(testArgs);
// test.main() sets dart:io's exitCode global.
globals.printTrace('test package returned with exit code $exitCode');
return exitCode;
} finally {
await platform.close();
}
}
// To compile root_test_isolate_spawner.dart and
// child_test_isolate_spawner.dart successfully, we will need to pass a
// package_config.json to the frontend server that contains the
// union of package:test_core, package:ffi, and all the dependencies of the
// project under test. This function generates such a package_config.json.
static Future<void> _generateIsolateSpawningTesterPackageConfig({
required FlutterProject flutterProject,
required File isolateSpawningTesterPackageConfigFile,
}) async {
final File projectPackageConfigFile = globals.fs.directory(
flutterProject.directory.path,
).childDirectory('.dart_tool').childFile('package_config.json');
final PackageConfig projectPackageConfig = PackageConfig.parseBytes(
projectPackageConfigFile.readAsBytesSync(),
projectPackageConfigFile.uri,
);
// The flutter_tools package_config.json is guaranteed to include
// package:ffi and package:test_core.
final File flutterToolsPackageConfigFile = globals.fs.directory(
globals.fs.path.join(
Cache.flutterRoot!,
'packages',
'flutter_tools',
),
).childDirectory('.dart_tool').childFile('package_config.json');
final PackageConfig flutterToolsPackageConfig = PackageConfig.parseBytes(
flutterToolsPackageConfigFile.readAsBytesSync(),
flutterToolsPackageConfigFile.uri,
);
final List<Package> mergedPackages = <Package>[
...projectPackageConfig.packages,
];
final Set<String> projectPackageNames = Set<String>.from(
mergedPackages.map((Package p) => p.name),
);
for (final Package p in flutterToolsPackageConfig.packages) {
if (!projectPackageNames.contains(p.name)) {
mergedPackages.add(p);
}
}
final PackageConfig mergedPackageConfig = PackageConfig(mergedPackages);
final StringBuffer buffer = StringBuffer();
PackageConfig.writeString(mergedPackageConfig, buffer);
isolateSpawningTesterPackageConfigFile.writeAsStringSync(buffer.toString());
}
static void _generateChildTestIsolateSpawnerSourceFile(
List<Uri> paths, {
required List<String> packageTestArgs,
required bool autoUpdateGoldenFiles,
required File childTestIsolateSpawnerSourceFile,
required File childTestIsolateSpawnerDillFile,
}) {
final Map<String, String> testConfigPaths = <String, String>{};
final StringBuffer buffer = StringBuffer();
buffer.writeln('''
import 'dart:ffi';
import 'dart:isolate';
import 'dart:ui';
import 'package:ffi/ffi.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_channel/isolate_channel.dart';
import 'package:test_api/backend.dart'; // flutter_ignore: test_api_import
''');
String pathToImport(String path) {
assert(path.endsWith('.dart'));
return path
.replaceAll('.', '_')
.replaceAll(':', '_')
.replaceAll('/', '_')
.replaceAll(r'\', '_')
.replaceRange(path.length - '.dart'.length, null, '');
}
final Map<String, String> testImports = <String, String>{};
final Set<String> seenTestConfigPaths = <String>{};
for (final Uri path in paths) {
final String sanitizedPath = !path.path.endsWith('?')
? path.path
: path.path.substring(0, path.path.length - 1);
final String sanitizedImport = pathToImport(sanitizedPath);
buffer.writeln("import '$sanitizedPath' as $sanitizedImport;");
testImports[sanitizedPath] = sanitizedImport;
final File? testConfigFile = findTestConfigFile(
globals.fs.file(
globals.platform.isWindows
? sanitizedPath.replaceAll('/', r'\').replaceFirst(r'\', '')
: sanitizedPath,
),
globals.logger,
);
if (testConfigFile != null) {
final String sanitizedTestConfigImport = pathToImport(testConfigFile.path);
testConfigPaths[sanitizedImport] = sanitizedTestConfigImport;
if (seenTestConfigPaths.add(testConfigFile.path)) {
buffer.writeln("import '${Uri.file(testConfigFile.path, windows: true)}' as $sanitizedTestConfigImport;");
}
}
}
buffer.writeln();
buffer.writeln('const List<String> packageTestArgs = <String>[');
for (final String arg in packageTestArgs) {
buffer.writeln(" '$arg',");
}
buffer.writeln('];');
buffer.writeln();
buffer.writeln('const List<String> testPaths = <String>[');
for (final Uri path in paths) {
buffer.writeln(" '$path',");
}
buffer.writeln('];');
buffer.writeln();
buffer.writeln(r'''
@Native<Void Function(Pointer<Utf8>, Pointer<Utf8>)>(symbol: 'Spawn')
external void _spawn(Pointer<Utf8> entrypoint, Pointer<Utf8> route);
void spawn({required SendPort port, String entrypoint = 'main', String route = '/'}) {
assert(
entrypoint != 'main' || route != '/',
'Spawn should not be used to spawn main with the default route name',
);
IsolateNameServer.registerPortWithName(port, route);
_spawn(entrypoint.toNativeUtf8(), route.toNativeUtf8());
}
''');
buffer.write('''
/// Runs on a spawned isolate.
void createChannelAndConnect(String path, String name, Function testMain) {
goldenFileComparator = LocalFileComparator(Uri.parse(path));
autoUpdateGoldenFiles = $autoUpdateGoldenFiles;
final IsolateChannel<dynamic> channel = IsolateChannel<dynamic>.connectSend(
IsolateNameServer.lookupPortByName(name)!,
);
channel.pipe(RemoteListener.start(() => testMain));
}
void testMain() {
final String route = PlatformDispatcher.instance.defaultRouteName;
switch (route) {
''');
for (final MapEntry<String, String> kvp in testImports.entries) {
final String importName = kvp.value;
final String path = kvp.key;
final String? testConfigImport = testConfigPaths[importName];
if (testConfigImport != null) {
buffer.writeln(" case '$importName':");
buffer.writeln(" createChannelAndConnect('$path', route, () => $testConfigImport.testExecutable($importName.main));");
} else {
buffer.writeln(" case '$importName':");
buffer.writeln(" createChannelAndConnect('$path', route, $importName.main);");
}
}
buffer.write(r'''
}
}
void main([dynamic sendPort]) {
if (sendPort is SendPort) {
final ReceivePort receivePort = ReceivePort();
receivePort.listen((dynamic msg) {
switch (msg as List<dynamic>) {
case ['spawn', final SendPort port, final String entrypoint, final String route]:
spawn(port: port, entrypoint: entrypoint, route: route);
case ['close']:
receivePort.close();
}
});
sendPort.send(<Object>[receivePort.sendPort, packageTestArgs, testPaths]);
}
}
''');
childTestIsolateSpawnerSourceFile.writeAsStringSync(buffer.toString());
}
static void _generateRootTestIsolateSpawnerSourceFile({
required File childTestIsolateSpawnerSourceFile,
required File childTestIsolateSpawnerDillFile,
required File rootTestIsolateSpawnerSourceFile,
}) {
final StringBuffer buffer = StringBuffer();
buffer.writeln('''
import 'dart:async';
import 'dart:ffi';
import 'dart:io' show exit, exitCode; // flutter_ignore: dart_io_import
import 'dart:isolate';
import 'dart:ui';
import 'package:ffi/ffi.dart';
import 'package:stream_channel/isolate_channel.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports
import 'package:test_core/src/platform.dart'; // ignore: implementation_imports
@Native<Handle Function(Pointer<Utf8>)>(symbol: 'LoadLibraryFromKernel')
external Object _loadLibraryFromKernel(Pointer<Utf8> path);
@Native<Handle Function(Pointer<Utf8>, Pointer<Utf8>)>(symbol: 'LookupEntryPoint')
external Object _lookupEntryPoint(Pointer<Utf8> library, Pointer<Utf8> name);
late final List<String> packageTestArgs;
late final List<String> testPaths;
/// Runs on the main isolate.
Future<void> registerPluginAndRun() {
final SpawnPlugin platform = SpawnPlugin();
registerPlatformPlugin(
<Runtime>[Runtime.vm],
() {
return platform;
},
);
return test.main(<String>[...packageTestArgs, '--', ...testPaths]);
}
late final Isolate rootTestIsolate;
late final SendPort commandPort;
bool readyToRun = false;
final Completer<void> readyToRunSignal = Completer<void>();
Future<void> spawn({
required SendPort port,
String entrypoint = 'main',
String route = '/',
}) async {
if (!readyToRun) {
await readyToRunSignal.future;
}
commandPort.send(<Object>['spawn', port, entrypoint, route]);
}
void main() async {
final String route = PlatformDispatcher.instance.defaultRouteName;
if (route == '/') {
final ReceivePort port = ReceivePort();
port.listen((dynamic message) {
final [SendPort sendPort, List<String> args, List<String> paths] = message as List<dynamic>;
commandPort = sendPort;
packageTestArgs = args;
testPaths = paths;
readyToRun = true;
readyToRunSignal.complete();
});
rootTestIsolate = await Isolate.spawn(
_loadLibraryFromKernel(
r'${childTestIsolateSpawnerDillFile.absolute.path}'
.toNativeUtf8()) as void Function(SendPort),
port.sendPort,
);
await readyToRunSignal.future;
port.close(); // Not expecting anything else.
await registerPluginAndRun();
// The [test.main] call in [registerPluginAndRun] sets dart:io's [exitCode]
// global.
exit(exitCode);
} else {
(_lookupEntryPoint(
r'file://${childTestIsolateSpawnerSourceFile.absolute.uri.toFilePath(windows: false)}'
.toNativeUtf8(),
'testMain'.toNativeUtf8()) as void Function())();
}
}
''');
buffer.write(r'''
String pathToImport(String path) {
assert(path.endsWith('.dart'));
return path
.replaceRange(path.length - '.dart'.length, null, '')
.replaceAll('.', '_')
.replaceAll(':', '_')
.replaceAll('/', '_')
.replaceAll(r'\', '_');
}
class SpawnPlugin extends PlatformPlugin {
SpawnPlugin();
final Map<String, IsolateChannel<dynamic>> _channels = <String, IsolateChannel<dynamic>>{};
Future<void> launchIsolate(String path) async {
final String name = pathToImport(path);
final ReceivePort port = ReceivePort();
_channels[name] = IsolateChannel<dynamic>.connectReceive(port);
await spawn(port: port.sendPort, route: name);
}
@override
Future<void> close() async {
commandPort.send(<String>['close']);
}
''');
buffer.write('''
@override
Future<RunnerSuite> load(
String path,
SuitePlatform platform,
SuiteConfiguration suiteConfig,
Object message,
) async {
final String correctedPath = ${globals.platform.isWindows ? r'"/$path"' : 'path'};
await launchIsolate(correctedPath);
final StreamChannel<dynamic> channel = _channels[pathToImport(correctedPath)]!;
final RunnerSuiteController controller = deserializeSuite(correctedPath, platform,
suiteConfig, const PluginEnvironment(), channel, message);
return controller.suite;
}
}
''');
rootTestIsolateSpawnerSourceFile.writeAsStringSync(buffer.toString());
}
static Future<void> _compileFile({
required DebuggingOptions debuggingOptions,
required File packageConfigFile,
required PackageConfig packageConfig,
required File sourceFile,
required File outputDillFile,
required TestTimeRecorder? testTimeRecorder,
Uri? nativeAssetsYaml,
}) async {
globals.printTrace('Compiling ${sourceFile.absolute.uri}');
final Stopwatch compilerTime = Stopwatch()..start();
final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Compile);
final ResidentCompiler residentCompiler = ResidentCompiler(
globals.artifacts!.getArtifactPath(Artifact.flutterPatchedSdkPath),
artifacts: globals.artifacts!,
logger: globals.logger,
processManager: globals.processManager,
buildMode: debuggingOptions.buildInfo.mode,
trackWidgetCreation: debuggingOptions. buildInfo.trackWidgetCreation,
dartDefines: debuggingOptions.buildInfo.dartDefines,
packagesPath: packageConfigFile.path,
frontendServerStarterPath: debuggingOptions.buildInfo.frontendServerStarterPath,
extraFrontEndOptions: debuggingOptions.buildInfo.extraFrontEndOptions,
platform: globals.platform,
testCompilation: true,
fileSystem: globals.fs,
fileSystemRoots: debuggingOptions.buildInfo.fileSystemRoots,
fileSystemScheme: debuggingOptions.buildInfo.fileSystemScheme,
);
await residentCompiler.recompile(
sourceFile.absolute.uri,
null,
outputPath: outputDillFile.absolute.path,
packageConfig: packageConfig,
fs: globals.fs,
nativeAssetsYaml: nativeAssetsYaml,
);
residentCompiler.accept();
globals.printTrace('Compiling ${sourceFile.absolute.uri} took ${compilerTime.elapsedMilliseconds}ms');
testTimeRecorder?.stop(TestTimePhases.Compile, testTimeRecorderStopwatch!);
}
@override
Future<int> runTestsBySpawningLightweightEngines(
List<Uri> testFiles, {
required DebuggingOptions debuggingOptions,
List<String> names = const <String>[],
List<String> plainNames = const <String>[],
String? tags,
String? excludeTags,
bool machine = false,
bool updateGoldens = false,
required int? concurrency,
String? testAssetDirectory,
FlutterProject? flutterProject,
String? icudtlPath,
String? randomSeed,
String? reporter,
String? fileReporter,
String? timeout,
bool failFast = false,
bool runSkipped = false,
int? shardIndex,
int? totalShards,
TestTimeRecorder? testTimeRecorder,
TestCompilerNativeAssetsBuilder? nativeAssetsBuilder,
}) async {
assert(testFiles.length > 1);
final Directory buildDirectory = globals.fs.directory(globals.fs.path.join(
flutterProject!.directory.path,
getBuildDirectory(),
));
final Directory isolateSpawningTesterDirectory = buildDirectory.childDirectory(
'isolate_spawning_tester',
);
isolateSpawningTesterDirectory.createSync();
final File isolateSpawningTesterPackageConfigFile = isolateSpawningTesterDirectory
.childDirectory('.dart_tool')
.childFile(
'package_config.json',
);
isolateSpawningTesterPackageConfigFile.createSync(recursive: true);
await _generateIsolateSpawningTesterPackageConfig(
flutterProject: flutterProject,
isolateSpawningTesterPackageConfigFile: isolateSpawningTesterPackageConfigFile,
);
final PackageConfig isolateSpawningTesterPackageConfig = PackageConfig.parseBytes(
isolateSpawningTesterPackageConfigFile.readAsBytesSync(),
isolateSpawningTesterPackageConfigFile.uri,
);
final File childTestIsolateSpawnerSourceFile = isolateSpawningTesterDirectory.childFile(
'child_test_isolate_spawner.dart',
);
final File rootTestIsolateSpawnerSourceFile = isolateSpawningTesterDirectory.childFile(
'root_test_isolate_spawner.dart',
);
final File childTestIsolateSpawnerDillFile = isolateSpawningTesterDirectory.childFile(
'child_test_isolate_spawner.dill',
);
final File rootTestIsolateSpawnerDillFile = isolateSpawningTesterDirectory.childFile(
'root_test_isolate_spawner.dill',
);
// Compute the command-line arguments for package:test.
final List<String> packageTestArgs = <String>[
if (!globals.terminal.supportsColor)
'--no-color',
if (machine)
...<String>['-r', 'json']
else if (reporter != null)
...<String>['-r', reporter],
if (fileReporter != null)
'--file-reporter=$fileReporter',
if (timeout != null)
...<String>['--timeout', timeout],
if (concurrency != null)
'--concurrency=$concurrency',
for (final String name in names)
...<String>['--name', name],
for (final String plainName in plainNames)
...<String>['--plain-name', plainName],
if (randomSeed != null)
'--test-randomize-ordering-seed=$randomSeed',
if (tags != null)
...<String>['--tags', tags],
if (excludeTags != null)
...<String>['--exclude-tags', excludeTags],
if (failFast)
'--fail-fast',
if (runSkipped)
'--run-skipped',
if (totalShards != null)
'--total-shards=$totalShards',
if (shardIndex != null)
'--shard-index=$shardIndex',
'--chain-stack-traces',
];
_generateChildTestIsolateSpawnerSourceFile(
testFiles,
packageTestArgs: packageTestArgs,
autoUpdateGoldenFiles: updateGoldens,
childTestIsolateSpawnerSourceFile: childTestIsolateSpawnerSourceFile,
childTestIsolateSpawnerDillFile: childTestIsolateSpawnerDillFile,
);
_generateRootTestIsolateSpawnerSourceFile(
childTestIsolateSpawnerSourceFile: childTestIsolateSpawnerSourceFile,
childTestIsolateSpawnerDillFile: childTestIsolateSpawnerDillFile,
rootTestIsolateSpawnerSourceFile: rootTestIsolateSpawnerSourceFile,
);
final Uri? nativeAssetsYaml = await nativeAssetsBuilder?.build(
debuggingOptions.buildInfo,
);
await _compileFile(
debuggingOptions: debuggingOptions,
packageConfigFile: isolateSpawningTesterPackageConfigFile,
packageConfig: isolateSpawningTesterPackageConfig,
sourceFile: childTestIsolateSpawnerSourceFile,
outputDillFile: childTestIsolateSpawnerDillFile,
testTimeRecorder: testTimeRecorder,
nativeAssetsYaml: nativeAssetsYaml,
);
await _compileFile(
debuggingOptions: debuggingOptions,
packageConfigFile: isolateSpawningTesterPackageConfigFile,
packageConfig: isolateSpawningTesterPackageConfig,
sourceFile: rootTestIsolateSpawnerSourceFile,
outputDillFile: rootTestIsolateSpawnerDillFile,
testTimeRecorder: testTimeRecorder,
);
final List<String> command = <String>[
globals.artifacts!.getArtifactPath(Artifact.flutterTester),
'--disable-vm-service',
if (icudtlPath != null) '--icu-data-file-path=$icudtlPath',
'--enable-checked-mode',
'--verify-entry-points',
'--enable-software-rendering',
'--skia-deterministic-rendering',
if (debuggingOptions.enableDartProfiling)
'--enable-dart-profiling',
'--non-interactive',
'--use-test-fonts',
'--disable-asset-fonts',
'--packages=${debuggingOptions.buildInfo.packageConfigPath}',
if (testAssetDirectory != null)
'--flutter-assets-dir=$testAssetDirectory',
if (debuggingOptions.nullAssertions)
'--dart-flags=--null_assertions',
...debuggingOptions.dartEntrypointArgs,
rootTestIsolateSpawnerDillFile.absolute.path
];
// If the FLUTTER_TEST environment variable has been set, then pass it on
// for package:flutter_test to handle the value.
//
// If FLUTTER_TEST has not been set, assume from this context that this
// call was invoked by the command 'flutter test'.
final String flutterTest = globals.platform.environment.containsKey('FLUTTER_TEST')
? globals.platform.environment['FLUTTER_TEST']!
: 'true';
final Map<String, String> environment = <String, String>{
'FLUTTER_TEST': flutterTest,
'FONTCONFIG_FILE': FontConfigManager().fontConfigFile.path,
'APP_NAME': flutterProject.manifest.appName,
if (testAssetDirectory != null)
'UNIT_TEST_ASSETS': testAssetDirectory,
};
globals.logger.printTrace('Starting flutter_tester process with command=$command, environment=$environment');
final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Run);
final Process process = await globals.processManager.start(command, environment: environment);
globals.logger.printTrace('Started flutter_tester process at pid ${process.pid}');
for (final Stream<List<int>> stream in <Stream<List<int>>>[
process.stderr,
process.stdout,
]) {
stream
.transform<String>(utf8.decoder)
.listen(globals.stdio.stdoutWrite);
}
return process.exitCode.then((int exitCode) {
testTimeRecorder?.stop(TestTimePhases.Run, testTimeRecorderStopwatch!);
globals.logger.printTrace('flutter_tester process at pid ${process.pid} exited with code=$exitCode');
return exitCode;
});
}
}