[baseline] Support baselining release builders from main
Example (baseline vm-linux-(dev|beta|stable) with vm-linux results):
bin/baseline.dart -n -tvm-linux -cdev,beta,stable
Bug: b/201272359
Change-Id: Ifd581d8ab1e86a625070d2c41be1ba141aa6d12f
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/279690
Reviewed-by: William Hesse <whesse@google.com>
Commit-Queue: Alexander Thomas <athom@google.com>
diff --git a/baseline/bin/baseline.dart b/baseline/bin/baseline.dart
index bc549fc..607689f 100755
--- a/baseline/bin/baseline.dart
+++ b/baseline/bin/baseline.dart
@@ -7,5 +7,5 @@
import 'package:baseline/options.dart';
void main(List<String> arguments) async {
- await baseline(BaselineOptions(arguments));
+ await baseline(BaselineOptions.parse(arguments));
}
diff --git a/baseline/lib/baseline.dart b/baseline/lib/baseline.dart
index b68bfd7..fd8653b 100644
--- a/baseline/lib/baseline.dart
+++ b/baseline/lib/baseline.dart
@@ -17,6 +17,8 @@
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,
@@ -24,9 +26,23 @@
options.target,
options.configs,
options.dryRun,
- options.ignoreUnmapped,
+ 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,
@@ -34,7 +50,7 @@
'${options.target}-$channel',
options.configs,
options.dryRun,
- options.ignoreUnmapped,
+ options.mapping,
resultBase)
]);
}
@@ -46,7 +62,7 @@
String target,
Map<String, String> configs,
bool dryRun,
- bool ignoreUnmapped,
+ ConfigurationMapping mapping,
String resultBase) async {
var resultsStream = Pool(4).forEach(builders, (builder) async {
var latest = await read('$resultBase/builders/$builder/latest');
@@ -55,14 +71,12 @@
var modifiedResults = StringBuffer();
var modifiedResultsPerConfig = <String, StringBuffer>{};
await for (var results in resultsStream) {
- for (var json in LineSplitter.split(results).map(jsonDecode)) {
- var configuration = configs[json['configuration']];
+ for (var json in LineSplitter.split(results)
+ .map(jsonDecode)
+ .cast<Map<String, dynamic>>()) {
+ var configuration = mapping(json['configuration'], configs);
if (configuration == null) {
- if (ignoreUnmapped) {
- continue;
- }
- throw Exception(
- "Missing configuration mapping for ${json['configuration']}");
+ continue;
}
if (suites.isNotEmpty && !suites.contains(json['suite'])) continue;
json['configuration'] = configuration;
diff --git a/baseline/lib/options.dart b/baseline/lib/options.dart
index df8ed0f..d4aea52 100644
--- a/baseline/lib/options.dart
+++ b/baseline/lib/options.dart
@@ -7,15 +7,18 @@
import 'package:args/args.dart';
class BaselineOptions {
- late final bool ignoreUnmapped;
- late final bool dryRun;
- late final List<String> builders;
- late final Map<String, String> configs;
- late final List<String> channels;
- late final Set<String> suites;
- late final String target;
+ final List<String> builders;
+ final Map<String, String> configs;
+ final bool dryRun;
+ final List<String> channels;
+ final ConfigurationMapping mapping;
+ final Set<String> suites;
+ final String target;
- BaselineOptions(List<String> arguments) {
+ BaselineOptions(this.builders, this.configs, this.dryRun, this.channels,
+ this.mapping, this.suites, this.target);
+
+ factory BaselineOptions.parse(List<String> arguments) {
var parser = ArgParser();
parser.addMultiOption('channel',
abbr: 'c',
@@ -24,6 +27,7 @@
help: 'a comma separated list of channels');
parser.addMultiOption('config-mapping',
abbr: 'm',
+ defaultsTo: ['*'],
help: 'a comma separated list of configuration mappings in the form:'
'<old1>:<new1>,<old2>:<new2>');
parser.addMultiOption('builders',
@@ -43,7 +47,6 @@
defaultsTo: false,
help: 'ignore tests in unmapped configurations',
negatable: false);
-
parser.addFlag('help',
abbr: 'h', negatable: false, help: 'prints this message');
var parsed = parser.parse(arguments);
@@ -53,16 +56,49 @@
print(parser.usage);
exit(64);
}
- builders = (parsed['builders'] as List<String>);
- configs = {
- for (var v in ((parsed['config-mapping'] as Iterable<String>)
- .map((c) => c.split(':'))))
- v[0]: v[1]
- };
- ignoreUnmapped = parsed['ignore-unmapped'];
- dryRun = parsed['dry-run'];
- channels = parsed['channel'];
- suites = Set.unmodifiable(parsed['suites']);
- target = parsed['target'];
+ var builders = (parsed['builders'] as List<String>);
+ final target = parsed['target'];
+ if (builders.isEmpty) {
+ builders = [target];
+ }
+ var mapping = parsed['ignore-unmapped']
+ ? ConfigurationMapping.relaxed
+ : ConfigurationMapping.strict;
+ var configs = const <String, String>{};
+ final configMapping = parsed['config-mapping'] as List<String>;
+ if (configMapping.length == 1 && configMapping.first == '*') {
+ mapping = ConfigurationMapping.none;
+ } else {
+ configs = {
+ for (var v in configMapping.map((c) => c.split(':'))) v[0]: v[1]
+ };
+ }
+ final dryRun = parsed['dry-run'];
+ final channels = parsed['channel'];
+ final suites = Set.unmodifiable(parsed['suites'] as List<String>);
+
+ return BaselineOptions(
+ builders, configs, dryRun, channels, mapping, suites, target);
}
}
+
+String? _strict(String configuration, Map<String, String> configs) =>
+ configs[configuration] ??
+ (throw Exception("Missing configuration mapping for $configuration"));
+String? _relaxed(String configuration, Map<String, String> configs) =>
+ configs[configuration];
+String? _none(String configuration, Map<String, String> configs) =>
+ configuration;
+
+enum ConfigurationMapping {
+ none(_none),
+ strict(_strict),
+ relaxed(_relaxed);
+
+ const ConfigurationMapping(this.mapping);
+ final String? Function(String configuration, Map<String, String> configs)
+ mapping;
+
+ String? call(String configuration, Map<String, String> configs) =>
+ mapping(configuration, configs);
+}
diff --git a/baseline/test/baseline_test.dart b/baseline/test/baseline_test.dart
index 0694b5c..aca7363 100644
--- a/baseline/test/baseline_test.dart
+++ b/baseline/test/baseline_test.dart
@@ -56,7 +56,7 @@
test('baseline missing config mapping throws', () {
expect(
baseline(
- BaselineOptions([
+ BaselineOptions.parse([
'--builders=builder,builder2',
'--target=new-builder',
'--channel=main,stable',
@@ -103,6 +103,50 @@
});
});
+ test('baseline default config mapping', () async {
+ final newBuilderDevResults = [
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-dev","configuration":"config1","suite":"suite1","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-dev","configuration":"config2","suite":"suite2","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}',
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-dev","configuration":"config3","suite":"suite1","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-dev","configuration":"config4","suite":"suite2","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}',
+ ];
+ final newBuilderStableResults = [
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-stable","configuration":"config1","suite":"suite1","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-stable","configuration":"config2","suite":"suite2","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}',
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-stable","configuration":"config3","suite":"suite1","test_name":"test1","result":"FAIL","flaky":false,"previous_flaky":false}',
+ '{"build_number":"0","previous_build_number":"0","builder_name":"builder-stable","configuration":"config4","suite":"suite2","test_name":"test2","result":"PASS","flaky":false,"previous_flaky":false}',
+ ];
+ await baselineTest([
+ '--builders=builder,builder2',
+ '--target=builder',
+ '--channel=dev,stable',
+ ], {
+ ...testData,
+ 'builders/builder-stable/0/results.json':
+ unorderedEquals(newBuilderStableResults),
+ 'builders/builder-stable/latest': ['0'],
+ 'configuration/stable/config1/0/results.json': [
+ newBuilderStableResults[0]
+ ],
+ 'configuration/stable/config2/0/results.json': [
+ newBuilderStableResults[1]
+ ],
+ 'configuration/stable/config3/0/results.json': [
+ newBuilderStableResults[2]
+ ],
+ 'configuration/stable/config4/0/results.json': [
+ newBuilderStableResults[3]
+ ],
+ 'builders/builder-dev/0/results.json':
+ unorderedEquals(newBuilderDevResults),
+ 'builders/builder-dev/latest': ['0'],
+ 'configuration/dev/config1/0/results.json': [newBuilderDevResults[0]],
+ 'configuration/dev/config2/0/results.json': [newBuilderDevResults[1]],
+ 'configuration/dev/config3/0/results.json': [newBuilderDevResults[2]],
+ 'configuration/dev/config4/0/results.json': [newBuilderDevResults[3]],
+ });
+ });
+
test('baseline dry-run', () async {
await baselineTest([
'--builders=builder,builder2',
@@ -200,7 +244,7 @@
var temp = await Directory.systemTemp.createTemp();
try {
await copyPath('test/data', temp.path);
- await baseline(BaselineOptions(arguments), temp.path);
+ await baseline(BaselineOptions.parse(arguments), temp.path);
var files = temp
.listSync(recursive: true)
.whereType<File>()
diff --git a/baseline/test/options_test.dart b/baseline/test/options_test.dart
index 856758e..ed9d9c1 100644
--- a/baseline/test/options_test.dart
+++ b/baseline/test/options_test.dart
@@ -15,39 +15,67 @@
]) {
var arguments = ['-c${channels.join(',')}', ..._builders];
test('channels: "$arguments"', () {
- var options = BaselineOptions(arguments);
+ var options = BaselineOptions.parse(arguments);
expect(options.channels, channels);
});
}
test('builder-mapping', () {
- var options = BaselineOptions(_builders);
+ var options = BaselineOptions.parse(_builders);
expect(options.builders, ['a1', 'a2']);
expect(options.target, 'b');
});
+ test('builder-mapping: default to target', () {
+ var options = BaselineOptions.parse(['-tb']);
+ expect(options.builders, ['b']);
+ });
+
test('config-mapping', () {
- var options = BaselineOptions(['-mc:d,e:f', ..._builders]);
+ var options = BaselineOptions.parse(['-mc:d,e:f', ..._builders]);
expect(options.configs, {'c': 'd', 'e': 'f'});
+ expect(options.mapping, ConfigurationMapping.strict);
+ });
+
+ test('config-mapping: star', () {
+ var options = BaselineOptions.parse(['-m*', ..._builders]);
+ expect(options.configs, const {});
+ expect(options.mapping, ConfigurationMapping.none);
+ });
+
+ test('config-mapping: ignore-unmapped', () {
+ var options = BaselineOptions.parse(['-u', '-mc:d,e:f', ..._builders]);
+ expect(options.mapping, ConfigurationMapping.relaxed);
});
test('suites', () {
- var options = BaselineOptions(['-ss1,s2', ..._builders]);
+ var options = BaselineOptions.parse(['-ss1,s2', ..._builders]);
expect(options.suites, {'s1', 's2'});
});
- test('ignore-unmapped', () {
- var options = BaselineOptions(['-u', '-mc:d,e:f', ..._builders]);
- expect(options.ignoreUnmapped, true);
- });
-
test('dry-run defaults to false', () {
- var options = BaselineOptions(_builders);
+ var options = BaselineOptions.parse(_builders);
expect(options.dryRun, false);
});
test('dry-run: true', () {
- var options = BaselineOptions(['-n', ..._builders]);
+ var options = BaselineOptions.parse(['-n', ..._builders]);
expect(options.dryRun, true);
});
+
+ test('mapping: strict', () {
+ expect(ConfigurationMapping.strict('foo', {'foo': 'bar'}), 'bar');
+ expect(() => ConfigurationMapping.strict('oof', {'foo': 'bar'}),
+ throwsException);
+ });
+
+ test('mapping: relaxed', () {
+ expect(ConfigurationMapping.relaxed('foo', {'foo': 'bar'}), 'bar');
+ expect(ConfigurationMapping.relaxed('oof', {'foo': 'bar'}), null);
+ });
+
+ test('mapping: none', () {
+ expect(ConfigurationMapping.none('foo', {'foo': 'bar'}), 'foo');
+ expect(ConfigurationMapping.none('oof', {'foo': 'bar'}), 'oof');
+ });
}