blob: 43a6a4128be1f20b50da4ae60272066a16e33799 [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 'package:baseline/options.dart';
import 'dart:convert';
import 'dart:io';
import 'package:pool/pool.dart';
const _resultBase = 'gs://dart-test-results';
/// Baselines a builder with the [options] and copies the results to the
/// [resultBase]. [resultBase] can be a URL or a path supported by `gsutil cp`.
Future<void> baseline(BaselineOptions options,
[String resultBase = _resultBase]) async {
await Future.wait([
for (final channel in options.channels)
if (channel == 'main')
// baseline a new builder on main
// builder,builder2 -> new-builder
baselineBuilder(
options.builders,
channel,
options.suites,
options.target,
options.configs,
options.dryRun,
options.mapping,
resultBase)
else if (options.builders.contains(options.target))
// baseline a builder on a channel with main builder data
// builder,builder2 -> builder-dev
baselineBuilder(
options.builders,
channel,
options.suites,
'${options.target}-$channel',
options.configs,
options.dryRun,
options.mapping,
resultBase)
else
// baseline a builder on a channel with channel builder data
// builder-dev,builder2-dev -> new-builder-dev
baselineBuilder(
options.builders.map((b) => '$b-$channel').toList(),
channel,
options.suites,
'${options.target}-$channel',
options.configs,
options.dryRun,
options.mapping,
resultBase)
]);
}
Future<void> baselineBuilder(
List<String> builders,
String channel,
Set<String> suites,
String target,
Map<String, List<String>> configs,
bool dryRun,
ConfigurationMapping mapping,
String resultBase) async {
var resultsStream = Pool(4).forEach(builders, (builder) async {
var latest = await read('$resultBase/builders/$builder/latest');
return await read('$resultBase/builders/$builder/$latest/results.json');
});
var modifiedResults = StringBuffer();
var modifiedResultsPerConfig = <String, StringBuffer>{};
await for (var results in resultsStream) {
for (var json in LineSplitter.split(results)
.map(jsonDecode)
.cast<Map<String, dynamic>>()) {
var configurations = mapping(json['configuration'], configs);
if (configurations == null) {
continue;
}
for (var configuration in configurations) {
if (suites.isNotEmpty && !suites.contains(json['suite'])) continue;
json['configuration'] = configuration;
json['build_number'] = '0';
json['previous_build_number'] = '0';
json['builder_name'] = target;
json['flaky'] = false;
json['previous_flaky'] = false;
var encoded = jsonEncode(json);
modifiedResults.writeln(encoded);
modifiedResultsPerConfig
.putIfAbsent(configuration, () => StringBuffer())
.writeln(encoded);
}
if (dryRun) break;
}
}
await write('$resultBase/builders/$target/0/results.json',
modifiedResults.toString(), dryRun);
for (var entry in modifiedResultsPerConfig.entries) {
await write(
'$resultBase/configuration/$channel/${entry.key}/0/results.json',
entry.value.toString(),
dryRun);
}
await write('$resultBase/builders/$target/latest', '0', dryRun);
}
Future<String> read(String url) {
return run('gsutil.py', ['cp', url, '-']);
}
Future<String> write(String url, String stdin, bool dryRun) {
return run('gsutil.py', ['cp', '-', url], stdin: stdin, dryRun: dryRun);
}
Future<String> run(String command, List<String> arguments,
{String? stdin, bool dryRun = false}) async {
print('Running $command $arguments...');
if (dryRun) {
if (stdin != null) {
print('stdin:\n$stdin');
}
return '';
}
var process = await Process.start(command, arguments);
process.stderr.transform(utf8.decoder).forEach(print);
if (stdin != null) {
process.stdin.write(stdin);
await process.stdin.flush();
process.stdin.close();
}
var stdout = process.stdout.transform(utf8.decoder).join();
var result = await process.exitCode;
if (result != 0) {
throw Exception('Failed to run $command $arguments: $result');
}
return await stdout;
}