[test] Add option to test harness to run VM tests under rr and save traces from crashes.

E.g., ./tools/test.py --rr --repeat=100

Change-Id: I2776b38daa6b14d1ca59800969a0ecfaeb71a0ee
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/167241
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Ryan Macnak
2020-10-13 21:59:54 +00:00
committed by commit-bot@chromium.org
parent 98a21f44d5
commit 79db8e0055
5 changed files with 90 additions and 2 deletions
+70
View File
@@ -10,6 +10,7 @@ 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.
@@ -531,6 +532,75 @@ class VMBatchCommand extends ProcessCommand implements VMCommand {
}
}
// 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 {
VMCommand originalCommand;
VMCommand wrappedCommand;
io.Directory recordingDir;
io.Directory savedDir;
RRCommand(this.originalCommand)
: super._("rr", index: originalCommand.index) {
final suffix = "/rr-trace-" + originalCommand.hashCode.toString();
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.arguments);
wrappedCommand = VMCommand(
executable, arguments, originalCommand.environmentOverrides,
index: originalCommand.index);
}
RRCommand indexedCopy(int index) =>
RRCommand(originalCommand.indexedCopy(index));
Future<CommandOutput> run(int timeout) 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).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 {
await recordingDir.delete(recursive: true);
}
return VMCommandOutput(this, output.exitCode, output.hasTimedOut,
output.stdout, output.stderr, output.time, output.pid);
}
String get reproductionCommand =>
wrappedCommand.reproductionCommand + " (rr replay " + savedDir.path + ")";
void _buildHashCode(HashCodeBuilder builder) {
originalCommand._buildHashCode(builder);
builder.add(42);
}
bool _equal(RRCommand other) =>
hashCode == other.hashCode &&
originalCommand._equal(other.originalCommand);
}
abstract class AdbCommand {
String get buildPath;
List<String> get extraLibraries;
@@ -32,6 +32,7 @@ class TestConfiguration {
this.batch,
this.batchDart2JS,
this.copyCoreDumps,
this.rr,
this.isVerbose,
this.listTests,
this.listStatusFiles,
@@ -82,6 +83,7 @@ class TestConfiguration {
final bool batch;
final bool batchDart2JS;
final bool copyCoreDumps;
final bool rr;
final bool fastTestsOnly;
final bool isVerbose;
final bool listTests;
+3
View File
@@ -293,6 +293,8 @@ settings.''',
'''If we see a crash that we did not expect, copy the core dumps to
"/tmp".''',
hide: true),
_Option.bool('rr', '''Run VM tests under rr and save traces from crashes''',
hide: true),
_Option(
'local_ip',
'''IP address the HTTP servers should listen on. This address is also
@@ -735,6 +737,7 @@ compiler.''',
batch: !(data["noBatch"] as bool),
batchDart2JS: data["dart2js_batch"] as bool,
copyCoreDumps: data["copy_coredumps"] as bool,
rr: data["rr"] as bool,
isVerbose: data["verbose"] as bool,
listTests: data["list"] as bool,
listStatusFiles: data["list_status_files"] as bool,
@@ -614,6 +614,8 @@ class CommandExecutorImpl implements CommandExecutor {
return RunningProcess(command, timeout,
configuration: globalConfiguration)
.run();
} else if (command is RRCommand) {
return command.run(timeout);
} else {
throw ArgumentError("Unknown command type ${command.runtimeType}.");
}
@@ -263,6 +263,9 @@ class DartVmRuntimeConfiguration extends RuntimeConfiguration {
if (_configuration.sanitizer != Sanitizer.none) {
multiplier *= 2;
}
if (_configuration.rr) {
multiplier *= 2;
}
return multiplier;
}
}
@@ -297,7 +300,11 @@ class StandaloneDartRuntimeConfiguration extends DartVmRuntimeConfiguration {
arguments.insertAll(0, config.arguments);
executable = config.executable;
}
return [VMCommand(executable, arguments, environmentOverrides)];
var command = VMCommand(executable, arguments, environmentOverrides);
if (_configuration.rr && !isCrashExpected) {
return [RRCommand(command)];
}
return [command];
}
}
@@ -326,7 +333,11 @@ class DartPrecompiledRuntimeConfiguration extends DartVmRuntimeConfiguration {
executable = config.executable;
}
return [VMCommand(executable, arguments, environmentOverrides)];
var command = VMCommand(executable, arguments, environmentOverrides);
if (_configuration.rr && !isCrashExpected) {
return [RRCommand(command)];
}
return [command];
}
}