blob: 4c52e933e54fb6ce6dae12bd3a28584d272d589b [file] [log] [blame]
// Copyright (c) 2017, 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 'util.dart';
/// The [Uri] of a build step stdio log split into its subparts.
class BuildUri {
final String botName;
final int buildNumber;
final String stepName;
factory BuildUri(Uri uri) {
if (uri.host == "logs.chromium.org") {
RegExp logdogBuilderRegExp = new RegExp(
r"^.*\/client\.dart\/(.*)\/(\d*)\/\+\/recipes\/steps\/(.*)\/0.*$");
var match =
logdogBuilderRegExp.firstMatch(Uri.decodeFull(uri.toString()));
return new BuildUri.fromData(
match.group(1), int.parse(match.group(2)), match.group(3));
} else {
List<String> parts = split(Uri.decodeFull(uri.path),
['/builders/', '/builds/', '/steps/', '/logs/']);
String botName = parts[1];
int buildNumber = int.parse(parts[2]);
String stepName = parts[3];
return new BuildUri.fromData(botName, buildNumber, stepName);
}
}
factory BuildUri.fromUrl(String url) {
if (!url.endsWith('/text') && !url.contains("logs.chromium.org")) {
// Use the text version of the stdio log.
url += '/text';
}
return new BuildUri(Uri.parse(url));
}
BuildUri.fromData(this.botName, this.buildNumber, this.stepName);
BuildUri withBuildNumber(int buildNumber) {
return new BuildUri.fromData(botName, buildNumber, stepName);
}
int get absoluteBuildNumber => buildNumber >= 0 ? buildNumber : null;
String get shortBuildName => '$botName/$stepName';
String get buildName =>
'/builders/$botName/builds/$buildNumber/steps/$stepName';
String get path => '/p/client.dart$buildName/logs/stdio/text';
/// Returns the path used in logdog for this build uri.
///
/// Since logdog only supports absolute build numbers, [buildNumber] must be
/// non-negative. A [StateError] is thrown, otherwise.
String get logdogPath {
if (buildNumber < 0)
throw new StateError('BuildUri $buildName must have a non-negative build '
'number to a valid logdog path.');
return 'bb/client.dart/$botName/$buildNumber/+/recipes/steps/'
'${stepName.replaceAll(' ', '_')}/0/stdout';
}
/// Creates the [Uri] for this build step stdio log.
Uri toUri() {
return new Uri(scheme: 'https', host: 'build.chromium.org', path: path);
}
/// Returns the [BuildUri] the previous build of this build step.
BuildUri prev() {
return new BuildUri.fromData(botName, buildNumber - 1, stepName);
}
String toString() {
return buildName;
}
}
/// Id for a test on a specific configuration, for instance
/// `dart2js-chrome release_x64 co19/Language/Metadata/before_function_t07`.
class TestConfiguration {
final String configName;
final String archName;
final String testName;
TestConfiguration(this.configName, this.archName, this.testName);
String toString() {
return '$configName $archName $testName';
}
int get hashCode =>
configName.hashCode * 13 +
archName.hashCode * 17 +
testName.hashCode * 19;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! TestConfiguration) return false;
return configName == other.configName &&
archName == other.archName &&
testName == other.testName;
}
}
/// The results of a build step.
class BuildResult {
final BuildUri buildUri;
/// The absolute build number, if found.
///
/// The [buildUri] can be created with a relative build number, such as `-2`
/// which means the second-to-last build. The absolute build number, a
/// positive number, is read from the build results.
final int buildNumber;
final String buildRevision;
final List<TestStatus> _results;
final List<TestFailure> _failures;
final List<Timing> _timings;
BuildResult(this.buildUri, this.buildNumber, this.buildRevision,
this._results, this._failures, this._timings);
/// `true` of the build result has test failures.
bool get hasFailures => _failures.isNotEmpty;
/// Returns the top-20 timings found in the build log.
Iterable<Timing> get timings => _timings;
/// Returns the [TestStatus] for all tests.
Iterable<TestStatus> get results => _results;
/// Returns the [TestFailure]s for tests that timed out.
Iterable<TestFailure> get timeouts {
return _failures
.where((TestFailure failure) => failure.actual == 'Timeout');
}
/// Returns the [TestFailure]s for failing tests that did not time out.
Iterable<TestFailure> get errors {
return _failures
.where((TestFailure failure) => failure.actual != 'Timeout');
}
/// Returns all [TestFailure]s.
Iterable<TestFailure> get failures => _failures;
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('$buildUri\n');
sb.write('Failures:\n${_failures.join('\n-----\n')}\n');
sb.write('\nTimings:\n${_timings.join('\n')}');
return sb.toString();
}
}
/// Test failure data derived from the test failure summary in the build step
/// stdio log.
class TestFailure {
final BuildUri uri;
final TestConfiguration id;
final String expected;
final String actual;
final String text;
factory TestFailure(BuildUri uri, List<String> lines) {
List<String> parts = split(lines.first, ['FAILED: ', ' ', ' ']);
String configName = parts[1];
String archName = parts[2];
String testName = parts[3];
TestConfiguration id =
new TestConfiguration(configName, archName, testName);
String expected = split(lines[1], ['Expected: '])[1].trim();
String actual = split(lines[2], ['Actual: '])[1].trim();
return new TestFailure.internal(
uri, id, expected, actual, lines.skip(3).join('\n'));
}
TestFailure.internal(
this.uri, this.id, this.expected, this.actual, this.text);
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('FAILED: $id\n');
sb.write('Expected: $expected\n');
sb.write('Actual: $actual\n');
sb.write(text);
return sb.toString();
}
}
/// Id for a single test step, for instance the compilation and run steps of
/// a test.
class TestStep {
final String stepName;
final TestConfiguration id;
TestStep(this.stepName, this.id);
String toString() {
return '$stepName - $id';
}
int get hashCode => stepName.hashCode * 13 + id.hashCode * 17;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! TestStep) return false;
return stepName == other.stepName && id == other.id;
}
}
/// The timing result for a single test step.
class Timing {
final BuildUri uri;
final String time;
final TestStep step;
Timing(this.uri, this.time, this.step);
String toString() {
return '$time - $step';
}
}
/// The result of a single test for a single test step.
class TestStatus {
final TestConfiguration config;
final String status;
TestStatus(this.config, this.status);
String toString() => '$config: $status';
}