| // Copyright (c) 2022, 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 'package:testing/src/chain.dart' show CreateContext; |
| import 'package:testing/src/log.dart' show Logger, StdoutLogger; |
| import 'package:testing/src/suite.dart' as testing show Suite; |
| import 'package:testing/testing.dart' show Step, TestDescription; |
| |
| import '../coverage_helper.dart'; |
| import '../testing/suite.dart'; |
| |
| const String limitToArgument = "--limitTo="; |
| |
| Future<void> internalMain( |
| CreateContext createContext, { |
| List<String> arguments = const [], |
| int? shards, |
| int? shard, |
| required String displayName, |
| required String configurationPath, |
| }) async { |
| Logger logger = const StdoutLogger(); |
| List<String>? argumentsTrimmed; |
| Uri? coverageUri; |
| int? limitTo; |
| for (int i = 0; i < arguments.length; i++) { |
| String argument = arguments[i]; |
| bool trimmed = false; |
| if (argument == "--traceStepTiming") { |
| logger = new TracingLogger(); |
| trimmed = true; |
| } else if (argument.startsWith("--coverage=")) { |
| coverageUri = Uri.base.resolveUri( |
| Uri.file(argument.substring("--coverage=".length)), |
| ); |
| trimmed = true; |
| } else if (argument.startsWith("--shards=")) { |
| shards = int.parse(argument.substring("--shards=".length)); |
| trimmed = true; |
| } else if (argument.startsWith("--shard=")) { |
| // Have this 1-indexed when given as an input. |
| shard = int.parse(argument.substring("--shard=".length)) - 1; |
| trimmed = true; |
| } else if (argument.startsWith(limitToArgument)) { |
| limitTo = int.tryParse(argument.substring(limitToArgument.length)); |
| trimmed = true; |
| } |
| |
| if (trimmed && argumentsTrimmed == null) { |
| argumentsTrimmed = []..addAll(arguments.sublist(0, i)); |
| } else if (!trimmed && argumentsTrimmed != null) { |
| argumentsTrimmed.add(argument); |
| } |
| } |
| if (argumentsTrimmed != null) { |
| arguments = argumentsTrimmed; |
| } |
| |
| shards ??= 1; |
| shard ??= 0; |
| |
| await runMe( |
| arguments, |
| createContext, |
| configurationPath: configurationPath, |
| shards: shards, |
| shard: shard, |
| limitTo: limitTo, |
| logger: logger, |
| ); |
| if (coverageUri != null) { |
| File f = new File.fromUri( |
| coverageUri.resolve("$displayName.$shard.$shards.coverage"), |
| ); |
| // Suites generally takes a while to run --- so setting force compile to |
| // true shouldn't be a big issue. It seems to add something like a second |
| // to the collection time. |
| (await collectCoverage( |
| displayName: displayName, |
| forceCompile: true, |
| ))?.writeToFile(f); |
| } |
| } |
| |
| class TracingLogger extends StdoutLogger { |
| Map<TestDescription, Map<Step, Stopwatch>> stopwatches = {}; |
| |
| @override |
| void logStepStart( |
| int completed, |
| int failed, |
| int total, |
| testing.Suite? suite, |
| TestDescription description, |
| Step step, |
| ) { |
| Map<Step, Stopwatch> map = stopwatches[description] ??= {}; |
| Stopwatch stopwatch = map[step] ??= new Stopwatch(); |
| if (stopwatch.isRunning) throw "unexpectedly already running @ $step"; |
| stopwatch.start(); |
| super.logStepStart(completed, failed, total, suite, description, step); |
| } |
| |
| @override |
| void logStepComplete( |
| int completed, |
| int failed, |
| int total, |
| testing.Suite? suite, |
| TestDescription description, |
| Step step, |
| ) { |
| Map<Step, Stopwatch> map = stopwatches[description]!; |
| Stopwatch stopwatch = map[step] = map[step]!; |
| if (!stopwatch.isRunning) throw "unexpectedly not running"; |
| stopwatch.stop(); |
| super.logStepComplete(completed, failed, total, suite, description, step); |
| } |
| |
| @override |
| void logSuiteComplete(testing.Suite suite) { |
| Map<String, Duration> totalRuntimesForSteps = {}; |
| // Make sure not to overwrite existing text about number of tests run, |
| // failures etc. |
| print(""); |
| print(""); |
| |
| for (MapEntry<TestDescription, Map<Step, Stopwatch>> entryMap |
| in stopwatches.entries) { |
| for (MapEntry<Step, Stopwatch> entry in entryMap.value.entries) { |
| if (entry.value.isRunning) throw "unexpectedly already running"; |
| entry.value.elapsed + entry.value.elapsed; |
| totalRuntimesForSteps[entry.key.name] = |
| (totalRuntimesForSteps[entry.key.name] ?? new Duration()) + |
| entry.value.elapsed; |
| } |
| } |
| List<MapEntry<String, Duration>> entries = totalRuntimesForSteps.entries |
| .toList(); |
| entries.sort((a, b) => a.value.compareTo(b.value)); |
| for (MapEntry<String, Duration> entry in entries) { |
| const int maxLength = 25; |
| String key = (entry.key.length > maxLength) |
| ? entry.key.substring(0, maxLength) |
| : entry.key.padLeft(maxLength); |
| print("$key: ${entry.value} ms"); |
| } |
| super.logSuiteComplete(suite); |
| } |
| } |