// 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 '../artifacts.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../device.dart'; import '../globals.dart' as globals; import '../project.dart'; import '../web/chrome.dart'; import '../web/memory_fs.dart'; import 'flutter_platform.dart' as loader; import 'flutter_web_platform.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 ipv6 = 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 runSkipped = false, int? shardIndex, int? totalShards, Device? integrationTestDevice, String? integrationTestUserIdentifier, TestTimeRecorder? testTimeRecorder, }); } 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 ipv6 = 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 runSkipped = false, int? shardIndex, int? totalShards, Device? integrationTestDevice, String? integrationTestUserIdentifier, TestTimeRecorder? testTimeRecorder, }) 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 (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, ); 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, 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, ); }, ); await testWrapper.main(testArgs); return exitCode; } testArgs ..add('--') ..addAll(testFiles.map((Uri uri) => uri.toString())); final InternetAddressType serverType = 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, ); 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(); } } }