diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md index 606d0d3ba..f1654aa3b 100644 --- a/pkgs/test/CHANGELOG.md +++ b/pkgs/test/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.27.0 + +* Add `--coverage-lcov` and `--branch-coverage` options to `dart test`. + ## 1.26.2 * Graduate native assets from experiment to preview. diff --git a/pkgs/test/README.md b/pkgs/test/README.md index 36103d9c1..ffee9e9eb 100644 --- a/pkgs/test/README.md +++ b/pkgs/test/README.md @@ -258,35 +258,27 @@ The available options for the `--reporter` flag are: ### Collecting Code Coverage To collect code coverage, you can run tests with the `--coverage ` -argument. The directory specified can be an absolute or relative path. +argument or the `--coverage-lcov ` argument. The `--coverage` option +outputs a json formatted report per test suite, whereas the `--coverage-lcov` +option merges all the test coverage into a single LCOV file. +The directory or file specified can be an absolute or relative path. If a directory does not exist at the path specified, a directory will be created. If a directory does exist, files may be overwritten with the latest coverage data, if they conflict. -This option will enable code coverage collection on a suite-by-suite basis, -and the resulting coverage files will be outputted in the directory specified. -The files can then be formatted using the `package:coverage` -`format_coverage` executable. - Coverage gathering is currently only implemented for tests run on the Dart VM or Chrome. Here's an example of how to run tests and format the collected coverage to LCOV: ```shell -## Run Dart tests and output them at directory `./coverage`: -dart run test --coverage=./coverage - -## Activate package `coverage` (if needed): -dart pub global activate coverage - -## Format collected coverage to LCOV (only for directory "lib") -dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage +## Run Dart tests and output coverage info to `./coverage/lcov.info`: +dart run test --coverage-lcov=./coverage/lcov.info -## Generate LCOV report: +## Generate a human readable report: genhtml -o ./coverage/report ./coverage/lcov.info -## Open the HTML coverage report: +## Open the coverage report: open ./coverage/report/index.html ``` diff --git a/pkgs/test/pubspec.yaml b/pkgs/test/pubspec.yaml index a62ae0e2f..9c3ddf657 100644 --- a/pkgs/test/pubspec.yaml +++ b/pkgs/test/pubspec.yaml @@ -1,5 +1,5 @@ name: test -version: 1.26.2 +version: 1.27.0 description: >- A full featured library for writing and running Dart tests across platforms. repository: https://github.com/dart-lang/test/tree/master/pkgs/test @@ -14,7 +14,7 @@ dependencies: async: ^2.5.0 boolean_selector: ^2.1.0 collection: ^1.15.0 - coverage: ^1.0.1 + coverage: ^1.15.0 http_multi_server: ^3.0.0 io: ^1.0.0 js: '>=0.6.4 <0.8.0' @@ -37,7 +37,7 @@ dependencies: # Use an exact version until the test_api and test_core package are stable. test_api: 0.7.6 - test_core: 0.6.11 + test_core: 0.6.12 typed_data: ^1.3.0 web_socket_channel: '>=2.0.0 <4.0.0' diff --git a/pkgs/test/test/io.dart b/pkgs/test/test/io.dart index b13f8bdda..66994d4fc 100644 --- a/pkgs/test/test/io.dart +++ b/pkgs/test/test/io.dart @@ -21,10 +21,6 @@ final Future packageDir = return dir; }); -/// The path to the `pub` executable in the current Dart SDK. -final _pubPath = p.absolute(p.join(p.dirname(Platform.resolvedExecutable), - Platform.isWindows ? 'pub.bat' : 'pub')); - /// The platform-specific message emitted when a nonexistent file is loaded. final String noSuchFileMessage = Platform.isWindows ? 'The system cannot find the file specified.' @@ -151,7 +147,8 @@ Future runDart(Iterable args, /// Runs Pub. Future runPub(Iterable args, {Map? environment}) { - return TestProcess.start(_pubPath, args, + return TestProcess.start( + p.absolute(Platform.resolvedExecutable), ['pub', ...args], workingDirectory: d.sandbox, environment: environment, description: 'pub ${args.first}'); diff --git a/pkgs/test/test/runner/coverage_test.dart b/pkgs/test/test/runner/coverage_test.dart index a49124ba8..ed0c2e600 100644 --- a/pkgs/test/test/runner/coverage_test.dart +++ b/pkgs/test/test/runner/coverage_test.dart @@ -8,6 +8,7 @@ library; import 'dart:convert'; import 'dart:io'; +import 'package:coverage/coverage.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; @@ -20,30 +21,63 @@ void main() { group('with the --coverage flag,', () { late Directory coverageDirectory; + late d.DirectoryDescriptor packageDirectory; - Future validateCoverage(TestProcess test, String coveragePath) async { + Future validateTest(TestProcess test) async { expect(test.stdout, emitsThrough(contains('+1: All tests passed!'))); await test.shouldExit(0); + } + + Future> validateCoverage( + TestProcess test, String coveragePath) async { + await validateTest(test); final coverageFile = File(p.join(coverageDirectory.path, coveragePath)); final coverage = await coverageFile.readAsString(); - final jsonCoverage = json.decode(coverage); - expect(jsonCoverage['coverage'], isNotEmpty); + final jsonCoverage = json.decode(coverage)['coverage'] as List; + expect(jsonCoverage, isNotEmpty); + + return HitMap.parseJson(jsonCoverage.cast>()); } setUp(() async { - await d.file('test.dart', ''' - import 'package:test/test.dart'; - - void main() { - test("test 1", () { - expect(true, isTrue); - }); - } - ''').create(); - coverageDirectory = await Directory.systemTemp.createTemp('test_coverage'); + + packageDirectory = d.dir(d.sandbox, [ + d.dir('lib', [ + d.file('calculate.dart', ''' + int calculate(int x) { + if (x % 2 == 0) { + return x * 2; + } else { + return x * 3; + } + } + '''), + ]), + d.dir('test', [ + d.file('test.dart', ''' + import 'package:fake_package/calculate.dart'; + import 'package:test/test.dart'; + + void main() { + test('test 1', () { + expect(calculate(6), 12); + }); + } + ''') + ]), + d.file('pubspec.yaml', ''' +name: fake_package +version: 1.0.0 +environment: + sdk: ^3.5.0 +dev_dependencies: + test: ^1.26.2 + '''), + ]); + await packageDirectory.create(); }); tearDown(() async { @@ -51,15 +85,62 @@ void main() { }); test('gathers coverage for VM tests', () async { - var test = - await runTest(['--coverage', coverageDirectory.path, 'test.dart']); - await validateCoverage(test, 'test.dart.vm.json'); + await (await runPub(['get'])).shouldExit(0); + var test = await runTest( + ['--coverage', coverageDirectory.path, 'test/test.dart'], + packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json')); + final coverage = await validateCoverage(test, 'test/test.dart.vm.json'); + final hitmap = coverage['package:fake_package/calculate.dart']!; + expect(hitmap.lineHits, {1: 1, 2: 2, 3: 1, 5: 0}); + expect(hitmap.funcHits, isNull); + expect(hitmap.branchHits, isNull); + }); + + test('gathers branch coverage for VM tests', () async { + await (await runPub(['get'])).shouldExit(0); + var test = await runTest([ + '--coverage', + coverageDirectory.path, + '--branch-coverage', + 'test/test.dart' + ], vmArgs: [ + '--branch-coverage' + ], packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json')); + final coverage = await validateCoverage(test, 'test/test.dart.vm.json'); + final hitmap = coverage['package:fake_package/calculate.dart']!; + expect(hitmap.lineHits, {1: 1, 2: 2, 3: 1, 5: 0}); + expect(hitmap.funcHits, isNull); + expect(hitmap.branchHits, {1: 1, 2: 1, 4: 0}); + }); + + test('gathers lcov coverage for VM tests', () async { + await (await runPub(['get'])).shouldExit(0); + final lcovFile = p.join(coverageDirectory.path, 'lcov.info'); + var test = await runTest(['--coverage-lcov', lcovFile, 'test/test.dart'], + packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json')); + await validateTest(test); + expect(File(lcovFile).readAsStringSync(), ''' +SF:${p.join(d.sandbox, 'lib', 'calculate.dart')} +DA:1,1 +DA:2,2 +DA:3,1 +DA:5,0 +LF:4 +LH:3 +end_of_record +'''); }); test('gathers coverage for Chrome tests', () async { - var test = await runTest( - ['--coverage', coverageDirectory.path, 'test.dart', '-p', 'chrome']); - await validateCoverage(test, 'test.dart.chrome.json'); + await (await runPub(['get'])).shouldExit(0); + var test = await runTest([ + '--coverage', + coverageDirectory.path, + 'test/test.dart', + '-p', + 'chrome' + ], packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json')); + await validateCoverage(test, 'test/test.dart.chrome.json'); }); test( diff --git a/pkgs/test/test/runner/runner_test.dart b/pkgs/test/test/runner/runner_test.dart index 379e2a7f1..b08c8f38d 100644 --- a/pkgs/test/test/runner/runner_test.dart +++ b/pkgs/test/test/runner/runner_test.dart @@ -92,6 +92,10 @@ $_runtimeCompilers --debug Run the VM and Chrome tests in debug mode. --coverage= Gather coverage and output it to the specified directory. Implies --debug. + --coverage-lcov= Gather coverage and output an lcov report to the specified file. + Implies --debug. + --branch-coverage Include branch coverage information in the coverage report. + Must be paired with --coverage or --coverage-lcov. --[no-]chain-stack-traces Use chained stack traces to provide greater exception details especially for asynchronous code. It may be useful to disable to provide improved test performance but at the cost of diff --git a/pkgs/test/test/utils.dart b/pkgs/test/test/utils.dart index 23d5b7e14..25c367ed6 100644 --- a/pkgs/test/test/utils.dart +++ b/pkgs/test/test/utils.dart @@ -121,6 +121,7 @@ Engine declareEngine( void Function() body, { bool runSkipped = false, String? coverage, + String? coverageLcov, bool stopOnFirstFailure = false, }) { var declarer = Declarer()..declare(body); @@ -133,6 +134,7 @@ Engine declareEngine( suitePlatform) ], coverage: coverage, + coverageLcov: coverageLcov, stopOnFirstFailure: stopOnFirstFailure, ); } @@ -199,6 +201,8 @@ Configuration configuration( String? reporter, Map? fileReporters, String? coverage, + String? coverageLcov, + bool? branchCoverage, int? concurrency, int? shardIndex, int? totalShards, @@ -249,6 +253,8 @@ Configuration configuration( reporter: reporter, fileReporters: fileReporters, coverage: coverage, + coverageLcov: coverageLcov, + branchCoverage: branchCoverage, concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md index 8029c3fe2..5e42ad96f 100644 --- a/pkgs/test_core/CHANGELOG.md +++ b/pkgs/test_core/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.12 + +* Add `--coverage-lcov` and `--branch-coverage` options to `dart test`. + ## 0.6.11 * Graduate native assets from experiment to preview. diff --git a/pkgs/test_core/lib/src/runner.dart b/pkgs/test_core/lib/src/runner.dart index af8e9c96d..51aa1d139 100644 --- a/pkgs/test_core/lib/src/runner.dart +++ b/pkgs/test_core/lib/src/runner.dart @@ -77,6 +77,7 @@ class Runner { var engine = Engine( concurrency: config.concurrency, coverage: config.coverage, + coverageLcov: config.coverageLcov, testRandomizeOrderingSeed: config.testRandomizeOrderingSeed, stopOnFirstFailure: config.stopOnFirstFailure, ); diff --git a/pkgs/test_core/lib/src/runner/configuration.dart b/pkgs/test_core/lib/src/runner/configuration.dart index 8ba4aa810..ebc302f9b 100644 --- a/pkgs/test_core/lib/src/runner/configuration.dart +++ b/pkgs/test_core/lib/src/runner/configuration.dart @@ -58,12 +58,23 @@ class Configuration { final bool? _pauseAfterLoad; /// Whether to run browsers in their respective debug modes - bool get debug => pauseAfterLoad || (_debug ?? false) || coverage != null; + bool get debug => + pauseAfterLoad || + (_debug ?? false) || + coverage != null || + coverageLcov != null; final bool? _debug; - /// The output folder for coverage gathering + /// The output folder for coverage gathering. final String? coverage; + /// The lcov file to output coverage to. + final String? coverageLcov; + + /// Whether to collect branch coverage info. + bool get branchCoverage => _branchCoverage ?? false; + final bool? _branchCoverage; + /// The path to the file from which to load more configuration information. /// /// This is *not* resolved automatically. @@ -257,6 +268,8 @@ class Configuration { required String? reporter, required Map? fileReporters, required String? coverage, + required String? coverageLcov, + required bool? branchCoverage, required int? concurrency, required int? shardIndex, required int? totalShards, @@ -309,6 +322,8 @@ class Configuration { reporter: reporter, fileReporters: fileReporters, coverage: coverage, + coverageLcov: coverageLcov, + branchCoverage: branchCoverage, concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, @@ -366,6 +381,8 @@ class Configuration { String? reporter, Map? fileReporters, String? coverage, + String? coverageLcov, + bool? branchCoverage, int? concurrency, int? shardIndex, int? totalShards, @@ -417,6 +434,8 @@ class Configuration { reporter: reporter, fileReporters: fileReporters, coverage: coverage, + coverageLcov: coverageLcov, + branchCoverage: branchCoverage, concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, @@ -485,6 +504,8 @@ class Configuration { reporter: null, fileReporters: null, coverage: null, + coverageLcov: null, + branchCoverage: null, concurrency: null, shardIndex: null, totalShards: null, @@ -550,6 +571,8 @@ class Configuration { reporter: null, fileReporters: null, coverage: null, + coverageLcov: null, + branchCoverage: null, concurrency: null, shardIndex: null, totalShards: null, @@ -617,6 +640,8 @@ class Configuration { color: null, configurationPath: null, coverage: null, + coverageLcov: null, + branchCoverage: null, shardIndex: null, totalShards: null, testSelections: null, @@ -678,6 +703,8 @@ class Configuration { reporter: null, fileReporters: null, coverage: null, + coverageLcov: null, + branchCoverage: null, concurrency: null, shardIndex: null, totalShards: null, @@ -740,6 +767,8 @@ class Configuration { required String? reporter, required Map? fileReporters, required this.coverage, + required this.coverageLcov, + required bool? branchCoverage, required int? concurrency, required this.shardIndex, required this.totalShards, @@ -766,6 +795,7 @@ class Configuration { _configurationPath = configurationPath, _reporter = reporter, fileReporters = fileReporters ?? {}, + _branchCoverage = branchCoverage, _concurrency = concurrency, _testSelections = testSelections == null || testSelections.isEmpty ? null @@ -823,6 +853,8 @@ class Configuration { reporter: null, fileReporters: null, coverage: null, + coverageLcov: null, + branchCoverage: null, concurrency: null, shardIndex: null, totalShards: null, @@ -921,6 +953,8 @@ class Configuration { reporter: other._reporter ?? _reporter, fileReporters: mergeMaps(fileReporters, other.fileReporters), coverage: other.coverage ?? coverage, + coverageLcov: other.coverageLcov ?? coverageLcov, + branchCoverage: other._branchCoverage ?? _branchCoverage, concurrency: other._concurrency ?? _concurrency, shardIndex: other.shardIndex ?? shardIndex, totalShards: other.totalShards ?? totalShards, @@ -969,6 +1003,8 @@ class Configuration { String? reporter, Map? fileReporters, String? coverage, + String? coverageLcov, + bool? branchCoverage, int? concurrency, int? shardIndex, int? totalShards, @@ -1016,6 +1052,8 @@ class Configuration { reporter: reporter ?? _reporter, fileReporters: fileReporters ?? this.fileReporters, coverage: coverage ?? this.coverage, + coverageLcov: coverageLcov ?? this.coverageLcov, + branchCoverage: branchCoverage ?? _branchCoverage, concurrency: concurrency ?? _concurrency, shardIndex: shardIndex ?? this.shardIndex, totalShards: totalShards ?? this.totalShards, diff --git a/pkgs/test_core/lib/src/runner/configuration/args.dart b/pkgs/test_core/lib/src/runner/configuration/args.dart index e981bedc3..b94d37687 100644 --- a/pkgs/test_core/lib/src/runner/configuration/args.dart +++ b/pkgs/test_core/lib/src/runner/configuration/args.dart @@ -113,6 +113,14 @@ final ArgParser _parser = (() { help: 'Gather coverage and output it to the specified directory.\n' 'Implies --debug.', valueHelp: 'directory'); + parser.addOption('coverage-lcov', + help: 'Gather coverage and output an lcov report to the specified file.\n' + 'Implies --debug.', + valueHelp: 'file'); + parser.addFlag('branch-coverage', + help: 'Include branch coverage information in the coverage report.\n' + 'Must be paired with --coverage or --coverage-lcov.', + negatable: false); parser.addFlag('chain-stack-traces', help: 'Use chained stack traces to provide greater exception details\n' 'especially for asynchronous code. It may be useful to disable\n' @@ -314,6 +322,18 @@ class _Parser { 'open an issue at https://github.com/dart-lang/test/issues/new.'); } + final coverageDir = _ifParsed('coverage'); + final coverageLcov = _ifParsed('coverage-lcov'); + final branchCoverage = _ifParsed('branch-coverage') ?? false; + if (coverageDir == null && coverageLcov == null && branchCoverage) { + throw ArgumentError( + 'If you set --branch-coverage you must set either --coverage or ' + '--coverage-lcov'); + } + if (coverageLcov != null && coverageDir != null) { + throw ArgumentError('Cannot set both --coverage and --coverage-lcov'); + } + return Configuration( help: _ifParsed('help'), version: _ifParsed('version'), @@ -328,7 +348,9 @@ class _Parser { precompiledPath: _ifParsed('precompiled'), reporter: reporter, fileReporters: _parseFileReporterOption(), - coverage: _ifParsed('coverage'), + coverage: coverageDir, + coverageLcov: coverageLcov, + branchCoverage: branchCoverage, concurrency: _parseOption('concurrency', int.parse), shardIndex: shardIndex, totalShards: totalShards, diff --git a/pkgs/test_core/lib/src/runner/coverage.dart b/pkgs/test_core/lib/src/runner/coverage.dart index 841bdd72f..0f67efdc8 100644 --- a/pkgs/test_core/lib/src/runner/coverage.dart +++ b/pkgs/test_core/lib/src/runner/coverage.dart @@ -5,20 +5,39 @@ import 'dart:convert'; import 'dart:io'; +import 'package:coverage/coverage.dart'; import 'package:path/path.dart' as p; +import '../util/package_config.dart'; import 'live_suite_controller.dart'; /// Collects coverage and outputs to the [coveragePath] path. -Future writeCoverage( - String coveragePath, LiveSuiteController controller) async { - var suite = controller.liveSuite.suite; - var coverage = await controller.liveSuite.suite.gatherCoverage(); - final outfile = File(p.join(coveragePath, - '${suite.path}.${suite.platform.runtime.name.toLowerCase()}.json')) - ..createSync(recursive: true); +Future> writeCoverage( + String? coveragePath, LiveSuiteController controller) async { + final suite = controller.liveSuite.suite; + final coverage = await controller.liveSuite.suite.gatherCoverage(); + if (coveragePath != null) { + final outfile = File(p.join(coveragePath, + '${suite.path}.${suite.platform.runtime.name.toLowerCase()}.json')) + ..createSync(recursive: true); + final out = outfile.openWrite(); + out.write(json.encode(coverage)); + await out.flush(); + await out.close(); + } + return HitMap.parseJson(coverage['coverage'] as List>); +} + +Future writeCoverageLcov( + String coverageLcov, Map allCoverageData) async { + final resolver = await Resolver.create( + packagePath: (await currentPackage).root.toFilePath()); + final filteredCoverageData = allCoverageData + .filterIgnored(ignoredLinesInFilesCache: {}, resolver: resolver); + final lcovData = filteredCoverageData.formatLcov(resolver); + final outfile = File(coverageLcov)..createSync(recursive: true); final out = outfile.openWrite(); - out.write(json.encode(coverage)); + out.write(lcovData); await out.flush(); await out.close(); } diff --git a/pkgs/test_core/lib/src/runner/coverage_stub.dart b/pkgs/test_core/lib/src/runner/coverage_stub.dart index 64f69c75e..05360d5dd 100644 --- a/pkgs/test_core/lib/src/runner/coverage_stub.dart +++ b/pkgs/test_core/lib/src/runner/coverage_stub.dart @@ -2,9 +2,16 @@ // 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 'package:coverage/coverage.dart'; + import 'live_suite_controller.dart'; -Future writeCoverage( - String coveragePath, LiveSuiteController controller) => +Future> writeCoverage( + String? coveragePath, LiveSuiteController controller) => + throw UnsupportedError( + 'Coverage is only supported through the test runner.'); + +Future writeCoverageLcov( + String coverageLcov, Map allCoverageData) => throw UnsupportedError( 'Coverage is only supported through the test runner.'); diff --git a/pkgs/test_core/lib/src/runner/engine.dart b/pkgs/test_core/lib/src/runner/engine.dart index 4404d1c5c..40a01f68d 100644 --- a/pkgs/test_core/lib/src/runner/engine.dart +++ b/pkgs/test_core/lib/src/runner/engine.dart @@ -7,6 +7,7 @@ import 'dart:math'; import 'package:async/async.dart' hide Result; import 'package:collection/collection.dart'; +import 'package:coverage/coverage.dart'; import 'package:pool/pool.dart'; import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports @@ -61,7 +62,13 @@ class Engine { bool? _closedBeforeDone; /// The coverage output directory. - String? _coverage; + final String? _coverage; + + /// The coverage output lcov file. + final String? _coverageLcov; + + /// The merged coverage data from all tests. + final Map _allCoverageData = {}; /// The seed used to generate randomness for test case shuffling. /// @@ -92,8 +99,7 @@ class Engine { /// This will be `null` if [close] was called before all the tests finished /// running. Future get success async { - await Future.wait([_group.future, _runPool.done], eagerError: true); - if (_closedBeforeDone!) return null; + if (!await _done) return null; return liveTests.every((liveTest) => liveTest.state.result.isPassing && liveTest.state.status == Status.complete); @@ -211,11 +217,13 @@ class Engine { Engine({ int? concurrency, String? coverage, + String? coverageLcov, this.testRandomizeOrderingSeed, bool stopOnFirstFailure = false, }) : _runPool = Pool(concurrency ?? 1), _stopOnFirstFailure = stopOnFirstFailure, - _coverage = coverage { + _coverage = coverage, + _coverageLcov = coverageLcov { _group.future.then((_) { _onTestStartedGroup.close(); _onSuiteStartedController.close(); @@ -233,10 +241,14 @@ class Engine { /// [concurrency] controls how many suites are run at once. If [runSkipped] is /// `true`, skipped tests will be run as though they weren't skipped. factory Engine.withSuites(List suites, - {int? concurrency, String? coverage, bool stopOnFirstFailure = false}) { + {int? concurrency, + String? coverage, + String? coverageLcov, + bool stopOnFirstFailure = false}) { var engine = Engine( concurrency: concurrency, coverage: coverage, + coverageLcov: coverageLcov, stopOnFirstFailure: stopOnFirstFailure, ); for (var suite in suites) { @@ -282,17 +294,24 @@ class Engine { if (_closed) return; await _runGroup(controller, controller.liveSuite.suite.group, []); controller.noMoreLiveTests(); - if (_coverage != null) await writeCoverage(_coverage!, controller); + if (_coverage != null || _coverageLcov != null) { + _allCoverageData + .merge(await writeCoverage(_coverage, controller)); + } } finally { resource.allowRelease(() => controller?.close()); } }()); }) - ..onDone(() { + ..onDone(() async { _subscriptions.remove(subscription); _onSuiteAddedController.close(); _group.close(); _runPool.close(); + + if (_coverageLcov != null && await _done) { + await writeCoverageLcov(_coverageLcov, _allCoverageData); + } }); _subscriptions.add(subscription); @@ -551,4 +570,9 @@ class Engine { futures.add(_runPool.close()); await Future.wait(futures, eagerError: true); } + + Future get _done async { + await Future.wait([_group.future, _runPool.done], eagerError: true); + return !_closedBeforeDone!; + } } diff --git a/pkgs/test_core/lib/src/runner/vm/platform.dart b/pkgs/test_core/lib/src/runner/vm/platform.dart index f2586abfb..9e5b0e3a1 100644 --- a/pkgs/test_core/lib/src/runner/vm/platform.dart +++ b/pkgs/test_core/lib/src/runner/vm/platform.dart @@ -137,7 +137,7 @@ class VMPlatform extends PlatformPlugin { var controller = deserializeSuite( path, platform, suiteConfig, environment, channel.cast(), message, - gatherCoverage: () => _gatherCoverage(environment!)); + gatherCoverage: () => _gatherCoverage(environment!, _config)); if (isolateRef != null) { await client!.streamListen('Debug'); @@ -343,11 +343,19 @@ stderr: ${processResult.stderr}'''); } } -Future> _gatherCoverage(Environment environment) async { +Future> _gatherCoverage( + Environment environment, Configuration config) async { final isolateId = Uri.parse(environment.observatoryUrl!.fragment) .queryParameters['isolateId']; - return await collect(environment.observatoryUrl!, false, false, false, {}, - isolateIds: {isolateId!}); + return await collect( + environment.observatoryUrl!, + false, + false, + false, + {(await currentPackage).name}, + isolateIds: {isolateId!}, + branchCoverage: config.branchCoverage, + ); } Uri _wsUriFor(Uri observatoryUrl) => diff --git a/pkgs/test_core/lib/src/util/package_config.dart b/pkgs/test_core/lib/src/util/package_config.dart index c33dda628..53ad07cc0 100644 --- a/pkgs/test_core/lib/src/util/package_config.dart +++ b/pkgs/test_core/lib/src/util/package_config.dart @@ -37,3 +37,8 @@ Future absoluteUri(String path) async { return absoluteUri; } } + +/// Returns the current package. +final Future currentPackage = () async { + return (await currentPackageConfig).packageOf(await packageConfigUri)!; +}(); diff --git a/pkgs/test_core/pubspec.yaml b/pkgs/test_core/pubspec.yaml index 78cc8ea24..4782b1d02 100644 --- a/pkgs/test_core/pubspec.yaml +++ b/pkgs/test_core/pubspec.yaml @@ -1,5 +1,5 @@ name: test_core -version: 0.6.11 +version: 0.6.12 description: A basic library for writing tests and running them on the VM. repository: https://github.com/dart-lang/test/tree/master/pkgs/test_core issue_tracker: https://github.com/dart-lang/test/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Atest @@ -14,7 +14,7 @@ dependencies: async: ^2.5.0 boolean_selector: ^2.1.0 collection: ^1.15.0 - coverage: ^1.0.0 + coverage: ^1.15.0 frontend_server_client: '>=3.2.0 <5.0.0' glob: ^2.0.0 io: ^1.0.0