Skip to content

Commit 0c14308

Browse files
authored
Add branch coverage to flutter test (#113802)
1 parent 5259e1b commit 0c14308

File tree

3 files changed

+115
-7
lines changed

3 files changed

+115
-7
lines changed

packages/flutter_tools/lib/src/commands/test.dart

+8-1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
121121
help: 'Whether to merge coverage data with "coverage/lcov.base.info".\n'
122122
'Implies collecting coverage data. (Requires lcov.)',
123123
)
124+
..addFlag('branch-coverage',
125+
negatable: false,
126+
help: 'Whether to collect branch coverage information. '
127+
'Implies collecting coverage data.',
128+
)
124129
..addFlag('ipv6',
125130
negatable: false,
126131
hide: !verboseHelp,
@@ -378,14 +383,16 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
378383

379384
final bool machine = boolArgDeprecated('machine');
380385
CoverageCollector? collector;
381-
if (boolArgDeprecated('coverage') || boolArgDeprecated('merge-coverage')) {
386+
if (boolArgDeprecated('coverage') || boolArgDeprecated('merge-coverage') ||
387+
boolArgDeprecated('branch-coverage')) {
382388
final String projectName = flutterProject.manifest.appName;
383389
collector = CoverageCollector(
384390
verbose: !machine,
385391
libraryNames: <String>{projectName},
386392
packagesPath: buildInfo.packagesPath,
387393
resolver: await CoverageCollector.getResolver(buildInfo.packagesPath),
388394
testTimeRecorder: testTimeRecorder,
395+
branchCoverage: boolArgDeprecated('branch-coverage'),
389396
);
390397
}
391398

packages/flutter_tools/lib/src/test/coverage_collector.dart

+16-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import 'watcher.dart';
1717

1818
/// A class that collects code coverage data during test runs.
1919
class CoverageCollector extends TestWatcher {
20-
CoverageCollector({this.libraryNames, this.verbose = true, required this.packagesPath, this.resolver, this.testTimeRecorder});
20+
CoverageCollector({
21+
this.libraryNames, this.verbose = true, required this.packagesPath,
22+
this.resolver, this.testTimeRecorder, this.branchCoverage = false});
2123

2224
/// True when log messages should be emitted.
2325
final bool verbose;
@@ -38,6 +40,9 @@ class CoverageCollector extends TestWatcher {
3840

3941
final TestTimeRecorder? testTimeRecorder;
4042

43+
/// Whether to collect branch coverage information.
44+
bool branchCoverage;
45+
4146
static Future<coverage.Resolver> getResolver(String? packagesPath) async {
4247
try {
4348
return await coverage.Resolver.create(packagesPath: packagesPath);
@@ -97,7 +102,8 @@ class CoverageCollector extends TestWatcher {
97102
/// The returned [Future] completes when the coverage is collected.
98103
Future<void> collectCoverageIsolate(Uri observatoryUri) async {
99104
_logMessage('collecting coverage data from $observatoryUri...');
100-
final Map<String, dynamic> data = await collect(observatoryUri, libraryNames);
105+
final Map<String, dynamic> data = await collect(
106+
observatoryUri, libraryNames, branchCoverage: branchCoverage);
101107
if (data == null) {
102108
throw Exception('Failed to collect coverage.');
103109
}
@@ -136,7 +142,9 @@ class CoverageCollector extends TestWatcher {
136142
final Future<void> collectionComplete = testDevice.observatoryUri
137143
.then((Uri? observatoryUri) {
138144
_logMessage('collecting coverage data from $testDevice at $observatoryUri...');
139-
return collect(observatoryUri!, libraryNames, serviceOverride: serviceOverride)
145+
return collect(
146+
observatoryUri!, libraryNames, serviceOverride: serviceOverride,
147+
branchCoverage: branchCoverage)
140148
.then<void>((Map<String, dynamic> result) {
141149
if (result == null) {
142150
throw Exception('Failed to collect coverage.');
@@ -254,6 +262,10 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, Set<String>? libraryNames,
254262
String? debugName,
255263
@visibleForTesting bool forceSequential = false,
256264
@visibleForTesting FlutterVmService? serviceOverride,
265+
bool branchCoverage = false,
257266
}) {
258-
return coverage.collect(serviceUri, false, false, false, libraryNames, serviceOverrideForTesting: serviceOverride?.service);
267+
return coverage.collect(
268+
serviceUri, false, false, false, libraryNames,
269+
serviceOverrideForTesting: serviceOverride?.service,
270+
branchCoverage: branchCoverage);
259271
}

packages/flutter_tools/test/general.shard/coverage_collector_test.dart

+91-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
@Timeout.factor(10)
6-
75
import 'dart:convert' show jsonEncode;
86
import 'dart:io' show Directory, File;
97

@@ -331,6 +329,97 @@ void main() {
331329
expect(fakeVmServiceHost.hasRemainingExpectations, false);
332330
});
333331

332+
testWithoutContext('Coverage collector with branch coverage', () async {
333+
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
334+
requests: <VmServiceExpectation>[
335+
FakeVmServiceRequest(
336+
method: 'getVM',
337+
jsonResponse: (VM.parse(<String, Object>{})!
338+
..isolates = <IsolateRef>[
339+
IsolateRef.parse(<String, Object>{
340+
'id': '1',
341+
})!,
342+
]
343+
).toJson(),
344+
),
345+
FakeVmServiceRequest(
346+
method: 'getVersion',
347+
jsonResponse: Version(major: 3, minor: 56).toJson(),
348+
),
349+
FakeVmServiceRequest(
350+
method: 'getScripts',
351+
args: <String, Object>{
352+
'isolateId': '1',
353+
},
354+
jsonResponse: ScriptList(scripts: <ScriptRef>[
355+
ScriptRef(uri: 'package:foo/foo.dart', id: '1'),
356+
ScriptRef(uri: 'package:bar/bar.dart', id: '2'),
357+
]).toJson(),
358+
),
359+
FakeVmServiceRequest(
360+
method: 'getSourceReport',
361+
args: <String, Object>{
362+
'isolateId': '1',
363+
'reports': <Object>['Coverage', 'BranchCoverage'],
364+
'scriptId': '1',
365+
'forceCompile': true,
366+
'reportLines': true,
367+
},
368+
jsonResponse: SourceReport(
369+
ranges: <SourceReportRange>[
370+
SourceReportRange(
371+
scriptIndex: 0,
372+
startPos: 0,
373+
endPos: 0,
374+
compiled: true,
375+
coverage: SourceReportCoverage(
376+
hits: <int>[1, 3],
377+
misses: <int>[2],
378+
),
379+
branchCoverage: SourceReportCoverage(
380+
hits: <int>[4, 6],
381+
misses: <int>[5],
382+
),
383+
),
384+
],
385+
scripts: <ScriptRef>[
386+
ScriptRef(
387+
uri: 'package:foo/foo.dart',
388+
id: '1',
389+
),
390+
],
391+
).toJson(),
392+
),
393+
],
394+
);
395+
396+
final Map<String, Object?> result = await collect(
397+
Uri(),
398+
<String>{'foo'},
399+
serviceOverride: fakeVmServiceHost.vmService,
400+
branchCoverage: true,
401+
);
402+
403+
expect(result, <String, Object>{
404+
'type': 'CodeCoverage',
405+
'coverage': <Object>[
406+
<String, Object>{
407+
'source': 'package:foo/foo.dart',
408+
'script': <String, Object>{
409+
'type': '@Script',
410+
'fixedId': true,
411+
'id': 'libraries/1/scripts/package%3Afoo%2Ffoo.dart',
412+
'uri': 'package:foo/foo.dart',
413+
'_kind': 'library',
414+
},
415+
'hits': <Object>[1, 1, 3, 1, 2, 0],
416+
'branchHits': <Object>[4, 1, 6, 1, 5, 0],
417+
},
418+
],
419+
});
420+
expect(fakeVmServiceHost.hasRemainingExpectations, false);
421+
});
422+
334423
testWithoutContext('Coverage collector caches read files', () async {
335424
Directory? tempDir;
336425
try {

0 commit comments

Comments
 (0)