diff --git a/tools/gardening/bin/results_get.dart b/tools/gardening/bin/results_get.dart index 7ac6c98bef2..64f589044ab 100644 --- a/tools/gardening/bin/results_get.dart +++ b/tools/gardening/bin/results_get.dart @@ -2,18 +2,17 @@ // 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:async'; -import 'dart:io'; import 'package:args/command_runner.dart'; import 'package:args/args.dart'; -import 'package:gardening/src/luci.dart'; -import 'package:gardening/src/luci_api.dart'; import 'package:gardening/src/results/status_expectations.dart'; import 'package:gardening/src/results/status_files.dart'; +import 'package:gardening/src/results/test_result_helper.dart'; import 'package:gardening/src/results/test_result_service.dart'; import 'package:gardening/src/util.dart'; import 'package:gardening/src/console_table.dart'; -import 'package:gardening/src/results/result_models.dart' as models; -import 'package:gardening/src/results/util.dart'; +import 'package:gardening/src/results/result_json_models.dart' as models; +import 'package:gardening/src/logdog.dart'; +import 'package:gardening/src/logdog_rpc.dart'; import 'package:gardening/src/buildbucket.dart'; import 'package:gardening/src/extended_printer.dart'; @@ -34,17 +33,6 @@ OutputTable getOutputTable(ArgResults argResults) { return new ConsoleTable(template: rows); } -/// Determine if arguments is a CQ url or commit-number + patchset. -bool isCqInput(ArgResults argResults) { - if (argResults.rest.length == 1) { - return isSwarmingTaskUrl(argResults.rest.first); - } - if (argResults.rest.length == 2) { - return areNumbers(argResults.rest); - } - return false; -} - String howToUse(String command) { return "Use by calling one of the following:\n\n" "\tget $command : for a local result.log file.\n" @@ -56,93 +44,6 @@ String howToUse(String command) { "\tget $command : for a builder group.\n"; } -/// Utility method to get a single test-result no matter what has been passed in -/// as arguments. The test-result can either be from a builder-group, a single -/// build on a builder or from a log. -Future getTestResult(ArgResults argResults) async { - if (argResults.rest.length == 0) { - print("No result.log file given as argument."); - print(howToUse(argResults.name)); - return null; - } - - var logger = createLogger(); - var cache = createCacheFunction(logger); - var testResultService = new TestResultService(logger, cache); - - String firstArgument = argResults.rest.first; - - var luciApi = new LuciApi(); - bool isBuilderGroup = (await getBuilderGroups(luciApi, DART_CLIENT, cache())) - .any((builder) => builder == firstArgument); - bool isBuilder = (await getAllBuilders(luciApi, DART_CLIENT, cache())) - .any((builder) => builder == firstArgument); - - if (argResults.rest.length == 1) { - if (argResults.rest.first.startsWith("http")) { - return testResultService.fromLogdog(firstArgument); - } else if (isBuilderGroup) { - return testResultService.forBuilderGroup(firstArgument); - } else if (isBuilder) { - return testResultService.latestForBuilder(BUILDER_PROJECT, firstArgument); - } - } - - var file = new File(argResults.rest.first); - if (await file.exists()) { - return testResultService.getFromFile(file); - } - - if (argResults.rest.length == 2 && - isBuilder && - isNumber(argResults.rest[1])) { - var buildNumber = int.parse(argResults.rest[1]); - return testResultService.forBuild( - BUILDER_PROJECT, argResults.rest[0], buildNumber); - } - - print("Too many arguments passed to command or arguments were incorrect."); - print(howToUse(argResults.name)); - return null; -} - -/// Utility method to get test results from the CQ. -Future> getTestResultsFromCq( - ArgResults argResults) async { - if (argResults.rest.length == 0) { - print("No result.log file given as argument."); - print(howToUse(argResults.name)); - return null; - } - - var logger = createLogger(); - var createCache = createCacheFunction(logger); - var testResultService = new TestResultService(logger, createCache); - - String firstArgument = argResults.rest.first; - - if (argResults.rest.length == 1) { - if (!isSwarmingTaskUrl(firstArgument)) { - print("URI does not match " - "`https://ci.chromium.org/swarming/task/?server...`."); - print(howToUse(argResults.name)); - return null; - } - String swarmingTaskId = getSwarmingTaskId(firstArgument); - return await testResultService.getFromSwarmingTaskId(swarmingTaskId); - } - - if (argResults.rest.length == 2 && areNumbers(argResults.rest)) { - int changeNumber = int.parse(firstArgument); - int patchset = int.parse(argResults.rest.last); - return await testResultService.fromGerrit(changeNumber, patchset); - } - - print("Too many arguments passed to command or arguments were incorrect."); - print(howToUse(argResults.name)); - return null; -} - /// [GetCommand] handles when given command 'get' and expect a sub-command. class GetCommand extends Command { @override @@ -162,7 +63,8 @@ class GetCommand extends Command { /// returns a list of tests with their respective results. class GetTestsWithResultCommand extends Command { @override - String get description => "Get results for tests."; + String get description => "Get a list of tests with their respective " + "results from result.logs found from input."; @override String get name => "tests"; @@ -172,8 +74,9 @@ class GetTestsWithResultCommand extends Command { } Future run() async { - models.TestResult testResults = await getTestResult(argResults); + models.TestResult testResults = await getTestResult(argResults.rest); if (testResults == null) { + print(howToUse("tests")); return; } var outputTable = getOutputTable(argResults) @@ -191,10 +94,11 @@ class GetTestsWithResultCommand extends Command { /// 'result' and returns a list of tests with their result and expectations. class GetTestsWithResultAndExpectationCommand extends Command { @override - String get description => "Get results and expectations for tests."; + String get description => "Get a list of tests with their respective " + "results and expectations from result.logs found from input."; @override - String get name => "results"; + String get name => "tests-with-expectations"; GetTestsWithResultAndExpectationCommand() { buildArgs(argParser); @@ -203,17 +107,20 @@ class GetTestsWithResultAndExpectationCommand extends Command { Future run() async { models.TestResult testResult = null; - if (isCqInput(argResults)) { + if (isCqInput(argResults.rest)) { Iterable buildBucketTestResults = - await getTestResultsFromCq(argResults); - Iterable testResults = - buildBucketTestResults.map((build) => build.testResult); - testResult = new models.TestResult()..combineWith(testResults); + await getTestResultsFromCq(argResults.rest); + if (buildBucketTestResults != null) { + testResult = buildBucketTestResults.fold( + new models.TestResult(), + (combined, buildResult) => combined..combineWith([buildResult])); + } } else { - testResult = await getTestResult(argResults); + testResult = await getTestResult(argResults.rest); } if (testResult == null) { + print(howToUse("results")); return; } @@ -241,7 +148,8 @@ class GetTestsWithResultAndExpectationCommand extends Command { /// returns only the failing tests. class GetTestFailuresCommand extends Command { @override - String get description => "Get failures of tests."; + String get description => "Get a list of tests with their respective " + "results and expectations from result.logs found from input."; @override String get name => "failures"; @@ -250,52 +158,38 @@ class GetTestFailuresCommand extends Command { buildArgs(argParser); } - Future run() { - if (isCqInput(argResults)) { - return handleCqInput(argResults); + Future run() async { + List testResults = []; + if (isCqInput(argResults.rest)) { + var buildBucketResults = await getTestResultsFromCq(argResults.rest); + if (buildBucketResults == null) { + print(howToUse("failures")); + return; + } + testResults.addAll(buildBucketResults); } else { - return handleBuildbotInput(argResults); - } - } - - Future handleCqInput(ArgResults argResults) async { - Iterable buildBucketTestResults = - await getTestResultsFromCq(argResults); - - if (buildBucketTestResults == null) { - return; + var testResult = await getTestResult(argResults.rest); + if (testResult == null) { + print(howToUse("failures")); + return; + } + testResults.add(testResult); } print("All result logs fetched."); print("Calling test.py to find statuses for each test."); print(""); - for (var buildResult in buildBucketTestResults) { - printBuild(buildResult.build); + for (var testResult in testResults) { + if (testResult is BuildBucketTestResult) { + printBuild(testResult.build); + } List results = - await getTestResultsWithExpectation(buildResult.testResult); + await getTestResultsWithExpectation(testResult); printFailingTestExpectationResults(results); print(""); } } - - Future handleBuildbotInput(ArgResults argResults) async { - models.TestResult testResult = await getTestResult(argResults); - - if (testResult == null) { - return; - } - - print("All result logs fetched."); - var estimatedTime = - new Duration(milliseconds: testResult.results.length * 100 ~/ 1000); - print("Calling test.py to find status files for the configuration and " - "the expectation for ${testResult.results.length} tests. "); - List withExpectations = - await getTestResultsWithExpectation(testResult); - printFailingTestExpectationResults(withExpectations); - print(""); - } } /// Prints a test result. diff --git a/tools/gardening/bin/results_list.dart b/tools/gardening/bin/results_list.dart index b05f0de468a..bf17290a2b3 100644 --- a/tools/gardening/bin/results_list.dart +++ b/tools/gardening/bin/results_list.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:gardening/src/results/configurations.dart'; -import 'package:gardening/src/results/result_models.dart' as models; +import 'package:gardening/src/results/result_json_models.dart' as models; import 'package:gardening/src/results/testpy_wrapper.dart'; /// Helper function to add all standard arguments to the [argParser]. diff --git a/tools/gardening/bin/results_status.dart b/tools/gardening/bin/results_status.dart index acfd07e7447..35f5afaf415 100644 --- a/tools/gardening/bin/results_status.dart +++ b/tools/gardening/bin/results_status.dart @@ -12,15 +12,11 @@ import 'package:gardening/src/logger.dart'; import 'package:gardening/src/luci.dart'; import 'package:gardening/src/luci_api.dart'; import 'package:gardening/src/results/configuration_environment.dart'; -import 'package:gardening/src/results/result_models.dart' as models; +import 'package:gardening/src/results/result_json_models.dart' as models; import 'package:gardening/src/results/status_files.dart'; import 'package:gardening/src/results/test_result_service.dart'; import 'package:gardening/src/results/testpy_wrapper.dart'; -import 'package:gardening/src/results/util.dart'; import 'package:gardening/src/util.dart'; -import 'package:gardening/src/workflow/workflow.dart'; - -import 'results_status_workflow.dart'; /// Class [StatusCommand] handles the 'status' subcommand and provides /// sub-commands for interacting with status files. @@ -33,7 +29,6 @@ class StatusCommand extends Command { StatusCommand() { addSubcommand(new CheckStatusCommand()); - addSubcommand(new UpdateStatusCommand()); } } @@ -201,19 +196,3 @@ class CheckStatusCommand extends Command { } } } - -/// Class [UpdateStatusCommand] handles the 'status update' subcommand and -/// updates status files. -class UpdateStatusCommand extends Command { - @override - String get description => "Update status files, from failure data and " - "existing status entries."; - - @override - String get name => "update"; - - Future run() async { - var workflow = new Workflow(); - return workflow.start(new AskForLogs()); - } -} diff --git a/tools/gardening/lib/src/buildbot_loading.dart b/tools/gardening/lib/src/buildbot_loading.dart index ea4e9e3ed89..6a55fdc1b8b 100644 --- a/tools/gardening/lib/src/buildbot_loading.dart +++ b/tools/gardening/lib/src/buildbot_loading.dart @@ -6,8 +6,6 @@ import 'dart:async'; import 'dart:io'; import 'cache_new.dart'; import 'logdog_rpc.dart'; -import 'results/util.dart'; - import 'util.dart'; import 'buildbot_structures.dart'; diff --git a/tools/gardening/lib/src/compare_failures_impl.dart b/tools/gardening/lib/src/compare_failures_impl.dart index fcef3d504fb..bc4c895fc61 100644 --- a/tools/gardening/lib/src/compare_failures_impl.dart +++ b/tools/gardening/lib/src/compare_failures_impl.dart @@ -7,7 +7,6 @@ /// Use this to detect flakiness of failures, especially timeouts. import 'dart:async'; -import 'dart:io'; import 'bot.dart'; import 'buildbot_structures.dart'; diff --git a/tools/gardening/lib/src/luci_api.dart b/tools/gardening/lib/src/luci_api.dart index f3242fc418a..2a448333eeb 100644 --- a/tools/gardening/lib/src/luci_api.dart +++ b/tools/gardening/lib/src/luci_api.dart @@ -91,7 +91,6 @@ class LuciApi { /// [_makeGetRequest] performs a get request to [uri]. Future _makeGetRequest(Uri uri) async { - String uriString = uri.toString(); var request = await _client.getUrl(uri); var response = await request.close(); if (response.statusCode != 200) { diff --git a/tools/gardening/lib/src/results/configuration_environment.dart b/tools/gardening/lib/src/results/configuration_environment.dart index ed50119b24f..ccc4fb14c95 100644 --- a/tools/gardening/lib/src/results/configuration_environment.dart +++ b/tools/gardening/lib/src/results/configuration_environment.dart @@ -7,7 +7,7 @@ // and also information about test-suites. import 'package:status_file/environment.dart'; -import 'result_models.dart'; +import 'result_json_models.dart'; import 'configurations.dart'; typedef String _LookUpFunction(Configuration configuration); @@ -45,6 +45,7 @@ final _variables = { "minified": new _Variable.bool((c) => c.minified), "mode": new _Variable((c) => c.mode, Mode.names), "runtime": new _Variable(_runtimeName, Runtime.names), + "spec_parser": new _Variable.bool((c) => c.compiler == Compiler.specParser), "strong": new _Variable.bool((c) => c.strong), "system": new _Variable((c) => c.system, System.names), "use_sdk": new _Variable.bool((c) => c.useSdk) diff --git a/tools/gardening/lib/src/results/result_models.dart b/tools/gardening/lib/src/results/result_json_models.dart similarity index 100% rename from tools/gardening/lib/src/results/result_models.dart rename to tools/gardening/lib/src/results/result_json_models.dart diff --git a/tools/gardening/lib/src/results/status_expectations.dart b/tools/gardening/lib/src/results/status_expectations.dart index 9cc09d5ba8e..450be5d457a 100644 --- a/tools/gardening/lib/src/results/status_expectations.dart +++ b/tools/gardening/lib/src/results/status_expectations.dart @@ -7,9 +7,9 @@ import 'dart:async'; import 'package:gardening/src/results/configuration_environment.dart'; import 'package:gardening/src/results/status_files.dart'; -import 'result_models.dart'; +import 'result_json_models.dart'; import 'testpy_wrapper.dart'; -import 'util.dart'; +import '../util.dart'; import 'package:status_file/expectation.dart'; /// Finds the expectation for each test found in the [testResult] results and diff --git a/tools/gardening/lib/src/results/status_files.dart b/tools/gardening/lib/src/results/status_files.dart index 65e11cfde12..722c8cd8e2a 100644 --- a/tools/gardening/lib/src/results/status_files.dart +++ b/tools/gardening/lib/src/results/status_files.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'package:gardening/src/results/configuration_environment.dart'; -import 'package:gardening/src/util.dart'; import 'package:status_file/status_file.dart'; class StatusFiles { diff --git a/tools/gardening/lib/src/results/test_result_helper.dart b/tools/gardening/lib/src/results/test_result_helper.dart new file mode 100644 index 00000000000..a8c073ebdad --- /dev/null +++ b/tools/gardening/lib/src/results/test_result_helper.dart @@ -0,0 +1,92 @@ +// 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:async'; +import 'dart:io'; + +import '../luci.dart'; +import '../luci_api.dart'; +import '../util.dart'; +import 'result_json_models.dart'; +import 'test_result_service.dart'; + +/// Utility method to get a single test-result no matter what has been passed in +/// as arguments. The test-result can either be from a builder-group, a single +/// build on a builder or from a log. +Future getTestResult(List arguments) async { + if (arguments.isEmpty) { + print("No result.log file given as argument."); + return null; + } + + var logger = createLogger(); + var cache = createCacheFunction(logger); + var testResultService = new TestResultService(logger, cache); + + String firstArgument = arguments.first; + + var luciApi = new LuciApi(); + bool isBuilderGroup = (await getBuilderGroups(luciApi, DART_CLIENT, cache())) + .any((builder) => builder == firstArgument); + bool isBuilder = (await getAllBuilders(luciApi, DART_CLIENT, cache())) + .any((builder) => builder == firstArgument); + + if (arguments.length == 1) { + if (arguments.first.startsWith("http")) { + return testResultService.fromLogdog(firstArgument); + } else if (isBuilderGroup) { + return testResultService.forBuilderGroup(firstArgument); + } else if (isBuilder) { + return testResultService.latestForBuilder(BUILDER_PROJECT, firstArgument); + } + } + + var file = new File(arguments.first); + if (await file.exists()) { + return testResultService.getFromFile(file); + } + + if (arguments.length == 2 && isBuilder && isNumber(arguments.last)) { + var buildNumber = int.parse(arguments.last); + return testResultService.forBuild( + BUILDER_PROJECT, firstArgument, buildNumber); + } + + print("Too many arguments passed to command or arguments were incorrect."); + return null; +} + +/// Utility method to get test results from the CQ. +Future> getTestResultsFromCq( + List arguments) async { + if (arguments.isEmpty) { + print("No result.log file given as argument."); + return null; + } + + var logger = createLogger(); + var createCache = createCacheFunction(logger); + var testResultService = new TestResultService(logger, createCache); + + String firstArgument = arguments.first; + + if (arguments.length == 1) { + if (!isSwarmingTaskUrl(firstArgument)) { + print("URI does not match " + "`https://ci.chromium.org/swarming/task/?server...`."); + return null; + } + String swarmingTaskId = getSwarmingTaskId(firstArgument); + return await testResultService.getFromSwarmingTaskId(swarmingTaskId); + } + + if (arguments.length == 2 && areNumbers(arguments)) { + int changeNumber = int.parse(firstArgument); + int patchset = int.parse(arguments.last); + return await testResultService.fromGerrit(changeNumber, patchset); + } + + print("Too many arguments passed to command or arguments were incorrect."); + return null; +} diff --git a/tools/gardening/lib/src/results/test_result_service.dart b/tools/gardening/lib/src/results/test_result_service.dart index 017ff36eefa..f6a56d7bdbc 100644 --- a/tools/gardening/lib/src/results/test_result_service.dart +++ b/tools/gardening/lib/src/results/test_result_service.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'dart:async'; import 'dart:convert'; import 'dart:core'; -import 'result_models.dart'; +import 'result_json_models.dart'; import '../logger.dart'; import '../cache_new.dart'; import '../logdog.dart'; @@ -14,7 +14,6 @@ import '../logdog_rpc.dart'; import '../luci_api.dart'; import '../luci.dart'; import '../buildbucket.dart'; -import 'util.dart'; import '../util.dart'; /// [TestResultService] provides functions to obtain [TestResult]s from logs. @@ -166,7 +165,7 @@ class TestResultService { TestResult result = steps.fold(new TestResult(), (acc, buildStep) { return acc..combineWith([buildStep.testResult]); }); - return new BuildBucketTestResult(build, result); + return new BuildBucketTestResult(build)..combineWith([result]); }); } @@ -228,10 +227,9 @@ class TestResultService { } /// Class that keeps track of a try build and the corresponding test result. -class BuildBucketTestResult { +class BuildBucketTestResult extends TestResult { final BuildBucketBuild build; - final TestResult testResult; - BuildBucketTestResult(this.build, this.testResult); + BuildBucketTestResult(this.build); } /// Class that keeps track of a test step and test result. diff --git a/tools/gardening/lib/src/results/testpy_wrapper.dart b/tools/gardening/lib/src/results/testpy_wrapper.dart index 2ec633f9834..29fb99d9ba5 100644 --- a/tools/gardening/lib/src/results/testpy_wrapper.dart +++ b/tools/gardening/lib/src/results/testpy_wrapper.dart @@ -2,11 +2,9 @@ // 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 'package:path/path.dart' as path; -import 'result_models.dart'; -import 'util.dart'; +import 'result_json_models.dart'; import '../util.dart'; /// Calls test.py with arguments gathered from a specific [configuration] and diff --git a/tools/gardening/lib/src/results/util.dart b/tools/gardening/lib/src/results/util.dart deleted file mode 100644 index b21694857c5..00000000000 --- a/tools/gardening/lib/src/results/util.dart +++ /dev/null @@ -1,92 +0,0 @@ -// 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 'result_models.dart'; -import 'dart:io'; - -const String BUILDER_PROJECT = "chromium"; - -/// [PathHelper] is a utility class holding information about static paths. -class PathHelper { - static String testPyPath() { - var root = sdkRepositoryRoot(); - return "${root}/tools/test.py"; - } - - static String _sdkRepositoryRoot; - static String sdkRepositoryRoot() { - return _sdkRepositoryRoot ??= - _findRoot(new Directory.fromUri(Platform.script)); - } - - static String _findRoot(Directory current) { - if (current.path.endsWith("sdk")) { - return current.path; - } - if (current.parent == null) { - print("Could not find the dart sdk folder. " - "Please run the tool in the root of the dart-sdk local repository."); - exit(1); - } - return _findRoot(current.parent); - } -} - -/// Tests if all strings passed in [stringsToTest] are integers. -bool areNumbers(Iterable stringsToTest) { - RegExp isNumberRegExp = new RegExp(r"^\d+$"); - return stringsToTest - .every((string) => isNumberRegExp.firstMatch(string) != null); -} - -bool isNumber(String stringToTest) { - bool succeeded = true; - int.parse(stringToTest, onError: (String) { - succeeded = false; - return 0; - }); - return succeeded; -} - -/// Gets if the [url] is a swarming task url. -bool isSwarmingTaskUrl(String url) { - return url.startsWith("https://ci.chromium.org/swarming"); -} - -/// Gets the swarming task id from the [url]. -String getSwarmingTaskId(String url) { - RegExp swarmingTaskIdInPathRegExp = - new RegExp(r"https:\/\/ci\.chromium\.org\/swarming\/task\/(.*)\?server"); - Match swarmingTaskIdMatch = swarmingTaskIdInPathRegExp.firstMatch(url); - if (swarmingTaskIdMatch == null) { - return null; - } - return swarmingTaskIdMatch.group(1); -} - -/// Returns the test-suite for [name]. -String getSuiteNameForTest(String name) { - var reg = new RegExp(r"^(.*?)\/.*$"); - var match = reg.firstMatch(name); - if (match == null) { - return null; - } - return match.group(1); -} - -/// Returns the qualified name (what to use in status-files) for a test with -/// [name]. -String getQualifiedNameForTest(String name) { - if (name.startsWith("cc/")) { - return name; - } - return name.substring(name.indexOf("/") + 1); -} - -/// Returns the reproduction command for test.py based on the [configuration] -/// and [name]. -String getReproductionCommand(Configuration configuration, String name) { - var allArgs = configuration.toArgs(includeSelectors: false)..add(name); - return "${PathHelper.testPyPath()} ${allArgs.join(' ')}"; -} diff --git a/tools/gardening/lib/src/try.dart b/tools/gardening/lib/src/try.dart index ebe8f09bb32..32b47f25f47 100644 --- a/tools/gardening/lib/src/try.dart +++ b/tools/gardening/lib/src/try.dart @@ -2,8 +2,6 @@ // 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:async'; - /// [Try] is similar to Haskell monad, where /// a computation may throw an exception. /// There is no checking of passing null into diff --git a/tools/gardening/lib/src/util.dart b/tools/gardening/lib/src/util.dart index a608f62717e..d0b44cd7e53 100644 --- a/tools/gardening/lib/src/util.dart +++ b/tools/gardening/lib/src/util.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:args/args.dart'; +import 'results/result_json_models.dart'; import 'cache.dart'; import 'cache_new.dart'; @@ -246,3 +247,100 @@ Future runPython(String script, List args) { /// Regular expression matches a Linux or Windows new line character. final RegExp newLine = new RegExp(r'\r\n|\n'); + +/// Determine if arguments is a CQ url or commit-number + patchset. +bool isCqInput(List arguments) { + if (arguments.length == 1) { + return isSwarmingTaskUrl(arguments.first); + } + if (arguments.length == 2) { + return areNumbers(arguments); + } + return false; +} + +const String BUILDER_PROJECT = "chromium"; + +/// [PathHelper] is a utility class holding information about static paths. +class PathHelper { + static String testPyPath() { + var root = sdkRepositoryRoot(); + return "${root}/tools/test.py"; + } + + static String _sdkRepositoryRoot; + static String sdkRepositoryRoot() { + return _sdkRepositoryRoot ??= + _findRoot(new Directory.fromUri(Platform.script)); + } + + static String _findRoot(Directory current) { + if (current.path.endsWith("sdk")) { + return current.path; + } + if (current.parent == null) { + print("Could not find the dart sdk folder. " + "Please run the tool in the root of the dart-sdk local repository."); + exit(1); + } + return _findRoot(current.parent); + } +} + +/// Tests if all strings passed in [stringsToTest] are integers. +bool areNumbers(Iterable stringsToTest) { + RegExp isNumberRegExp = new RegExp(r"^\d+$"); + return stringsToTest + .every((string) => isNumberRegExp.firstMatch(string) != null); +} + +bool isNumber(String stringToTest) { + bool succeeded = true; + int.parse(stringToTest, onError: (String) { + succeeded = false; + return 0; + }); + return succeeded; +} + +/// Gets if the [url] is a swarming task url. +bool isSwarmingTaskUrl(String url) { + return url.startsWith("https://ci.chromium.org/swarming"); +} + +/// Gets the swarming task id from the [url]. +String getSwarmingTaskId(String url) { + RegExp swarmingTaskIdInPathRegExp = + new RegExp(r"https:\/\/ci\.chromium\.org\/swarming\/task\/(.*)\?server"); + Match swarmingTaskIdMatch = swarmingTaskIdInPathRegExp.firstMatch(url); + if (swarmingTaskIdMatch == null) { + return null; + } + return swarmingTaskIdMatch.group(1); +} + +/// Returns the test-suite for [name]. +String getSuiteNameForTest(String name) { + var reg = new RegExp(r"^(.*?)\/.*$"); + var match = reg.firstMatch(name); + if (match == null) { + return null; + } + return match.group(1); +} + +/// Returns the qualified name (what to use in status-files) for a test with +/// [name]. +String getQualifiedNameForTest(String name) { + if (name.startsWith("cc/")) { + return name; + } + return name.substring(name.indexOf("/") + 1); +} + +/// Returns the reproduction command for test.py based on the [configuration] +/// and [name]. +String getReproductionCommand(Configuration configuration, String name) { + var allArgs = configuration.toArgs(includeSelectors: false)..add(name); + return "${PathHelper.testPyPath()} ${allArgs.join(' ')}"; +} diff --git a/tools/gardening/lib/src/workflow/workflow.dart b/tools/gardening/lib/src/workflow.dart similarity index 100% rename from tools/gardening/lib/src/workflow/workflow.dart rename to tools/gardening/lib/src/workflow.dart