blob: bae35d6d1ab689bf0ac0d69f0e9a67f366ea41c9 [file] [log] [blame]
// 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 '../vm/dart/snapshot_test_helper.dart';
int crashCounter = 0;
final Map<String, String> environmentForTests = (() {
final env = Map<String, String>.from(Platform.environment);
final testMatrix =
json.decode(File('tools/bots/test_matrix.json').readAsStringSync());
env.addAll(testMatrix['sanitizer_options'].cast<String, String>());
return env;
})();
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('Running "$executable ${args.join(' ')}"');
final Process process =
await Process.start(executable, args, environment: environmentForTests);
forwardStream(process.stdout, stdout);
forwardStream(process.stderr, stderr);
final int exitCode = await process.exitCode;
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/dart_precompiled_runtime',
[...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());
}
const int tsanShards = 200;
final configurations = <TestRunner>[
JitTestRunner('out/DebugX64', [
'--disable-dart-dev',
'--no-sound-null-safety',
'runtime/tests/concurrency/generated_stress_test.dart.jit.dill',
]),
JitTestRunner('out/ReleaseX64', [
'--disable-dart-dev',
'--no-sound-null-safety',
'--no-inline-alloc',
'--use-slow-path',
'--deoptimize-on-runtime-call-every=3',
'runtime/tests/concurrency/generated_stress_test.dart.jit.dill',
]),
for (int i = 0; i < tsanShards; ++i)
JitTestRunner('out/ReleaseTSANX64', [
'--disable-dart-dev',
'-Drepeat=4',
'-Dshard=$i',
'-Dshards=$tsanShards',
'--no-sound-null-safety',
'runtime/tests/concurrency/generated_stress_test.dart.jit.dill',
]),
AotTestRunner('out/ReleaseX64', [
'--no-sound-null-safety',
'runtime/tests/concurrency/generated_stress_test.dart.aot.dill',
], [
'--no-sound-null-safety',
]),
AotTestRunner('out/DebugX64', [
'--no-sound-null-safety',
'runtime/tests/concurrency/generated_stress_test.dart.aot.dill',
], [
'--no-sound-null-safety',
]),
];
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);
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;
// 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();
}
}