| // 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 'dart:io'; |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:core'; |
| import 'result_json_models.dart'; |
| import '../logger.dart'; |
| import '../cache_new.dart'; |
| import '../logdog.dart'; |
| import '../logdog_rpc.dart'; |
| import '../luci_api.dart'; |
| import '../luci.dart'; |
| import '../buildbucket.dart'; |
| import '../util.dart'; |
| |
| /// [TestResultService] provides functions to obtain [TestResult]s from logs. |
| class TestResultService { |
| final Logger logger; |
| final CreateCacheFunction standardCache; |
| |
| TestResultService(this.logger, this.standardCache); |
| |
| /// Gets the latest result from a builder with [name] in a [project]. |
| Future<TestResult> latestForBuilder(String project, String name, |
| {CreateCacheFunction createCache}) async { |
| int buildNumber = (await latestBuildNumbersForBuilders([name]))[name]; |
| if (buildNumber == 0) { |
| print("Could not get any results for builder with name $name."); |
| return new TestResult(); |
| } |
| return forBuild(project, name, buildNumber, createCache: createCache); |
| } |
| |
| /// Get a test result from logdog by massaging the [uri] passed in, if it is |
| /// not in the correct format. |
| Future<TestResult> fromLogdog(String uri, {CreateCacheFunction createCache}) { |
| String logName; |
| // If it is an (invalid) buildbot url: |
| // https://uberchromegw.corp.google.com/i/client.dart/builders/.....log |
| if (uri.contains("uberchrome")) { |
| logger.debug("Assuming that $uri is an uberchrome url."); |
| uri = Uri.decodeFull(uri); |
| var reg = new RegExp(r"^https:\/\/uberchromegw\.corp\.google\.com\/i\/" |
| r"(.*)\/builders\/(.*)\/builds\/(\d*)(.*)\/logs\/result.log$"); |
| var match = reg.firstMatch(uri); |
| if (match != null) { |
| logName = "bb/${match.group(1)}/${match.group(2)}/" |
| "${match.group(3)}/+/recipes${match.group(4).replaceAll(' ', '_')}" |
| "/0/logs/result.log/0"; |
| } |
| } else if (uri.contains("build.chromium.org")) { |
| // If it is an (invalid) public buildbot url: |
| // https://build.chromium.org/p/client.dart/builders/....log |
| logger.debug("Assuming that $uri is an build.chromium.org url."); |
| uri = Uri.decodeFull(uri); |
| var reg = new RegExp(r"^https:\/\/build\.chromium\.org\/p\/(.*)\/" |
| r"builders\/(.*)\/builds\/(\d*)(.*)\/logs\/result.log$"); |
| var match = reg.firstMatch(uri); |
| if (match != null) { |
| logName = "bb/${match.group(1)}/${match.group(2)}/" |
| "${match.group(3)}/+/recipes${match.group(4).replaceAll(' ', '_')}" |
| "/0/logs/result.log/0"; |
| } |
| } else if (uri.contains("luci-logdog")) { |
| // If it is an luci log-dog url: |
| // https://luci-logdog.appspot.com/v/?s=chromium%2Fbb%2Fclient.dart%...log |
| logger.debug("Assuming that $uri is a luci-logdog url."); |
| logName = "${Uri.decodeFull(uri.substring(48))}"; |
| } else if (uri.contains("logs.chromium")) { |
| // If it is a logs.chromium.org url |
| // https://logs.chromium.org/v/?s=chromium%2Fbb%2Fclient.dart%...log |
| logger.debug("Assuming that $uri is a logs.chromium url."); |
| logName = "${Uri.decodeFull(uri.substring(42))}"; |
| } else { |
| logger.debug( |
| "Assuming that $uri is a logdog url that can be used directly"); |
| // Assume it is a logdog url and use it directly. |
| logName = uri; |
| } |
| |
| if (logName == null) { |
| throw new Exception("Could not identify URL $uri"); |
| } |
| |
| var logdog = new LogdogRpc(); |
| var cache = createCache ?? standardCache; |
| return logdog |
| .get(BUILDER_PROJECT, logName, cache(duration: new Duration(days: 365))) |
| .then((json) => new TestResult.fromJson(jsonDecode(json))); |
| } |
| |
| /// Gets result logs from logdog streams. |
| Future<List<TestResult>> fromStreams( |
| String project, List<LogdogStream> streams, WithCacheFunction cache) { |
| var logdog = new LogdogRpc(); |
| return Future.wait(streams.map((stream) { |
| logger.debug('Getting the log ${stream.path}...'); |
| return logdog.get(project, stream.path, cache).then((log) { |
| return new TestResult.fromJson(jsonDecode(log)); |
| }).catchError( |
| errorLogger(logger, "Could not get a log.", new TestResult())); |
| })); |
| } |
| |
| /// Get test results for a build [buildNumber] on a builder with [name] in a |
| /// [project]. |
| Future<TestResult> forBuild(String project, String name, int buildNumber, |
| {CreateCacheFunction createCache}) async { |
| logger.info('Querying $name for logs in $buildNumber...'); |
| Iterable<BuildStepTestResult> steps = await fromPrefix( |
| "chromium", "bb/client.dart/$name/$buildNumber", false, |
| createCache: createCache); |
| return steps.fold<TestResult>(new TestResult(), (acc, buildStep) { |
| return acc..combineWith([buildStep.testResult]); |
| }); |
| } |
| |
| /// Get latest test-result for a builder group with [name]. |
| /// TODO(mkroghj): Needs project to allow for FYI. |
| Future<TestResult> forBuilderGroup(String name, |
| {CreateCacheFunction createCache}) async { |
| var cacheCreater = createCache ?? standardCache; |
| var cache = cacheCreater(duration: new Duration(days: 1)); |
| |
| LuciApi luciApi = new LuciApi(); |
| logger.info("Getting builders in builder-group $name."); |
| List<String> builders = |
| await getBuildersInBuilderGroup(luciApi, "client.dart", cache, name) |
| .whenComplete(() => luciApi.close()); |
| var buildNumbers = await latestBuildNumbersForBuilders(builders); |
| var testResults = await Future.wait(builders.map((builder) { |
| int buildNumber = buildNumbers[builder]; |
| if (buildNumber == 0) { |
| return new Future.value(new TestResult()); |
| } |
| return forBuild(BUILDER_PROJECT, builder, buildNumber, |
| createCache: createCache) |
| .catchError(errorLogger( |
| logger, |
| "Could not get log for builder $builder " |
| "with build number $buildNumber", |
| new TestResult())); |
| })); |
| return new TestResult()..combineWith(testResults); |
| } |
| |
| /// Gets [BuildBucketTestResult]s by querying logdog for information, coming |
| /// from [builds]. |
| Future<Iterable<BuildBucketTestResult>> fromBuildBucketBuilds( |
| Iterable<BuildBucketBuild> builds, |
| {CreateCacheFunction createCache}) async { |
| Iterable<Iterable<BuildStepTestResult>> testResults = |
| await Future.wait(builds.map((build) { |
| logger.debug('Querying ${build.builder} for logs...'); |
| String prefix = "buildbucket/cr-buildbucket.appspot.com/${build.id}"; |
| return fromPrefix("dart", prefix, true, createCache: createCache) |
| .catchError(errorLogger(logger, null, [])); |
| })); |
| |
| return zipWith(builds, testResults, |
| (BuildBucketBuild build, Iterable<BuildStepTestResult> steps) { |
| TestResult result = steps.fold(new TestResult(), (acc, buildStep) { |
| return acc..combineWith([buildStep.testResult]); |
| }); |
| return new BuildBucketTestResult(build)..combineWith([result]); |
| }); |
| } |
| |
| /// Gets [BuildStepTestResult]s by querying logdog for information from |
| /// a given [prefix]. |
| Future<Iterable<BuildStepTestResult>> fromPrefix( |
| String project, String prefix, bool buildBucket, |
| {CreateCacheFunction createCache}) async { |
| var cacheCreater = createCache ?? standardCache; |
| var cache = cacheCreater(duration: new Duration(minutes: 1)); |
| var longCache = cacheCreater(duration: new Duration(days: 365)); |
| LogdogRpc logdog = new LogdogRpc(); |
| String recipes = buildBucket ? "" : "recipes/"; |
| List<LogdogStream> streams = await logdog.query( |
| project, "$prefix/+/${recipes}steps/**/result.log/0", cache); |
| RegExp stepNameRegExp = new RegExp( |
| r"^.*\/steps\/read_results_of_(.*)\/0\/logs\/result.log\/.*"); |
| RegExp stepNameShardRegExp = |
| new RegExp(r"^.*\/steps\/(.*)\/0\/logs\/result.log\/.*"); |
| return await Future.wait(streams.map((stream) async { |
| try { |
| var match = stepNameRegExp.firstMatch(stream.path); |
| match ??= stepNameShardRegExp.firstMatch(stream.path); |
| String name = match.group(1); |
| List<TestResult> results = |
| await fromStreams(project, [stream], longCache); |
| return new BuildStepTestResult(name.replaceAll("_", " "), results[0]); |
| } catch (ex) { |
| return new BuildStepTestResult("TEST", new TestResult()); |
| } |
| })); |
| } |
| |
| /// Gets [BuildBucketTestResult]s from a specific from [swarmTaskId]. |
| Future<Iterable<BuildBucketTestResult>> getFromSwarmingTaskId( |
| String swarmTaskId, |
| {CreateCacheFunction createCache}) { |
| return buildsFromSwarmingTaskId(swarmTaskId).then( |
| (builds) => fromBuildBucketBuilds(builds, createCache: createCache)); |
| } |
| |
| /// Gets [BuildBucketTestResult]s from a Gerrit CL with [changeNumber] and |
| /// [patchset]. |
| Future<Iterable<BuildBucketTestResult>> fromGerrit( |
| int changeNumber, int patchset, |
| {CreateCacheFunction createCache}) { |
| // First get builds from the buildbucket. |
| return buildsFromGerrit(changeNumber, patchset).then( |
| (builds) => fromBuildBucketBuilds(builds, createCache: createCache)); |
| } |
| |
| /// Reads the test result from a [file]. |
| Future<TestResult> getFromFile(File file) { |
| return file |
| .readAsString() |
| .then(jsonDecode) |
| .then((json) => new TestResult.fromJson(json)); |
| } |
| } |
| |
| /// Class that keeps track of a try build and the corresponding test result. |
| class BuildBucketTestResult extends TestResult { |
| final BuildBucketBuild build; |
| BuildBucketTestResult(this.build); |
| } |
| |
| /// Class that keeps track of a test step and test result. |
| class BuildStepTestResult { |
| final String name; |
| final TestResult testResult; |
| BuildStepTestResult(this.name, this.testResult); |
| } |