[baseline] Allow baselining a builder from multiple other builders
The script now takes a list of source builders (e.g. `--builders=a,b`).
The target builder is passed separately (e.g. `--target=c`). The results
from all source builders are mapped using the specified configuration
mappings and combined into the target builders results.
Bug: b/210809535
Change-Id: Ib23fff5d326977b06ef408ad29c21eefaf06f770
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/224201
Reviewed-by: William Hesse <whesse@google.com>
Commit-Queue: Alexander Thomas <athom@google.com>
diff --git a/baseline/lib/baseline.dart b/baseline/lib/baseline.dart
index b5eac65..6a93a70 100644
--- a/baseline/lib/baseline.dart
+++ b/baseline/lib/baseline.dart
@@ -6,6 +6,8 @@
import 'dart:convert';
import 'dart:io';
+import 'package:pool/pool.dart';
+
const _resultBase = 'gs://dart-test-results/builders';
/// Baselines a builder with the [options] and copies the results to the
@@ -17,43 +19,46 @@
if (channel != 'main') {
futures.add(baselineBuilder(
options.builders.map((b) => '$b-$channel').toList(),
+ '${options.target}-$channel',
options.configs,
options.dryRun,
resultBase));
} else {
- futures.add(baselineBuilder(
- options.builders, options.configs, options.dryRun, resultBase));
+ futures.add(baselineBuilder(options.builders, options.target,
+ options.configs, options.dryRun, resultBase));
}
}
await Future.wait(futures);
}
-Future<void> baselineBuilder(List<String> builders, Map<String, String> configs,
- bool dryRun, String resultBase) async {
- var from = builders[0];
- var to = builders[1];
- var latest = await read('$resultBase/$from/latest');
- var results = await read('$resultBase/$from/$latest/results.json');
+Future<void> baselineBuilder(List<String> builders, String target,
+ Map<String, String> configs, bool dryRun, String resultBase) async {
+ var resultsStream = Pool(4).forEach(builders, (builder) async {
+ var latest = await read('$resultBase/$builder/latest');
+ return await read('$resultBase/$builder/$latest/results.json');
+ });
var modifiedResults = StringBuffer();
- for (var json in LineSplitter.split(results).map(jsonDecode)) {
- json['build_number'] = 0;
- json['previous_build_number'] = 0;
- json['builder_name'] = to;
- var configuration = configs[json['configuration']];
- if (configuration == null) {
- throw Exception(
- "Missing configuration mapping for ${json['configuration']}");
- }
- json['configuration'] = configuration;
- json['flaky'] = false;
- json['previous_flaky'] = false;
+ await for (var results in resultsStream) {
+ for (var json in LineSplitter.split(results).map(jsonDecode)) {
+ json['build_number'] = 0;
+ json['previous_build_number'] = 0;
+ json['builder_name'] = target;
+ var configuration = configs[json['configuration']];
+ if (configuration == null) {
+ throw Exception(
+ "Missing configuration mapping for ${json['configuration']}");
+ }
+ json['configuration'] = configuration;
+ json['flaky'] = false;
+ json['previous_flaky'] = false;
- modifiedResults.writeln(jsonEncode(json));
- if (dryRun) break;
+ modifiedResults.writeln(jsonEncode(json));
+ if (dryRun) break;
+ }
}
await write(
- '$resultBase/$to/0/results.json', modifiedResults.toString(), dryRun);
- await write('$resultBase/$to/latest', '0', dryRun);
+ '$resultBase/$target/0/results.json', modifiedResults.toString(), dryRun);
+ await write('$resultBase/$target/latest', '0', dryRun);
}
Future<String> read(String url) {
diff --git a/baseline/lib/options.dart b/baseline/lib/options.dart
index ef773e1..6606483 100644
--- a/baseline/lib/options.dart
+++ b/baseline/lib/options.dart
@@ -11,6 +11,7 @@
late final List<String> builders;
late final Map<String, String> configs;
late final List<String> channels;
+ late final String target;
BaselineOptions(List<String> arguments) {
var parser = ArgParser();
@@ -23,10 +24,11 @@
abbr: 'm',
help: 'a comma separated list of configuration mappings in the form:'
'<old1>:<new1>,<old2>:<new2>');
- parser.addOption('builder-mapping',
+ parser.addMultiOption('builders',
abbr: 'b',
- help:
- 'a mapping from an old to a new builder in the form: <old>:<new>');
+ help: 'a comma separated list of builders to read result data from');
+ parser.addOption('target',
+ abbr: 't', help: 'a the name of the builder to baseline');
parser.addFlag('dry-run',
abbr: 'n',
defaultsTo: false,
@@ -35,11 +37,13 @@
parser.addFlag('help',
abbr: 'h', negatable: false, help: 'prints this message');
var parsed = parser.parse(arguments);
- if (parsed['help'] || parsed['builder-mapping'] is! String) {
+ if (parsed['help'] ||
+ parsed['builders'] is! List<String> ||
+ parsed['target'] is! String) {
print(parser.usage);
- exit(0);
+ exit(64);
}
- builders = (parsed['builder-mapping'] as String).split(':');
+ builders = (parsed['builders'] as List<String>);
configs = {
for (var v in ((parsed['config-mapping'] as Iterable<String>)
.map((c) => c.split(':'))))
@@ -47,5 +51,6 @@
};
dryRun = parsed['dry-run'];
channels = parsed['channel'];
+ target = parsed['target'];
}
}
diff --git a/baseline/pubspec.lock b/baseline/pubspec.lock
index f469ed6..fb61d12 100644
--- a/baseline/pubspec.lock
+++ b/baseline/pubspec.lock
@@ -7,14 +7,14 @@
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
- version: "26.0.0"
+ version: "31.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
- version: "2.3.0"
+ version: "2.8.0"
args:
dependency: "direct main"
description:
@@ -49,7 +49,7 @@
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
- version: "0.3.3"
+ version: "0.3.5"
collection:
dependency: transitive
description:
@@ -98,7 +98,7 @@
name: glob
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.1"
+ version: "2.0.2"
http_multi_server:
dependency: transitive
description:
@@ -161,7 +161,7 @@
name: mime
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.0"
+ version: "1.0.1"
node_preamble:
dependency: transitive
description:
@@ -183,15 +183,8 @@
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
- pedantic:
- dependency: transitive
- description:
- name: pedantic
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.11.1"
pool:
- dependency: transitive
+ dependency: "direct main"
description:
name: pool
url: "https://pub.dartlang.org"
@@ -287,21 +280,21 @@
name: test
url: "https://pub.dartlang.org"
source: hosted
- version: "1.17.12"
+ version: "1.19.5"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.3"
+ version: "0.4.8"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.2"
+ version: "0.4.9"
typed_data:
dependency: transitive
description:
@@ -315,14 +308,14 @@
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
- version: "7.3.0"
+ version: "7.5.0"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.0"
+ version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
diff --git a/baseline/pubspec.yaml b/baseline/pubspec.yaml
index 269e260..231d934 100644
--- a/baseline/pubspec.yaml
+++ b/baseline/pubspec.yaml
@@ -8,6 +8,7 @@
dependencies:
args: ^2.3.0
+ pool: ^1.5.0
dev_dependencies:
io: ^1.0.3
diff --git a/baseline/test/baseline_test.dart b/baseline/test/baseline_test.dart
index f4d429c..ab9b0d4 100644
--- a/baseline/test/baseline_test.dart
+++ b/baseline/test/baseline_test.dart
@@ -2,6 +2,7 @@
// 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:convert';
import 'dart:io';
import 'package:baseline/baseline.dart';
@@ -9,10 +10,16 @@
import 'package:io/io.dart';
import 'package:test/test.dart';
-final String builderResults =
- File('test/data/builder/42/results.json').readAsStringSync();
-final String builderStableResults =
- File('test/data/builder-stable/12/results.json').readAsStringSync();
+final builder1Results = _readTestData('test/data/builder/42/results.json');
+final builder2Results = _readTestData('test/data/builder2/36/results.json');
+final builderStableResults =
+ _readTestData('test/data/builder-stable/12/results.json');
+final builder2StableResults =
+ _readTestData('test/data/builder2-stable/15/results.json');
+
+List<String> _readTestData(String path) {
+ return LineSplitter.split(File(path).readAsStringSync()).toList();
+}
main() {
test('run', () async {
@@ -38,7 +45,8 @@
expect(
baseline(
BaselineOptions([
- '--builder-mapping=builder:new-builder',
+ '--builders=builder1,builder2',
+ '--target=new-builder',
'--channel=main,stable',
'--config-mapping=config2:new-config2',
'--dry-run',
@@ -49,47 +57,63 @@
test('baseline dry-run', () async {
await baselineTest([
- '--builder-mapping=builder:new-builder',
+ '--builders=builder,builder2',
+ '--target=new-builder',
'--channel=main,stable',
- '--config-mapping=config1:new-config1,config2:new-config2',
+ '--config-mapping=config1:new-config1,config2:new-config2,'
+ 'config3:new-config3,config4:new-config4',
'--dry-run',
], {
- 'builder-stable/latest': '12',
+ 'builder-stable/latest': ['12'],
'builder-stable/12/results.json': builderStableResults,
- 'builder/42/results.json': builderResults,
- 'builder/latest': '42',
+ 'builder/42/results.json': builder1Results,
+ 'builder/latest': ['42'],
+ 'builder2-stable/15/results.json': builder2StableResults,
+ 'builder2-stable/latest': ['15'],
+ 'builder2/36/results.json': builder2Results,
+ 'builder2/latest': ['36'],
});
});
test('baseline', () async {
- const newBuilderStableResults = '''
-{"build_number":0,"previous_build_number":0,"builder_name":"new-builder-stable","configuration":"new-config1","test_name":"test1","result":"PASS","flaky":false,"previous_flaky":false}
-{"build_number":0,"previous_build_number":0,"builder_name":"new-builder-stable","configuration":"new-config2","test_name":"test2","result":"FAIL","flaky":false,"previous_flaky":false}
-''';
- const newBuilderResults = '''
-{"build_number":0,"previous_build_number":0,"builder_name":"new-builder","configuration":"new-config1","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}
-{"build_number":0,"previous_build_number":0,"builder_name":"new-builder","configuration":"new-config2","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}
-''';
+ final newBuilderStableResults = unorderedEquals([
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder-stable","configuration":"new-config1","test_name":"test1","result":"PASS","flaky":false,"previous_flaky":false}',
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder-stable","configuration":"new-config2","test_name":"test2","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder-stable","configuration":"new-config3","test_name":"test1","result":"PASS","flaky":false,"previous_flaky":false}',
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder-stable","configuration":"new-config4","test_name":"test2","result":"FAIL","flaky":false,"previous_flaky":false}',
+ ]);
+ final newBuilderResults = unorderedEquals([
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder","configuration":"new-config1","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder","configuration":"new-config2","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}',
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder","configuration":"new-config3","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":0,"previous_build_number":0,"builder_name":"new-builder","configuration":"new-config4","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}',
+ ]);
await baselineTest([
- '--builder-mapping=builder:new-builder',
+ '--builders=builder,builder2',
+ '--target=new-builder',
'--channel=main,stable',
- '--config-mapping=config1:new-config1,config2:new-config2',
+ '--config-mapping=config1:new-config1,config2:new-config2,'
+ 'config3:new-config3,config4:new-config4',
], {
- 'builder-stable/latest': '12',
- 'builder-stable/12/results.json': builderStableResults,
'new-builder-stable/0/results.json': newBuilderStableResults,
- 'new-builder-stable/latest': '0',
+ 'new-builder-stable/latest': ['0'],
'new-builder/0/results.json': newBuilderResults,
- 'new-builder/latest': '0',
- 'builder/42/results.json': builderResults,
- 'builder/latest': '42',
+ 'new-builder/latest': ['0'],
+ 'builder-stable/latest': ['12'],
+ 'builder-stable/12/results.json': builderStableResults,
+ 'builder/42/results.json': builder1Results,
+ 'builder/latest': ['42'],
+ 'builder2-stable/15/results.json': builder2StableResults,
+ 'builder2-stable/latest': ['15'],
+ 'builder2/36/results.json': builder2Results,
+ 'builder2/latest': ['36'],
});
});
}
Future<void> baselineTest(
- List<String> arguments, Map<String, String> expectedFiles) async {
+ List<String> arguments, Map<String, dynamic> expectedFiles) async {
var temp = await Directory.systemTemp.createTemp();
try {
await copyPath('test/data', temp.path);
@@ -100,8 +124,8 @@
.map((e) => e.path.substring(temp.path.length + 1));
expect(files, containsAll(expectedFiles.keys));
for (var expectedFile in expectedFiles.entries) {
- var content =
- await File('${temp.path}/${expectedFile.key}').readAsString();
+ var content = LineSplitter.split(
+ await File('${temp.path}/${expectedFile.key}').readAsString());
expect(content, expectedFile.value,
reason: 'File "${expectedFile.key}" mismatch');
}
diff --git a/baseline/test/data/builder2-stable/15/results.json b/baseline/test/data/builder2-stable/15/results.json
new file mode 100644
index 0000000..ed50c1e
--- /dev/null
+++ b/baseline/test/data/builder2-stable/15/results.json
@@ -0,0 +1,2 @@
+{"build_number":15,"previous_build_number":12,"builder_name":"builder2-stable","configuration":"config3","test_name":"test1","result":"PASS","flaky":false,"previous_flaky":true}
+{"build_number":15,"previous_build_number":12,"builder_name":"builder2-stable","configuration":"config4","test_name":"test2","result":"FAIL","flaky":true,"previous_flaky":false}
diff --git a/baseline/test/data/builder2-stable/latest b/baseline/test/data/builder2-stable/latest
new file mode 100644
index 0000000..3f10ffe
--- /dev/null
+++ b/baseline/test/data/builder2-stable/latest
@@ -0,0 +1 @@
+15
\ No newline at end of file
diff --git a/baseline/test/data/builder2/36/results.json b/baseline/test/data/builder2/36/results.json
new file mode 100644
index 0000000..43b4db1
--- /dev/null
+++ b/baseline/test/data/builder2/36/results.json
@@ -0,0 +1,2 @@
+{"build_number":36,"previous_build_number":34,"builder_name":"builder2","configuration":"config3","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":true}
+{"build_number":36,"previous_build_number":34,"builder_name":"builder2","configuration":"config4","test_name":"test2","result":"PASS","flaky":true,"previous_flaky":false}
diff --git a/baseline/test/data/builder2/latest b/baseline/test/data/builder2/latest
new file mode 100644
index 0000000..dce6588
--- /dev/null
+++ b/baseline/test/data/builder2/latest
@@ -0,0 +1 @@
+36
\ No newline at end of file
diff --git a/baseline/test/options_test.dart b/baseline/test/options_test.dart
index 66d9297..906b476 100644
--- a/baseline/test/options_test.dart
+++ b/baseline/test/options_test.dart
@@ -5,7 +5,7 @@
import 'package:baseline/options.dart';
import 'package:test/test.dart';
-const _builders = '-ba:b';
+const _builders = ['-ba1,a2', '-tb'];
main() {
for (var channels in [
@@ -13,7 +13,7 @@
['dev', 'beta'],
['stable'],
]) {
- var arguments = ['-c${channels.join(',')}', _builders];
+ var arguments = ['-c${channels.join(',')}', ..._builders];
test('channels: "$arguments"', () {
var options = BaselineOptions(arguments);
expect(options.channels, channels);
@@ -21,22 +21,23 @@
}
test('builder-mapping', () {
- var options = BaselineOptions([_builders]);
- expect(options.builders, ['a', 'b']);
+ var options = BaselineOptions(_builders);
+ expect(options.builders, ['a1', 'a2']);
+ expect(options.target, 'b');
});
test('config-mapping', () {
- var options = BaselineOptions(['-mc:d,e:f', _builders]);
+ var options = BaselineOptions(['-mc:d,e:f', ..._builders]);
expect(options.configs, {'c': 'd', 'e': 'f'});
});
test('dry-run defaults to false', () {
- var options = BaselineOptions([_builders]);
+ var options = BaselineOptions(_builders);
expect(options.dryRun, false);
});
test('dry-run: true', () {
- var options = BaselineOptions(['-n', _builders]);
+ var options = BaselineOptions(['-n', ..._builders]);
expect(options.dryRun, true);
});
}