dd5b9cd7d7
Change-Id: I1bd930c11e0463ba0de1938ba417b6664c8cdb28 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/376023 Reviewed-by: Nate Bosch <nbosch@google.com> Commit-Queue: Devon Carew <devoncarew@google.com> Reviewed-by: Alexander Thomas <athom@google.com>
130 lines
4.8 KiB
Dart
Executable File
130 lines
4.8 KiB
Dart
Executable File
#!/usr/bin/env dart
|
|
// Copyright (c) 2018, 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.
|
|
|
|
// Update the flakiness data with a set of fresh results.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:args/args.dart';
|
|
|
|
import 'package:test_runner/bot_results.dart';
|
|
|
|
void main(List<String> args) async {
|
|
final parser = ArgParser();
|
|
parser.addFlag('help', help: 'Show the program usage.', negatable: false);
|
|
parser.addOption('input', abbr: 'i', help: 'Input flakiness file.');
|
|
parser.addOption('output', abbr: 'o', help: 'Output flakiness file.');
|
|
parser.addOption('build-id', help: 'Logdog ID of this buildbot run');
|
|
parser.addOption('commit', help: 'Commit hash of this buildbot run');
|
|
parser.addFlag('no-forgive', help: 'Don\'t remove any flaky records');
|
|
|
|
final options = parser.parse(args);
|
|
if (options.flag('help')) {
|
|
print('''
|
|
Usage: update_flakiness.dart [OPTION]... [RESULT-FILE]...
|
|
Update the flakiness data with a set of fresh results.
|
|
|
|
The options are as follows:
|
|
|
|
${parser.usage}''');
|
|
return;
|
|
}
|
|
final parameters = options.rest;
|
|
|
|
// Load the existing flakiness data, if any.
|
|
final data = options.option('input') != null
|
|
? await loadResultsMap(options.option('input')!)
|
|
: <String, Map<String, dynamic>>{};
|
|
|
|
final resultsForInactiveFlakiness = {
|
|
for (final flakyTest in data.keys)
|
|
if (data[flakyTest]!['active'] == false) flakyTest: <String>{}
|
|
};
|
|
|
|
final now = DateTime.now();
|
|
final nowString = now.toIso8601String();
|
|
// Incrementally update the flakiness data with each observed result.
|
|
for (final path in parameters) {
|
|
final results = await loadResults(path);
|
|
for (final resultObject in results) {
|
|
final String configuration = resultObject['configuration'];
|
|
final String name = resultObject['name'];
|
|
final String result = resultObject['result'];
|
|
final key = '$configuration:$name';
|
|
resultsForInactiveFlakiness[key]?.add(result);
|
|
final testData = data[key] ??= {};
|
|
testData['configuration'] = configuration;
|
|
testData['name'] = name;
|
|
testData['expected'] = resultObject['expected'];
|
|
final List<dynamic> outcomes = testData['outcomes'] ??= <dynamic>[];
|
|
if (!outcomes.contains(result)) {
|
|
outcomes
|
|
..add(result)
|
|
..sort();
|
|
testData['last_new_result_seen'] = nowString;
|
|
}
|
|
if (testData['current'] == result) {
|
|
var currentCounter = testData['current_counter'] as int;
|
|
testData['current_counter'] = currentCounter + 1;
|
|
} else {
|
|
testData['current'] = result;
|
|
testData['current_counter'] = 1;
|
|
}
|
|
Map<String, dynamic> mapField(String key) =>
|
|
testData[key] ??= <String, dynamic>{};
|
|
mapField('occurrences')[result] =
|
|
(mapField('occurrences')[result] as int? ?? 0) + 1;
|
|
mapField('first_seen')[result] ??= nowString;
|
|
mapField('last_seen')[result] = nowString;
|
|
mapField('matches')[result] = resultObject['matches'];
|
|
if (options.option('build-id') != null) {
|
|
mapField('build_ids')[result] = options.option('build-id')!;
|
|
}
|
|
if (options.option('commit') != null) {
|
|
mapField('commits')[result] = options.option('commit')!;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write out the new flakiness data.
|
|
final flakinessHorizon = now.subtract(Duration(days: 7));
|
|
final sink = options.option('output') != null
|
|
? File(options.option('output')!).openWrite()
|
|
: stdout;
|
|
final keys = data.keys.toList()..sort();
|
|
for (final key in keys) {
|
|
final testData = data[key]!;
|
|
if ((testData['outcomes'] as List).length < 2) continue;
|
|
// Reactivate inactive flaky results that are flaky again.
|
|
if (testData['active'] == false) {
|
|
if (resultsForInactiveFlakiness[key]!.length > 1) {
|
|
testData['active'] = true;
|
|
testData['reactivation_count'] =
|
|
(testData['reactivation_count'] as int? ?? 0) + 1;
|
|
}
|
|
} else if (options.flag('no-forgive')) {
|
|
testData['active'] = true;
|
|
} else if (testData['current_counter'] as int >= 100) {
|
|
// Forgive tests that have been stable for 100 builds.
|
|
testData['active'] = false;
|
|
} else {
|
|
// Forgive tests that have been stable since flakiness horizon (one week).
|
|
final resultTimes = [
|
|
for (final timeString in (testData['last_seen'] as Map).values)
|
|
DateTime.parse(timeString)
|
|
]..sort();
|
|
// The latest timestamp is the current result. The one before that is the
|
|
// timestamp of the latest flake. Timestamps are sorted from earliest to latest.
|
|
if (resultTimes[resultTimes.length - 2].isBefore(flakinessHorizon)) {
|
|
testData['active'] = false;
|
|
} else {
|
|
testData['active'] = true;
|
|
}
|
|
}
|
|
sink.writeln(jsonEncode(testData));
|
|
}
|
|
}
|