mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 10:49:00 +00:00
665981b4bd
Fixes: https://github.com/dart-lang/sdk/issues/41627 Change-Id: Ie197409520b8215047df215429c3febf3ee7000d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/144501 Reviewed-by: Karl Klose <karlklose@google.com>
300 lines
9.4 KiB
Dart
Executable file
300 lines
9.4 KiB
Dart
Executable file
#!/usr/bin/env dart
|
|
// Copyright (c) 2019, 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.
|
|
|
|
// Find the success/failure status for a builder that is written to
|
|
// Firestore by the cloud functions that process results.json.
|
|
// These cloud functions write a success/failure result to the
|
|
// builder table based on the approvals in Firestore.
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
const numAttempts = 20;
|
|
const failuresPerConfiguration = 20;
|
|
const queryUrl = 'https://firestore.googleapis.com/v1/'
|
|
'projects/dart-ci/databases/(default)/documents:runQuery';
|
|
String builder;
|
|
String builderBase;
|
|
int buildNumber;
|
|
String token;
|
|
http.Client client;
|
|
|
|
String get buildTable => builder.endsWith('-try') ? 'try_builds' : 'builds';
|
|
String get resultsTable => builder.endsWith('-try') ? 'try_results' : 'results';
|
|
|
|
bool booleanFieldOrFalse(Map<String, dynamic> document, String field) {
|
|
Map<String, dynamic> fieldObject = document['fields'][field];
|
|
if (fieldObject == null) return false;
|
|
return fieldObject['booleanValue'] ?? false;
|
|
}
|
|
|
|
void usage(ArgParser parser) {
|
|
print('''
|
|
Usage: get_builder_status.dart [OPTIONS]
|
|
Gets the builder status from the Firestore database.
|
|
Polls until it gets a completed builder status, or times out.
|
|
|
|
The options are as follows:
|
|
|
|
${parser.usage}''');
|
|
exit(1);
|
|
}
|
|
|
|
main(List<String> args) async {
|
|
final parser = new ArgParser();
|
|
parser.addFlag('help', help: 'Show the program usage.', negatable: false);
|
|
parser.addOption('auth_token',
|
|
abbr: 'a', help: 'Authorization token with cloud-platform scope');
|
|
parser.addOption('builder', abbr: 'b', help: 'The builder name');
|
|
parser.addOption('build_number', abbr: 'n', help: 'The build number');
|
|
|
|
final options = parser.parse(args);
|
|
if (options['help']) {
|
|
usage(parser);
|
|
}
|
|
|
|
builder = options['builder'];
|
|
buildNumber = int.parse(options['build_number']);
|
|
builderBase = builder.replaceFirst(RegExp('-try\$'), '');
|
|
token = await File(options['auth_token']).readAsString();
|
|
client = http.Client();
|
|
for (int count = 0; count < numAttempts; ++count) {
|
|
if (count > 0) {
|
|
await Future.delayed(Duration(seconds: 10));
|
|
}
|
|
final response = await runFirestoreQuery(buildQuery());
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
final documents = jsonDecode(response.body);
|
|
final document = documents.first['document'];
|
|
if (document != null) {
|
|
bool success = booleanFieldOrFalse(document, 'success');
|
|
bool completed = booleanFieldOrFalse(document, 'completed');
|
|
if (completed) {
|
|
print(success
|
|
? 'No new unapproved failures'
|
|
: 'There are new unapproved failures on this build');
|
|
if (builder.endsWith('-try')) exit(success ? 0 : 1);
|
|
final configurations = await getConfigurations();
|
|
final failures = await fetchActiveFailures(configurations);
|
|
if (failures.isNotEmpty) {
|
|
print('There are unapproved failures');
|
|
printActiveFailures(failures);
|
|
exit(1);
|
|
} else {
|
|
print('There are no unapproved failures');
|
|
exit(0);
|
|
}
|
|
}
|
|
String chunks =
|
|
(document['fields']['num_chunks'] ?? const {})['integerValue'];
|
|
String processedChunks = (document['fields']['processed_chunks'] ??
|
|
const {})['integerValue'];
|
|
if (processedChunks != null) {
|
|
print([
|
|
'Received',
|
|
processedChunks,
|
|
if (chunks != null) ...['out of', chunks],
|
|
'chunks.'
|
|
].join(' '));
|
|
}
|
|
} else {
|
|
print('No results recieved for build $buildNumber of $builder');
|
|
}
|
|
} else {
|
|
print('HTTP status ${response.statusCode} received '
|
|
'when fetching build data');
|
|
}
|
|
}
|
|
print('No status received for build $buildNumber of $builder '
|
|
'after $numAttempts attempts, with 10 second waits.');
|
|
exit(2);
|
|
}
|
|
|
|
Future<List<String>> getConfigurations() async {
|
|
final response = await runFirestoreQuery(configurationsQuery());
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
final documents = jsonDecode(response.body);
|
|
final groups = <String>{
|
|
for (Map document in documents)
|
|
if (document.containsKey('document'))
|
|
document['document']['name'].split('/').last
|
|
};
|
|
return groups.toList();
|
|
}
|
|
print('Could not fetch configurations for $builderBase');
|
|
return [];
|
|
}
|
|
|
|
Map<int, Future<String>> commitHashes = {};
|
|
Future<String> commitHash(int index) =>
|
|
commitHashes.putIfAbsent(index, () => fetchCommitHash(index));
|
|
|
|
Future<String> fetchCommitHash(int index) async {
|
|
final response = await runFirestoreQuery(commitQuery(index));
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
final document = jsonDecode(response.body).first['document'];
|
|
if (document != null) {
|
|
return document['name'].split('/').last;
|
|
}
|
|
}
|
|
print('Could not fetch commit with index $index');
|
|
return 'missing hash for commit $index';
|
|
}
|
|
|
|
Future<Map<String, List<Map<String, dynamic>>>> fetchActiveFailures(
|
|
List<String> configurations) async {
|
|
final failures = <String, List<Map<String, dynamic>>>{};
|
|
for (final configuration in configurations) {
|
|
final response =
|
|
await runFirestoreQuery(unapprovedFailuresQuery(configuration));
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
final documents = jsonDecode(response.body);
|
|
for (final documentItem in documents) {
|
|
final document = documentItem['document'];
|
|
if (document == null) continue;
|
|
final fields = document['fields'];
|
|
failures.putIfAbsent(configuration, () => []).add({
|
|
'name': fields['name']['stringValue'],
|
|
'start_commit': await commitHash(
|
|
int.parse(fields['blamelist_start_index']['integerValue'])),
|
|
'end_commit': await commitHash(
|
|
int.parse(fields['blamelist_end_index']['integerValue'])),
|
|
'result': fields['result']['stringValue'],
|
|
'expected': fields['expected']['stringValue'],
|
|
'previous': fields['previous_result']['stringValue'],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return failures;
|
|
}
|
|
|
|
void printActiveFailures(Map<String, List<Map<String, dynamic>>> failures) {
|
|
for (final configuration in failures.keys) {
|
|
print('($configuration):');
|
|
for (final failure in failures[configuration]) {
|
|
print([
|
|
' ',
|
|
failure['name'],
|
|
' (',
|
|
failure['previous'],
|
|
' -> ',
|
|
failure['result'],
|
|
', expected ',
|
|
failure['expected'],
|
|
') at ',
|
|
failure['start_commit'].substring(0, 6),
|
|
if (failure['end_commit'] != failure['start_commit']) ...[
|
|
'..',
|
|
failure['end_commit'].substring(0, 6)
|
|
]
|
|
].join(''));
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<http.Response> runFirestoreQuery(String query) {
|
|
final headers = {
|
|
'Authorization': 'Bearer $token',
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json'
|
|
};
|
|
return client.post(queryUrl, headers: headers, body: query);
|
|
}
|
|
|
|
String buildQuery() => jsonEncode({
|
|
'structuredQuery': {
|
|
'from': [
|
|
{'collectionId': buildTable}
|
|
],
|
|
'limit': 1,
|
|
'where': {
|
|
'compositeFilter': {
|
|
'op': 'AND',
|
|
'filters': [
|
|
{
|
|
'fieldFilter': {
|
|
'field': {'fieldPath': 'build_number'},
|
|
'op': 'EQUAL',
|
|
'value': {'integerValue': buildNumber}
|
|
}
|
|
},
|
|
{
|
|
'fieldFilter': {
|
|
'field': {'fieldPath': 'builder'},
|
|
'op': 'EQUAL',
|
|
'value': {'stringValue': builder}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
String configurationsQuery() => jsonEncode({
|
|
'structuredQuery': {
|
|
'from': [
|
|
{'collectionId': 'configurations'}
|
|
],
|
|
'where': {
|
|
'fieldFilter': {
|
|
'field': {'fieldPath': 'builder'},
|
|
'op': 'EQUAL',
|
|
'value': {'stringValue': builderBase}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
String unapprovedFailuresQuery(String configuration) => jsonEncode({
|
|
'structuredQuery': {
|
|
'from': [
|
|
{'collectionId': resultsTable}
|
|
],
|
|
'limit': failuresPerConfiguration,
|
|
'where': {
|
|
'compositeFilter': {
|
|
'op': 'AND',
|
|
'filters': [
|
|
{
|
|
'fieldFilter': {
|
|
'field': {'fieldPath': 'active_configurations'},
|
|
'op': 'ARRAY_CONTAINS',
|
|
'value': {'stringValue': configuration}
|
|
}
|
|
},
|
|
{
|
|
'fieldFilter': {
|
|
'field': {'fieldPath': 'approved'},
|
|
'op': 'EQUAL',
|
|
'value': {'booleanValue': false}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
String commitQuery(int index) => jsonEncode({
|
|
'structuredQuery': {
|
|
'from': [
|
|
{'collectionId': 'commits'}
|
|
],
|
|
'limit': 1,
|
|
'where': {
|
|
'fieldFilter': {
|
|
'field': {'fieldPath': 'index'},
|
|
'op': 'EQUAL',
|
|
'value': {'integerValue': index}
|
|
}
|
|
}
|
|
}
|
|
});
|