blob: 7e8bb024eb6cf2d9e70dd16255fe23ece43302a3 [file] [log] [blame] [edit]
// Copyright (c) 2021, 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';
import 'dart:convert';
import 'dart:io';
import 'dart:io' as io;
import 'package:args/args.dart';
import 'package:path/path.dart' as path;
import 'package:test_runner/src/options.dart';
import '../vm/dart/snapshot_test_helper.dart';
import '../../tools/dartfuzz/flag_fuzzer.dart';
int crashCounter = 0;
void forwardStream(Stream<List<int>> input, IOSink output) {
// Print the information line-by-line.
input.transform(utf8.decoder).transform(const LineSplitter()).listen((
String line,
) {
output.writeln(line);
});
}
class PotentialCrash {
final String test;
final int pid;
final List<String> binaries;
PotentialCrash(this.test, this.pid, this.binaries);
}
Future<bool> run(
String executable,
List<String> args,
List<PotentialCrash> crashes,
) async {
print('\n\nRunning "$executable ${args.join(' ')}"');
final sw = Stopwatch()..start();
final Process process = await Process.start(
executable,
args,
environment: sanitizerEnvironmentVariables,
);
forwardStream(process.stdout, stdout);
forwardStream(process.stderr, stderr);
final int exitCode = await process.exitCode;
print('Completed in ${sw.elapsed}');
if (exitCode != 0) {
// Ignore normal exceptions and compile-time errors for the purpose of
// crashdump reporting.
if (exitCode != 255 && exitCode != 254) {
final crashNr = crashCounter++;
print('=> Running "$executable ${args.join(' ')}" failed with $exitCode');
print('=> Possible crash $crashNr (pid: ${process.pid})');
crashes.add(PotentialCrash('crash-$crashNr', process.pid, [executable]));
}
io.exitCode = 255; // Make this shard fail.
return false;
}
return true;
}
abstract class TestRunner {
Future runTest(List<PotentialCrash> crashes);
}
class JitTestRunner extends TestRunner {
final String buildDir;
final List<String> arguments;
JitTestRunner(this.buildDir, this.arguments);
Future runTest(List<PotentialCrash> crashes) async {
await run('$buildDir/dart', arguments, crashes);
}
}
class AotTestRunner extends TestRunner {
final String buildDir;
final List<String> arguments;
final List<String> aotArguments;
AotTestRunner(this.buildDir, this.arguments, this.aotArguments);
Future runTest(List<PotentialCrash> crashes) async {
await withTempDir((String dir) async {
final elfFile = path.join(dir, 'app.elf');
if (await run('$buildDir/gen_snapshot', [
'--snapshot-kind=app-aot-elf',
'--elf=$elfFile',
...arguments,
], crashes)) {
await run('$buildDir/dartaotruntime', [
...aotArguments,
elfFile,
], crashes);
}
});
}
}
// Produces a name that tools/utils.py:BaseCoredumpArchiver supports.
String getArchiveName(String binary) {
final parts = binary.split(Platform.pathSeparator);
late String mode;
late String arch;
final buildDir = parts[1];
for (final prefix in ['Release', 'Debug', 'Product']) {
if (buildDir.startsWith(prefix)) {
mode = prefix.toLowerCase();
arch = buildDir.substring(prefix.length);
}
}
final name = parts.skip(2).join('__');
return 'binary.${mode}_${arch}_${name}';
}
void writeUnexpectedCrashesFile(List<PotentialCrash> crashes) {
// The format of this file is:
//
// test-name,pid,binary-file1,binary-file2,...
//
const unexpectedCrashesFile = 'unexpected-crashes';
final buffer = StringBuffer();
final Set<String> archivedBinaries = {};
for (final crash in crashes) {
buffer.write('${crash.test},${crash.pid}');
for (final binary in crash.binaries) {
final archivedName = getArchiveName(binary);
buffer.write(',$archivedName');
if (!archivedBinaries.contains(archivedName)) {
File(binary).copySync(archivedName);
archivedBinaries.add(archivedName);
}
}
buffer.writeln();
}
File(unexpectedCrashesFile).writeAsStringSync(buffer.toString());
}
Iterable<String> filterSlowFlags(Iterable<String> flags) =>
flags.where((flag) => !flag.startsWith('--gc_at_throw'));
const int tsanShards = 64;
late final List<TestRunner> configurations;
main(List<String> arguments) async {
final parser = ArgParser()
..addOption('shards', help: 'number of shards used', defaultsTo: '1')
..addOption('shard', help: 'shard id', defaultsTo: '1')
..addOption(
'output-directory',
help: 'unused parameter to make sharding infra work',
defaultsTo: '',
)
..addFlag(
'copy-coredumps',
help: 'whether to copy binaries for coredumps',
defaultsTo: false,
)
..addOption(
'previous-results',
help: 'An earlier results.json for balancing tests across shards.',
)
..addOption('arch', help: 'architecture to be tested', defaultsTo: 'X64');
final options = parser.parse(arguments);
final shards = int.parse(options['shards']);
final shard = int.parse(options['shard']) - 1;
final copyCoredumps = options['copy-coredumps'] as bool;
final arch = options['arch'].toUpperCase();
configurations = <TestRunner>[
JitTestRunner('out/Debug$arch', [
'--disable-dart-dev',
...filterSlowFlags(someJitRuntimeFlags()),
'runtime/tests/concurrency/generated_stress_test.dart.jit.dill',
]),
JitTestRunner('out/Release$arch', [
'--disable-dart-dev',
...filterSlowFlags(someJitRuntimeFlags()),
'runtime/tests/concurrency/generated_stress_test.dart.jit.dill',
]),
AotTestRunner(
'out/Debug$arch',
[
...someGenSnapshotFlags(),
'runtime/tests/concurrency/generated_stress_test.dart.aot.dill',
],
[...filterSlowFlags(someAotRuntimeFlags())],
),
AotTestRunner(
'out/Release$arch',
[
...someGenSnapshotFlags(),
'runtime/tests/concurrency/generated_stress_test.dart.aot.dill',
],
[...filterSlowFlags(someAotRuntimeFlags())],
),
// TSAN last so the other steps are evenly distributed.
for (int i = 0; i < tsanShards; ++i)
JitTestRunner('out/ReleaseTSAN$arch', [
'--disable-dart-dev',
...filterSlowFlags(someJitRuntimeFlags()),
'--no-profiler', // TODO(https://github.com/dart-lang/sdk/issues/60804, https://github.com/dart-lang/sdk/issues/60805)
'-Drepeat=4',
'-Dshard=$i',
'-Dshards=$tsanShards',
'runtime/tests/concurrency/generated_stress_test.dart.jit.dill',
]),
];
// Tasks will eventually be killed if they do not have any output for some
// time. So we'll explicitly print something every 4 minutes.
final sw = Stopwatch()..start();
final timer = Timer.periodic(const Duration(minutes: 4), (_) {
print('[${sw.elapsed}] ... still working ...');
});
try {
final thisShardsConfigurations = [];
for (int i = 0; i < configurations.length; i++) {
if ((i % shards) == shard) {
thisShardsConfigurations.add(configurations[i]);
}
}
final crashes = <PotentialCrash>[];
for (final config in thisShardsConfigurations) {
await config.runTest(crashes);
}
if (!crashes.isEmpty && copyCoredumps) {
writeUnexpectedCrashesFile(crashes);
}
} finally {
timer.cancel();
}
}