| // Copyright (c) 2017, 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'; | |
| // We need to use the 'io' prefix here, otherwise io.exitCode will shadow | |
| // CommandOutput.exitCode in subclasses of CommandOutput. | |
| import 'dart:io' as io; | |
| import 'command_output.dart'; | |
| import 'configuration.dart'; | |
| import 'path.dart'; | |
| import 'test_case.dart'; | |
| import 'utils.dart'; | |
| /// A command executed as a step in a test case. | |
| abstract class Command { | |
| /// A descriptive name for this command. | |
| final String displayName; | |
| /// When cloning a command object to run it multiple times, we give | |
| /// the different copies distinct values for index. | |
| final int index; | |
| /// Number of times this command *can* be retried. | |
| int get maxNumRetries => 2; | |
| /// Reproduction command. | |
| String get reproductionCommand; | |
| /// We compute the Command.hashCode lazily and cache it here, since it might | |
| /// be expensive to compute (and hashCode is called often). | |
| int? _cachedHashCode; | |
| Command._(this.displayName, {this.index = 0}); | |
| /// A virtual clone method for a member of the Command hierarchy. | |
| /// Two clones with the same index will be equal, with different indices | |
| /// will be distinct. Used to run tests multiple times, since identical | |
| /// commands are only run once by the dependency graph scheduler. | |
| Command indexedCopy(int index); | |
| CommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int pid = 0, | |
| ]) => CommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| pid, | |
| ); | |
| @override | |
| int get hashCode { | |
| if (_cachedHashCode == null) { | |
| var builder = HashCodeBuilder(); | |
| _buildHashCode(builder); | |
| _cachedHashCode = builder.value; | |
| } | |
| return _cachedHashCode!; | |
| } | |
| @override | |
| operator ==(Object other) => | |
| identical(this, other) || | |
| (runtimeType == other.runtimeType && _equal(other as Command)); | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| builder.add(displayName); | |
| builder.add(index); | |
| } | |
| bool _equal(covariant Command other) => | |
| hashCode == other.hashCode && | |
| displayName == other.displayName && | |
| index == other.index; | |
| @override | |
| String toString() => reproductionCommand; | |
| bool get outputIsUpToDate => false; | |
| } | |
| class ProcessCommand extends Command { | |
| /// Path to the executable of this command. | |
| String executable; | |
| /// Command line arguments to the executable. | |
| final List<String> arguments; | |
| /// Environment for the command. | |
| final Map<String, String> environmentOverrides; | |
| /// Working directory for the command. | |
| final String? workingDirectory; | |
| ProcessCommand( | |
| super.displayName, | |
| this.executable, | |
| this.arguments, [ | |
| this.environmentOverrides = const {}, | |
| this.workingDirectory, | |
| int index = 0, | |
| ]) : super._(index: index) { | |
| if (io.Platform.operatingSystem == 'windows') { | |
| // Windows can't handle the first command if it is a .bat file or the like | |
| // with the slashes going the other direction. | |
| // NOTE: Issue 1306 | |
| executable = executable.replaceAll('/', '\\'); | |
| } | |
| } | |
| @override | |
| ProcessCommand indexedCopy(int index) { | |
| return ProcessCommand( | |
| displayName, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| workingDirectory, | |
| index, | |
| ); | |
| } | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.addJson(executable); | |
| builder.addJson(workingDirectory); | |
| builder.addJson(arguments); | |
| builder.addJson(environmentOverrides); | |
| } | |
| @override | |
| bool _equal(ProcessCommand other) => | |
| super._equal(other) && | |
| executable == other.executable && | |
| deepJsonCompare(arguments, other.arguments) && | |
| workingDirectory == other.workingDirectory && | |
| deepJsonCompare(environmentOverrides, other.environmentOverrides); | |
| @override | |
| String get reproductionCommand { | |
| var env = StringBuffer(); | |
| environmentOverrides.forEach( | |
| (key, value) => (io.Platform.operatingSystem == 'windows') | |
| ? env.write('set $key=${escapeCommandLineArgument(value)} & ') | |
| : env.write('$key=${escapeCommandLineArgument(value)} '), | |
| ); | |
| var command = [ | |
| executable, | |
| ...nonBatchArguments, | |
| ...arguments, | |
| ].map(escapeCommandLineArgument).join(' '); | |
| if (workingDirectory != null) { | |
| command = "$command (working directory: $workingDirectory)"; | |
| } | |
| return "$env$command"; | |
| } | |
| @override | |
| bool get outputIsUpToDate => false; | |
| /// Additional arguments to prepend before [arguments] when running the | |
| /// process in batch mode. | |
| List<String> get batchArguments => const []; | |
| /// Additional arguments to prepend before [arguments] when running the | |
| /// process in non-batch mode. | |
| List<String> get nonBatchArguments => const []; | |
| } | |
| class CompilationCommand extends ProcessCommand { | |
| /// The primary output file that will be created by this command. | |
| final String outputFile; | |
| /// If true, then the compilation is run even if the input files are older | |
| /// than the output file. | |
| final bool _alwaysCompile; | |
| final List<Uri> _bootstrapDependencies; | |
| CompilationCommand( | |
| String displayName, | |
| this.outputFile, | |
| this._bootstrapDependencies, | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, { | |
| required this._alwaysCompile, | |
| String? workingDirectory, | |
| int index = 0, | |
| }) : super( | |
| displayName, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| workingDirectory, | |
| index, | |
| ); | |
| @override | |
| CompilationCommand indexedCopy(int index) => CompilationCommand( | |
| displayName, | |
| outputFile, | |
| _bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| alwaysCompile: _alwaysCompile, | |
| workingDirectory: workingDirectory, | |
| index: index, | |
| ); | |
| @override | |
| CommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int pid = 0, | |
| ]) { | |
| if (displayName == 'precompiler' || | |
| displayName == 'app_jit' || | |
| displayName == 'dart2bytecode' || | |
| displayName == 'modaot') { | |
| return VMCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| pid, | |
| ); | |
| } else if (displayName == 'dart2wasm') { | |
| return Dart2WasmCompilerCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| } | |
| return CompilationCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| } | |
| @override | |
| bool get outputIsUpToDate { | |
| if (_alwaysCompile) return false; | |
| var file = io.File(Path("$outputFile.deps").toNativePath()); | |
| if (!file.existsSync()) return false; | |
| var lines = file.readAsLinesSync(); | |
| var dependencies = <Uri>[]; | |
| for (var line in lines) { | |
| line = line.trim(); | |
| if (line.isNotEmpty) { | |
| dependencies.add(Uri.parse(line)); | |
| } | |
| } | |
| dependencies.addAll(_bootstrapDependencies); | |
| var jsOutputLastModified = TestUtils.lastModifiedCache.getLastModified( | |
| Uri(scheme: 'file', path: outputFile), | |
| ); | |
| if (jsOutputLastModified == null) return false; | |
| for (var dependency in dependencies) { | |
| var dependencyLastModified = TestUtils.lastModifiedCache.getLastModified( | |
| dependency, | |
| ); | |
| if (dependencyLastModified == null || | |
| dependencyLastModified.isAfter(jsOutputLastModified)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| @override | |
| List<String> get batchArguments { | |
| return [...arguments.where((arg) => arg.startsWith('--enable-experiment'))]; | |
| } | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.addJson(outputFile); | |
| builder.addJson(_alwaysCompile); | |
| builder.addJson(_bootstrapDependencies); | |
| } | |
| @override | |
| bool _equal(CompilationCommand other) => | |
| super._equal(other) && | |
| outputFile == other.outputFile && | |
| _alwaysCompile == other._alwaysCompile && | |
| deepJsonCompare(_bootstrapDependencies, other._bootstrapDependencies); | |
| } | |
| class Dart2jsCompilationCommand extends CompilationCommand { | |
| final bool useSdk; | |
| Dart2jsCompilationCommand( | |
| String outputFile, | |
| List<Uri> bootstrapDependencies, | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, { | |
| required this.useSdk, | |
| required super.alwaysCompile, | |
| super.workingDirectory, | |
| super.index = 0, | |
| }) : super( | |
| "dart2js", | |
| outputFile, | |
| bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| ); | |
| @override | |
| Dart2jsCompilationCommand indexedCopy(int index) => Dart2jsCompilationCommand( | |
| outputFile, | |
| _bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| useSdk: useSdk, | |
| alwaysCompile: _alwaysCompile, | |
| workingDirectory: workingDirectory, | |
| index: index, | |
| ); | |
| @override | |
| CommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) { | |
| return Dart2jsCompilerCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| } | |
| @override | |
| List<String> get batchArguments { | |
| return <String>[ | |
| if (useSdk) ...['compile', 'js'], | |
| ...super.batchArguments, | |
| ]; | |
| } | |
| @override | |
| List<String> get nonBatchArguments { | |
| return <String>[ | |
| if (useSdk) ...['compile', 'js'], | |
| ...super.nonBatchArguments, | |
| ]; | |
| } | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.addJson(useSdk); | |
| } | |
| @override | |
| bool _equal(Dart2jsCompilationCommand other) { | |
| return super._equal(other) && useSdk == other.useSdk; | |
| } | |
| } | |
| class DevCompilerCompilationCommand extends CompilationCommand { | |
| final String compilerPath; | |
| final bool enableHostAsserts; | |
| DevCompilerCompilationCommand( | |
| String outputFile, | |
| List<Uri> bootstrapDependencies, | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, { | |
| required this.compilerPath, | |
| required super.alwaysCompile, | |
| required this.enableHostAsserts, | |
| super.workingDirectory, | |
| super.index = 0, | |
| }) : super( | |
| "ddc", | |
| outputFile, | |
| bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| ); | |
| @override | |
| DevCompilerCompilationCommand indexedCopy(int index) => | |
| DevCompilerCompilationCommand( | |
| outputFile, | |
| _bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| compilerPath: compilerPath, | |
| alwaysCompile: _alwaysCompile, | |
| enableHostAsserts: enableHostAsserts, | |
| workingDirectory: workingDirectory, | |
| index: index, | |
| ); | |
| @override | |
| CommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int pid = 0, | |
| ]) { | |
| return DevCompilerCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| pid, | |
| ); | |
| } | |
| @override | |
| List<String> get batchArguments { | |
| return <String>[ | |
| if (enableHostAsserts) '--enable-asserts', | |
| compilerPath, | |
| ...super.batchArguments, | |
| ]; | |
| } | |
| @override | |
| List<String> get nonBatchArguments { | |
| return <String>[ | |
| if (enableHostAsserts) '--enable-asserts', | |
| compilerPath, | |
| ...super.nonBatchArguments, | |
| ]; | |
| } | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.addJson(compilerPath); | |
| } | |
| @override | |
| bool _equal(DevCompilerCompilationCommand other) { | |
| return super._equal(other) && compilerPath == other.compilerPath; | |
| } | |
| } | |
| class FastaCompilationCommand extends CompilationCommand { | |
| final Uri _compilerLocation; | |
| FastaCompilationCommand( | |
| this._compilerLocation, | |
| String outputFile, | |
| List<Uri> bootstrapDependencies, | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, | |
| String? workingDirectory, { | |
| super.index = 0, | |
| }) : super( | |
| "fasta", | |
| outputFile, | |
| bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| alwaysCompile: true, | |
| workingDirectory: workingDirectory, | |
| ); | |
| @override | |
| FastaCompilationCommand indexedCopy(int index) => FastaCompilationCommand( | |
| _compilerLocation, | |
| outputFile, | |
| _bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| workingDirectory, | |
| index: index, | |
| ); | |
| @override | |
| FastaCommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) => FastaCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| @override | |
| List<String> get batchArguments { | |
| return <String>[ | |
| ...super.batchArguments, | |
| '--enable-asserts', | |
| _compilerLocation.resolve("batch.dart").toFilePath(), | |
| ]; | |
| } | |
| @override | |
| String get reproductionCommand { | |
| String relativizeAndEscape(String argument) { | |
| if (workingDirectory != null) { | |
| argument = argument.replaceAll( | |
| workingDirectory!, | |
| Uri.directory(".").toFilePath(), | |
| ); | |
| } | |
| return escapeCommandLineArgument(argument); | |
| } | |
| var buffer = StringBuffer(); | |
| if (workingDirectory != null && !io.Platform.isWindows) { | |
| buffer.write("(cd "); | |
| buffer.write(escapeCommandLineArgument(workingDirectory!)); | |
| buffer.write(" ; "); | |
| } | |
| environmentOverrides.forEach((key, value) { | |
| if (io.Platform.isWindows) { | |
| buffer.write("set "); | |
| } | |
| buffer.write(key); | |
| buffer.write("="); | |
| buffer.write(relativizeAndEscape(value)); | |
| if (io.Platform.isWindows) { | |
| buffer.write(" &"); | |
| } | |
| buffer.write(" "); | |
| }); | |
| buffer.writeAll( | |
| [ | |
| executable, | |
| _compilerLocation.toFilePath(), | |
| ...arguments, | |
| ].map(relativizeAndEscape), | |
| " ", | |
| ); | |
| if (workingDirectory != null) { | |
| if (io.Platform.isWindows) { | |
| buffer.write(" (working directory: $workingDirectory)"); | |
| } else { | |
| buffer.write(" )"); | |
| } | |
| } | |
| return "$buffer"; | |
| } | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.addJson(_compilerLocation); | |
| } | |
| @override | |
| bool _equal(FastaCompilationCommand other) { | |
| return super._equal(other) && _compilerLocation == other._compilerLocation; | |
| } | |
| } | |
| class VMKernelCompilationCommand extends CompilationCommand { | |
| VMKernelCompilationCommand( | |
| String outputFile, | |
| List<Uri> bootstrapDependencies, | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, { | |
| required super.alwaysCompile, | |
| super.index = 0, | |
| }) : super( | |
| 'vm_compile_to_kernel', | |
| outputFile, | |
| bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| ); | |
| @override | |
| VMKernelCompilationCommand indexedCopy(int index) => | |
| VMKernelCompilationCommand( | |
| outputFile, | |
| _bootstrapDependencies, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| alwaysCompile: _alwaysCompile, | |
| index: index, | |
| ); | |
| @override | |
| VMKernelCompilationCommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) => VMKernelCompilationCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| @override | |
| int get maxNumRetries => 1; | |
| } | |
| class BrowserTestCommand extends Command { | |
| Runtime get browser => configuration.runtime; | |
| final String url; | |
| final TestConfiguration configuration; | |
| BrowserTestCommand(this.url, this.configuration, {super.index = 0}) | |
| : super._(configuration.runtime.name); | |
| @override | |
| BrowserTestCommand indexedCopy(int index) => | |
| BrowserTestCommand(url, configuration, index: index); | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.addJson(browser.name); | |
| builder.addJson(url); | |
| builder.add(configuration); | |
| } | |
| @override | |
| bool _equal(BrowserTestCommand other) => | |
| super._equal(other) && | |
| browser == other.browser && | |
| url == other.url && | |
| identical(configuration, other.configuration); | |
| @override | |
| String get reproductionCommand { | |
| var parts = [ | |
| io.Platform.resolvedExecutable, | |
| 'pkg/test_runner/bin/launch_browser.dart', | |
| browser.name, | |
| url, | |
| ]; | |
| return parts.map(escapeCommandLineArgument).join(' '); | |
| } | |
| @override | |
| int get maxNumRetries => 4; | |
| } | |
| class AnalysisCommand extends ProcessCommand { | |
| final List<String> commonAnalyzerCliArguments; | |
| AnalysisCommand( | |
| String executable, | |
| List<String> arguments, | |
| this.commonAnalyzerCliArguments, | |
| Map<String, String> environmentOverrides, { | |
| int index = 0, | |
| }) : super( | |
| 'dart2analyzer', | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| null, | |
| index, | |
| ); | |
| @override | |
| AnalysisCommand indexedCopy(int index) => AnalysisCommand( | |
| executable, | |
| arguments, | |
| commonAnalyzerCliArguments, | |
| environmentOverrides, | |
| index: index, | |
| ); | |
| @override | |
| CommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) => AnalysisCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| @override | |
| List<String> get batchArguments => commonAnalyzerCliArguments; | |
| @override | |
| List<String> get nonBatchArguments => commonAnalyzerCliArguments; | |
| @override | |
| bool _equal(covariant ProcessCommand other) { | |
| return other is AnalysisCommand && | |
| super._equal(other) && | |
| deepJsonCompare( | |
| commonAnalyzerCliArguments, | |
| other.commonAnalyzerCliArguments, | |
| ); | |
| } | |
| } | |
| class SpecParseCommand extends ProcessCommand { | |
| SpecParseCommand( | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, { | |
| int index = 0, | |
| }) : super( | |
| 'spec_parser', | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| null, | |
| index, | |
| ); | |
| @override | |
| SpecParseCommand indexedCopy(int index) => SpecParseCommand( | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| index: index, | |
| ); | |
| @override | |
| SpecParseCommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) => SpecParseCommandOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| compilationSkipped, | |
| ); | |
| } | |
| class VMCommand extends ProcessCommand { | |
| VMCommand( | |
| String executable, | |
| List<String> arguments, | |
| Map<String, String> environmentOverrides, { | |
| int index = 0, | |
| }) : super('vm', executable, arguments, environmentOverrides, null, index); | |
| @override | |
| VMCommand indexedCopy(int index) => | |
| VMCommand(executable, arguments, environmentOverrides, index: index); | |
| @override | |
| CommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int pid = 0, | |
| ]) => VMCommandOutput(this, exitCode, timedOut, stdout, stderr, time, pid); | |
| } | |
| // Run a VM test under RR, and copy the trace if it crashes. Using a helper | |
| // script like the precompiler does not work because the RR traces are large | |
| // and we must diligently erase them for non-crashes even if the test times | |
| // out and would be killed by the harness, so the copying and cleanup logic | |
| // must be in the harness. | |
| class RRCommand extends Command { | |
| ProcessCommand originalCommand; | |
| late ProcessCommand wrappedCommand; | |
| late io.Directory recordingDir; | |
| late io.Directory savedDir; | |
| RRCommand(this.originalCommand) | |
| : super._(originalCommand.displayName, index: originalCommand.index) { | |
| final suffix = "/rr-trace-${originalCommand.hashCode}"; | |
| recordingDir = io.Directory(io.Directory.systemTemp.path + suffix); | |
| savedDir = io.Directory("out$suffix"); | |
| final executable = "rr"; | |
| final arguments = <String>[ | |
| "record", | |
| "--chaos", | |
| "--output-trace-dir=${recordingDir.path}", | |
| ]; | |
| arguments.add(originalCommand.executable); | |
| arguments.addAll(originalCommand.nonBatchArguments); | |
| arguments.addAll(originalCommand.arguments); | |
| wrappedCommand = ProcessCommand( | |
| originalCommand.displayName, | |
| executable, | |
| arguments, | |
| originalCommand.environmentOverrides, | |
| originalCommand.workingDirectory, | |
| originalCommand.index, | |
| ); | |
| } | |
| @override | |
| RRCommand indexedCopy(int index) => | |
| RRCommand(originalCommand.indexedCopy(index)); | |
| Future<CommandOutput> run( | |
| int timeout, | |
| TestConfiguration configuration, | |
| ) async { | |
| // rr will fail if the output trace directory already exists. Delete any | |
| // that might be leftover from interrupting the harness. | |
| if (await recordingDir.exists()) { | |
| await recordingDir.delete(recursive: true); | |
| } | |
| final output = await RunningProcess( | |
| wrappedCommand, | |
| timeout, | |
| configuration: configuration, | |
| ).run(); | |
| if (output.hasCrashed) { | |
| if (await savedDir.exists()) { | |
| await savedDir.delete(recursive: true); | |
| } | |
| await recordingDir.rename(savedDir.path); | |
| await io.File( | |
| "${savedDir.path}/command.txt", | |
| ).writeAsString(wrappedCommand.reproductionCommand); | |
| await io.File("${savedDir.path}/stdout.txt").writeAsBytes(output.stdout); | |
| await io.File("${savedDir.path}/stderr.txt").writeAsBytes(output.stderr); | |
| } else if (await recordingDir.exists()) { | |
| await recordingDir.delete(recursive: true); | |
| } | |
| final compilationSkipped = false; | |
| switch (displayName) { | |
| case 'app_jit': | |
| case 'precompiler': | |
| case 'run_vm_unittest': | |
| case 'vm': | |
| return VMCommandOutput( | |
| this, | |
| output.exitCode, | |
| output.hasTimedOut, | |
| output.stdout, | |
| output.stderr, | |
| output.time, | |
| output.pid, | |
| ); | |
| case 'dart2wasm': | |
| return Dart2WasmCompilerCommandOutput( | |
| this, | |
| output.exitCode, | |
| output.hasTimedOut, | |
| output.stdout, | |
| output.stderr, | |
| output.time, | |
| compilationSkipped, | |
| ); | |
| case 'dart2js': | |
| return Dart2jsCompilerCommandOutput( | |
| this, | |
| output.exitCode, | |
| output.hasTimedOut, | |
| output.stdout, | |
| output.stderr, | |
| output.time, | |
| compilationSkipped, | |
| ); | |
| case 'ddc': | |
| return DevCompilerCommandOutput( | |
| this, | |
| output.exitCode, | |
| output.hasTimedOut, | |
| output.stdout, | |
| output.stderr, | |
| output.time, | |
| compilationSkipped, | |
| output.pid, | |
| ); | |
| } | |
| throw "Don't know how to interpret output for $displayName"; | |
| } | |
| @override | |
| String get reproductionCommand => | |
| "${wrappedCommand.reproductionCommand} (rr replay ${savedDir.path})"; | |
| @override | |
| void _buildHashCode(HashCodeBuilder builder) { | |
| originalCommand._buildHashCode(builder); | |
| builder.add(42); | |
| } | |
| @override | |
| bool _equal(RRCommand other) => | |
| hashCode == other.hashCode && | |
| originalCommand._equal(other.originalCommand); | |
| } | |
| abstract class AdbCommand { | |
| String get buildPath; | |
| List<String> get extraLibraries; | |
| } | |
| class AdbPrecompilationCommand extends Command implements AdbCommand { | |
| @override | |
| final String buildPath; // Path to the output directory of the build. | |
| final String processTestFilename; | |
| final String abstractSocketTestFilename; | |
| final String precompiledTestDirectory; | |
| final List<String> arguments; | |
| final bool useElf; | |
| @override | |
| final List<String> extraLibraries; | |
| AdbPrecompilationCommand( | |
| this.buildPath, | |
| this.processTestFilename, | |
| this.abstractSocketTestFilename, | |
| this.precompiledTestDirectory, | |
| this.arguments, | |
| this.useElf, | |
| this.extraLibraries, { | |
| super.index = 0, | |
| }) : super._("adb_precompilation"); | |
| @override | |
| AdbPrecompilationCommand indexedCopy(int index) => AdbPrecompilationCommand( | |
| buildPath, | |
| processTestFilename, | |
| abstractSocketTestFilename, | |
| precompiledTestDirectory, | |
| arguments, | |
| useElf, | |
| extraLibraries, | |
| index: index, | |
| ); | |
| @override | |
| VMCommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int pid = 0, | |
| ]) => VMCommandOutput(this, exitCode, timedOut, stdout, stderr, time, pid); | |
| @override | |
| _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.add(buildPath); | |
| builder.add(precompiledTestDirectory); | |
| builder.add(arguments); | |
| builder.add(useElf); | |
| extraLibraries.forEach(builder.add); | |
| } | |
| @override | |
| bool _equal(AdbPrecompilationCommand other) => | |
| super._equal(other) && | |
| buildPath == other.buildPath && | |
| useElf == other.useElf && | |
| arguments == other.arguments && | |
| precompiledTestDirectory == other.precompiledTestDirectory && | |
| deepJsonCompare(extraLibraries, other.extraLibraries); | |
| @override | |
| String toString() => | |
| 'Steps to push precompiled runner and precompiled code ' | |
| 'to an attached device. Uses (and requires) adb.'; | |
| @override | |
| String get reproductionCommand => throw UnimplementedError(); | |
| } | |
| class AdbDartkCommand extends Command implements AdbCommand { | |
| @override | |
| final String buildPath; | |
| final String processTestFilename; | |
| final String abstractSocketTestFilename; | |
| final String kernelFile; | |
| final List<String> arguments; | |
| @override | |
| final List<String> extraLibraries; | |
| AdbDartkCommand( | |
| this.buildPath, | |
| this.processTestFilename, | |
| this.abstractSocketTestFilename, | |
| this.kernelFile, | |
| this.arguments, | |
| this.extraLibraries, { | |
| super.index = 0, | |
| }) : super._("adb_precompilation"); | |
| @override | |
| AdbDartkCommand indexedCopy(int index) => AdbDartkCommand( | |
| buildPath, | |
| processTestFilename, | |
| abstractSocketTestFilename, | |
| kernelFile, | |
| arguments, | |
| extraLibraries, | |
| index: index, | |
| ); | |
| @override | |
| _buildHashCode(HashCodeBuilder builder) { | |
| super._buildHashCode(builder); | |
| builder.add(buildPath); | |
| builder.add(kernelFile); | |
| builder.add(arguments); | |
| builder.add(extraLibraries); | |
| } | |
| @override | |
| bool _equal(AdbDartkCommand other) => | |
| super._equal(other) && | |
| buildPath == other.buildPath && | |
| arguments == other.arguments && | |
| extraLibraries == other.extraLibraries && | |
| kernelFile == other.kernelFile; | |
| @override | |
| VMCommandOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int pid = 0, | |
| ]) => VMCommandOutput(this, exitCode, timedOut, stdout, stderr, time, pid); | |
| @override | |
| String toString() => | |
| 'Steps to push Dart VM and Dill file ' | |
| 'to an attached device. Uses (and requires) adb.'; | |
| @override | |
| String get reproductionCommand => throw UnimplementedError(); | |
| } | |
| class JSCommandLineCommand extends ProcessCommand { | |
| JSCommandLineCommand( | |
| String displayName, | |
| String executable, | |
| List<String> arguments, [ | |
| Map<String, String> environmentOverrides = const {}, | |
| int index = 0, | |
| ]) : super( | |
| displayName, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| null, | |
| index, | |
| ); | |
| @override | |
| JSCommandLineCommand indexedCopy(int index) => JSCommandLineCommand( | |
| displayName, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| index, | |
| ); | |
| @override | |
| JSCommandLineOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) => JSCommandLineOutput(this, exitCode, timedOut, stdout, stderr, time); | |
| } | |
| class Dart2WasmCommandLineCommand extends ProcessCommand { | |
| Dart2WasmCommandLineCommand( | |
| String displayName, | |
| String executable, | |
| List<String> arguments, [ | |
| Map<String, String> environmentOverrides = const {}, | |
| int index = 0, | |
| ]) : super( | |
| displayName, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| null, | |
| index, | |
| ); | |
| @override | |
| Dart2WasmCommandLineCommand indexedCopy(int index) => | |
| Dart2WasmCommandLineCommand( | |
| displayName, | |
| executable, | |
| arguments, | |
| environmentOverrides, | |
| index, | |
| ); | |
| @override | |
| Dart2WasmCommandLineOutput createOutput( | |
| int exitCode, | |
| bool timedOut, | |
| List<int> stdout, | |
| List<int> stderr, | |
| Duration time, | |
| bool compilationSkipped, [ | |
| int? pid = 0, | |
| ]) => Dart2WasmCommandLineOutput( | |
| this, | |
| exitCode, | |
| timedOut, | |
| stdout, | |
| stderr, | |
| time, | |
| ); | |
| } | |
| /// [ScriptCommand]s are executed by dart code. | |
| abstract class ScriptCommand extends Command { | |
| ScriptCommand._(super.displayName) : super._(); | |
| Future<ScriptCommandOutput> run(); | |
| } |