Skip to content

Commit 9354f23

Browse files
authored
Add --coverage-path and --branch-coverage options (#2517)
1 parent 5aef971 commit 9354f23

File tree

17 files changed

+288
-79
lines changed

17 files changed

+288
-79
lines changed

pkgs/test/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
## 1.26.4-wip
1+
## 1.27.0-wip
22

33
* Restrict to latest version of analyzer package.
44
* Require Dart 3.7
5+
* Add `--coverage-path` and `--branch-coverage` options to `dart test`.
56

67
## 1.26.3
78

pkgs/test/README.md

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -257,36 +257,25 @@ The available options for the `--reporter` flag are:
257257

258258
### Collecting Code Coverage
259259

260-
To collect code coverage, you can run tests with the `--coverage <directory>`
261-
argument. The directory specified can be an absolute or relative path.
262-
If a directory does not exist at the path specified, a directory will be
263-
created. If a directory does exist, files may be overwritten with the latest
264-
coverage data, if they conflict.
265-
266-
This option will enable code coverage collection on a suite-by-suite basis,
267-
and the resulting coverage files will be outputted in the directory specified.
268-
The files can then be formatted using the `package:coverage`
269-
`format_coverage` executable.
260+
To collect code coverage, you can run tests with the `--coverage-path <file>`
261+
argument, which outputs a LCOV report to the given file path. The file
262+
specified can be an absolute or relative path. If a directory does not exist at
263+
the path specified, a directory will be created. If a directory does exist,
264+
files may be overwritten with the latest coverage data, if they conflict.
270265

271266
Coverage gathering is currently only implemented for tests run on the Dart VM or
272267
Chrome.
273268

274-
Here's an example of how to run tests and format the collected coverage to LCOV:
269+
Here's an example of how to run tests and collect coverage:
275270

276271
```shell
277-
## Run Dart tests and output them at directory `./coverage`:
278-
dart run test --coverage=./coverage
279-
280-
## Activate package `coverage` (if needed):
281-
dart pub global activate coverage
282-
283-
## Format collected coverage to LCOV (only for directory "lib")
284-
dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage
272+
## Run Dart tests and output coverage info to `./coverage/lcov.info`:
273+
dart run test --coverage-path=./coverage/lcov.info
285274

286-
## Generate LCOV report:
275+
## Generate a human readable report:
287276
genhtml -o ./coverage/report ./coverage/lcov.info
288277

289-
## Open the HTML coverage report:
278+
## Open the coverage report:
290279
open ./coverage/report/index.html
291280
```
292281

pkgs/test/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: test
2-
version: 1.26.4-wip
2+
version: 1.27.0-wip
33
description: >-
44
A full featured library for writing and running Dart tests across platforms.
55
repository: https://github.com/dart-lang/test/tree/master/pkgs/test
@@ -14,7 +14,7 @@ dependencies:
1414
async: ^2.5.0
1515
boolean_selector: ^2.1.0
1616
collection: ^1.15.0
17-
coverage: ^1.0.1
17+
coverage: ^1.15.0
1818
http_multi_server: ^3.0.0
1919
io: ^1.0.0
2020
js: '>=0.6.4 <0.8.0'

pkgs/test/test/io.dart

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,6 @@ final Future<String> packageDir = Isolate.resolvePackageUri(
2121
return dir;
2222
});
2323

24-
/// The path to the `pub` executable in the current Dart SDK.
25-
final _pubPath = p.absolute(
26-
p.join(
27-
p.dirname(Platform.resolvedExecutable),
28-
Platform.isWindows ? 'pub.bat' : 'pub',
29-
),
30-
);
31-
3224
/// The platform-specific message emitted when a nonexistent file is loaded.
3325
final String noSuchFileMessage =
3426
Platform.isWindows
@@ -174,8 +166,8 @@ Future<TestProcess> runPub(
174166
Map<String, String>? environment,
175167
}) {
176168
return TestProcess.start(
177-
_pubPath,
178-
args,
169+
p.absolute(Platform.resolvedExecutable),
170+
['pub', ...args],
179171
workingDirectory: d.sandbox,
180172
environment: environment,
181173
description: 'pub ${args.first}',

pkgs/test/test/runner/coverage_test.dart

Lines changed: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ library;
88
import 'dart:convert';
99
import 'dart:io';
1010

11+
import 'package:coverage/coverage.dart';
1112
import 'package:path/path.dart' as p;
1213
import 'package:test/test.dart';
1314
import 'package:test_descriptor/test_descriptor.dart' as d;
@@ -20,55 +21,136 @@ void main() {
2021

2122
group('with the --coverage flag,', () {
2223
late Directory coverageDirectory;
24+
late d.DirectoryDescriptor packageDirectory;
2325

24-
Future<void> validateCoverage(TestProcess test, String coveragePath) async {
26+
Future<void> validateTest(TestProcess test) async {
2527
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
2628
await test.shouldExit(0);
29+
}
30+
31+
Future<Map<String, HitMap>> validateCoverage(
32+
TestProcess test,
33+
String coveragePath,
34+
) async {
35+
await validateTest(test);
2736

2837
final coverageFile = File(p.join(coverageDirectory.path, coveragePath));
2938
final coverage = await coverageFile.readAsString();
30-
final jsonCoverage = json.decode(coverage);
31-
expect(jsonCoverage['coverage'], isNotEmpty);
39+
final jsonCoverage = json.decode(coverage)['coverage'] as List<dynamic>;
40+
expect(jsonCoverage, isNotEmpty);
41+
42+
return HitMap.parseJson(jsonCoverage.cast<Map<String, dynamic>>());
3243
}
3344

3445
setUp(() async {
35-
await d.file('test.dart', '''
36-
import 'package:test/test.dart';
37-
38-
void main() {
39-
test("test 1", () {
40-
expect(true, isTrue);
41-
});
42-
}
43-
''').create();
44-
4546
coverageDirectory = await Directory.systemTemp.createTemp(
4647
'test_coverage',
4748
);
49+
50+
packageDirectory = d.dir(d.sandbox, [
51+
d.dir('lib', [
52+
d.file('calculate.dart', '''
53+
int calculate(int x) {
54+
if (x % 2 == 0) {
55+
return x * 2;
56+
} else {
57+
return x * 3;
58+
}
59+
}
60+
'''),
61+
]),
62+
d.dir('test', [
63+
d.file('test.dart', '''
64+
import 'package:fake_package/calculate.dart';
65+
import 'package:test/test.dart';
66+
67+
void main() {
68+
test('test 1', () {
69+
expect(calculate(6), 12);
70+
});
71+
}
72+
'''),
73+
]),
74+
d.file('pubspec.yaml', '''
75+
name: fake_package
76+
version: 1.0.0
77+
environment:
78+
sdk: ^3.5.0
79+
dev_dependencies:
80+
test: ^1.26.2
81+
'''),
82+
]);
83+
await packageDirectory.create();
4884
});
4985

5086
tearDown(() async {
5187
await coverageDirectory.delete(recursive: true);
5288
});
5389

5490
test('gathers coverage for VM tests', () async {
91+
await (await runPub(['get'])).shouldExit(0);
5592
var test = await runTest([
5693
'--coverage',
5794
coverageDirectory.path,
58-
'test.dart',
59-
]);
60-
await validateCoverage(test, 'test.dart.vm.json');
95+
'test/test.dart',
96+
], packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json'));
97+
final coverage = await validateCoverage(test, 'test/test.dart.vm.json');
98+
final hitmap = coverage['package:fake_package/calculate.dart']!;
99+
expect(hitmap.lineHits, {1: 1, 2: 2, 3: 1, 5: 0});
100+
expect(hitmap.funcHits, isNull);
101+
expect(hitmap.branchHits, isNull);
102+
});
103+
104+
test('gathers branch coverage for VM tests', () async {
105+
await (await runPub(['get'])).shouldExit(0);
106+
var test = await runTest(
107+
[
108+
'--coverage',
109+
coverageDirectory.path,
110+
'--branch-coverage',
111+
'test/test.dart',
112+
],
113+
vmArgs: ['--branch-coverage'],
114+
packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json'),
115+
);
116+
final coverage = await validateCoverage(test, 'test/test.dart.vm.json');
117+
final hitmap = coverage['package:fake_package/calculate.dart']!;
118+
expect(hitmap.lineHits, {1: 1, 2: 2, 3: 1, 5: 0});
119+
expect(hitmap.funcHits, isNull);
120+
expect(hitmap.branchHits, {1: 1, 2: 1, 4: 0});
121+
});
122+
123+
test('gathers lcov coverage for VM tests', () async {
124+
await (await runPub(['get'])).shouldExit(0);
125+
final lcovFile = p.join(coverageDirectory.path, 'lcov.info');
126+
var test = await runTest([
127+
'--coverage-path',
128+
lcovFile,
129+
'test/test.dart',
130+
], packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json'));
131+
await validateTest(test);
132+
expect(File(lcovFile).readAsStringSync(), '''
133+
SF:${p.join(d.sandbox, 'lib', 'calculate.dart')}
134+
DA:1,1
135+
DA:2,2
136+
DA:3,1
137+
DA:5,0
138+
LF:4
139+
LH:3
140+
end_of_record
141+
''');
61142
});
62143

63144
test('gathers coverage for Chrome tests', () async {
145+
await (await runPub(['get'])).shouldExit(0);
64146
var test = await runTest([
65147
'--coverage',
66148
coverageDirectory.path,
67-
'test.dart',
149+
'test/test.dart',
68150
'-p',
69151
'chrome',
70-
]);
71-
await validateCoverage(test, 'test.dart.chrome.json');
152+
], packageConfig: p.join(d.sandbox, '.dart_tool/package_config.json'));
153+
await validateCoverage(test, 'test/test.dart.chrome.json');
72154
});
73155

74156
test(

pkgs/test/test/runner/runner_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ $_runtimeCompilers
9292
--debug Run the VM and Chrome tests in debug mode.
9393
--coverage=<directory> Gather coverage and output it to the specified directory.
9494
Implies --debug.
95+
--coverage-path=<file> Gather coverage and output an lcov report to the specified file.
96+
Implies --debug.
97+
--branch-coverage Include branch coverage information in the coverage report.
98+
Must be paired with --coverage or --coverage-path.
9599
--[no-]chain-stack-traces Use chained stack traces to provide greater exception details
96100
especially for asynchronous code. It may be useful to disable
97101
to provide improved test performance but at the cost of

pkgs/test/test/utils.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Engine declareEngine(
138138
void Function() body, {
139139
bool runSkipped = false,
140140
String? coverage,
141+
String? coverageLcov,
141142
bool stopOnFirstFailure = false,
142143
}) {
143144
var declarer = Declarer()..declare(body);
@@ -151,6 +152,7 @@ Engine declareEngine(
151152
),
152153
],
153154
coverage: coverage,
155+
coverageLcov: coverageLcov,
154156
stopOnFirstFailure: stopOnFirstFailure,
155157
);
156158
}
@@ -222,6 +224,8 @@ Configuration configuration({
222224
String? reporter,
223225
Map<String, String>? fileReporters,
224226
String? coverage,
227+
String? coverageLcov,
228+
bool? branchCoverage,
225229
int? concurrency,
226230
int? shardIndex,
227231
int? totalShards,
@@ -272,6 +276,8 @@ Configuration configuration({
272276
reporter: reporter,
273277
fileReporters: fileReporters,
274278
coverage: coverage,
279+
coverageLcov: coverageLcov,
280+
branchCoverage: branchCoverage,
275281
concurrency: concurrency,
276282
shardIndex: shardIndex,
277283
totalShards: totalShards,

pkgs/test_core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Restrict to latest version of analyzer package.
44
* Require Dart 3.7
5+
* Add `--coverage-path` and `--branch-coverage` options to `dart test`.
56

67
## 0.6.12
78

pkgs/test_core/lib/src/runner.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class Runner {
7777
var engine = Engine(
7878
concurrency: config.concurrency,
7979
coverage: config.coverage,
80+
coverageLcov: config.coverageLcov,
8081
testRandomizeOrderingSeed: config.testRandomizeOrderingSeed,
8182
stopOnFirstFailure: config.stopOnFirstFailure,
8283
);

0 commit comments

Comments
 (0)