Unverified Commit 0c143084 authored by Liam Appelbe's avatar Liam Appelbe Committed by GitHub

Add branch coverage to flutter test (#113802)

parent 5259e1bc
......@@ -121,6 +121,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
help: 'Whether to merge coverage data with "coverage/lcov.base.info".\n'
'Implies collecting coverage data. (Requires lcov.)',
)
..addFlag('branch-coverage',
negatable: false,
help: 'Whether to collect branch coverage information. '
'Implies collecting coverage data.',
)
..addFlag('ipv6',
negatable: false,
hide: !verboseHelp,
......@@ -378,7 +383,8 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
final bool machine = boolArgDeprecated('machine');
CoverageCollector? collector;
if (boolArgDeprecated('coverage') || boolArgDeprecated('merge-coverage')) {
if (boolArgDeprecated('coverage') || boolArgDeprecated('merge-coverage') ||
boolArgDeprecated('branch-coverage')) {
final String projectName = flutterProject.manifest.appName;
collector = CoverageCollector(
verbose: !machine,
......@@ -386,6 +392,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
packagesPath: buildInfo.packagesPath,
resolver: await CoverageCollector.getResolver(buildInfo.packagesPath),
testTimeRecorder: testTimeRecorder,
branchCoverage: boolArgDeprecated('branch-coverage'),
);
}
......
......@@ -17,7 +17,9 @@ import 'watcher.dart';
/// A class that collects code coverage data during test runs.
class CoverageCollector extends TestWatcher {
CoverageCollector({this.libraryNames, this.verbose = true, required this.packagesPath, this.resolver, this.testTimeRecorder});
CoverageCollector({
this.libraryNames, this.verbose = true, required this.packagesPath,
this.resolver, this.testTimeRecorder, this.branchCoverage = false});
/// True when log messages should be emitted.
final bool verbose;
......@@ -38,6 +40,9 @@ class CoverageCollector extends TestWatcher {
final TestTimeRecorder? testTimeRecorder;
/// Whether to collect branch coverage information.
bool branchCoverage;
static Future<coverage.Resolver> getResolver(String? packagesPath) async {
try {
return await coverage.Resolver.create(packagesPath: packagesPath);
......@@ -97,7 +102,8 @@ class CoverageCollector extends TestWatcher {
/// The returned [Future] completes when the coverage is collected.
Future<void> collectCoverageIsolate(Uri observatoryUri) async {
_logMessage('collecting coverage data from $observatoryUri...');
final Map<String, dynamic> data = await collect(observatoryUri, libraryNames);
final Map<String, dynamic> data = await collect(
observatoryUri, libraryNames, branchCoverage: branchCoverage);
if (data == null) {
throw Exception('Failed to collect coverage.');
}
......@@ -136,7 +142,9 @@ class CoverageCollector extends TestWatcher {
final Future<void> collectionComplete = testDevice.observatoryUri
.then((Uri? observatoryUri) {
_logMessage('collecting coverage data from $testDevice at $observatoryUri...');
return collect(observatoryUri!, libraryNames, serviceOverride: serviceOverride)
return collect(
observatoryUri!, libraryNames, serviceOverride: serviceOverride,
branchCoverage: branchCoverage)
.then<void>((Map<String, dynamic> result) {
if (result == null) {
throw Exception('Failed to collect coverage.');
......@@ -254,6 +262,10 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, Set<String>? libraryNames,
String? debugName,
@visibleForTesting bool forceSequential = false,
@visibleForTesting FlutterVmService? serviceOverride,
bool branchCoverage = false,
}) {
return coverage.collect(serviceUri, false, false, false, libraryNames, serviceOverrideForTesting: serviceOverride?.service);
return coverage.collect(
serviceUri, false, false, false, libraryNames,
serviceOverrideForTesting: serviceOverride?.service,
branchCoverage: branchCoverage);
}
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@Timeout.factor(10)
import 'dart:convert' show jsonEncode;
import 'dart:io' show Directory, File;
......@@ -331,6 +329,97 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('Coverage collector with branch coverage', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: (VM.parse(<String, Object>{})!
..isolates = <IsolateRef>[
IsolateRef.parse(<String, Object>{
'id': '1',
})!,
]
).toJson(),
),
FakeVmServiceRequest(
method: 'getVersion',
jsonResponse: Version(major: 3, minor: 56).toJson(),
),
FakeVmServiceRequest(
method: 'getScripts',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: ScriptList(scripts: <ScriptRef>[
ScriptRef(uri: 'package:foo/foo.dart', id: '1'),
ScriptRef(uri: 'package:bar/bar.dart', id: '2'),
]).toJson(),
),
FakeVmServiceRequest(
method: 'getSourceReport',
args: <String, Object>{
'isolateId': '1',
'reports': <Object>['Coverage', 'BranchCoverage'],
'scriptId': '1',
'forceCompile': true,
'reportLines': true,
},
jsonResponse: SourceReport(
ranges: <SourceReportRange>[
SourceReportRange(
scriptIndex: 0,
startPos: 0,
endPos: 0,
compiled: true,
coverage: SourceReportCoverage(
hits: <int>[1, 3],
misses: <int>[2],
),
branchCoverage: SourceReportCoverage(
hits: <int>[4, 6],
misses: <int>[5],
),
),
],
scripts: <ScriptRef>[
ScriptRef(
uri: 'package:foo/foo.dart',
id: '1',
),
],
).toJson(),
),
],
);
final Map<String, Object?> result = await collect(
Uri(),
<String>{'foo'},
serviceOverride: fakeVmServiceHost.vmService,
branchCoverage: true,
);
expect(result, <String, Object>{
'type': 'CodeCoverage',
'coverage': <Object>[
<String, Object>{
'source': 'package:foo/foo.dart',
'script': <String, Object>{
'type': '@Script',
'fixedId': true,
'id': 'libraries/1/scripts/package%3Afoo%2Ffoo.dart',
'uri': 'package:foo/foo.dart',
'_kind': 'library',
},
'hits': <Object>[1, 1, 3, 1, 2, 0],
'branchHits': <Object>[4, 1, 6, 1, 5, 0],
},
],
});
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('Coverage collector caches read files', () async {
Directory? tempDir;
try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment