f8b363a335
Change-Id: I24016d9a827e0506b2d5a6b6eed3356afaf90dbd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500602 Reviewed-by: Bob Nystrom <rnystrom@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
1271 lines
31 KiB
Dart
1271 lines
31 KiB
Dart
// 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;
|
|
}
|
|
|
|
/// This is just a Pair(String, Map) class with hashCode and operator ==
|
|
class AddFlagsKey {
|
|
final String flags;
|
|
final Map env;
|
|
AddFlagsKey(this.flags, this.env);
|
|
// Just use object identity for environment map
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
other is AddFlagsKey && flags == other.flags && env == other.env;
|
|
@override
|
|
int get hashCode => flags.hashCode ^ env.hashCode;
|
|
}
|
|
|
|
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 CompareAnalyzerCfeCommand extends ProcessCommand {
|
|
CompareAnalyzerCfeCommand(
|
|
String executable,
|
|
List<String> arguments,
|
|
Map<String, String> environmentOverrides, {
|
|
int index = 0,
|
|
}) : super(
|
|
'compare_analyzer_cfe',
|
|
executable,
|
|
arguments,
|
|
environmentOverrides,
|
|
null,
|
|
index,
|
|
);
|
|
|
|
@override
|
|
CompareAnalyzerCfeCommand indexedCopy(int index) => CompareAnalyzerCfeCommand(
|
|
executable,
|
|
arguments,
|
|
environmentOverrides,
|
|
index: index,
|
|
);
|
|
|
|
@override
|
|
CompareAnalyzerCfeCommandOutput createOutput(
|
|
int exitCode,
|
|
bool timedOut,
|
|
List<int> stdout,
|
|
List<int> stderr,
|
|
Duration time,
|
|
bool compilationSkipped, [
|
|
int? pid = 0,
|
|
]) => CompareAnalyzerCfeCommandOutput(
|
|
this,
|
|
exitCode,
|
|
timedOut,
|
|
stdout,
|
|
stderr,
|
|
time,
|
|
compilationSkipped,
|
|
);
|
|
}
|
|
|
|
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();
|
|
}
|