Add branch coverage to flutter test (#113802)
This commit is contained in:
parent
5259e1bc6d
commit
0c14308404
@ -121,6 +121,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
|||||||
help: 'Whether to merge coverage data with "coverage/lcov.base.info".\n'
|
help: 'Whether to merge coverage data with "coverage/lcov.base.info".\n'
|
||||||
'Implies collecting coverage data. (Requires lcov.)',
|
'Implies collecting coverage data. (Requires lcov.)',
|
||||||
)
|
)
|
||||||
|
..addFlag('branch-coverage',
|
||||||
|
negatable: false,
|
||||||
|
help: 'Whether to collect branch coverage information. '
|
||||||
|
'Implies collecting coverage data.',
|
||||||
|
)
|
||||||
..addFlag('ipv6',
|
..addFlag('ipv6',
|
||||||
negatable: false,
|
negatable: false,
|
||||||
hide: !verboseHelp,
|
hide: !verboseHelp,
|
||||||
@ -378,7 +383,8 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
|||||||
|
|
||||||
final bool machine = boolArgDeprecated('machine');
|
final bool machine = boolArgDeprecated('machine');
|
||||||
CoverageCollector? collector;
|
CoverageCollector? collector;
|
||||||
if (boolArgDeprecated('coverage') || boolArgDeprecated('merge-coverage')) {
|
if (boolArgDeprecated('coverage') || boolArgDeprecated('merge-coverage') ||
|
||||||
|
boolArgDeprecated('branch-coverage')) {
|
||||||
final String projectName = flutterProject.manifest.appName;
|
final String projectName = flutterProject.manifest.appName;
|
||||||
collector = CoverageCollector(
|
collector = CoverageCollector(
|
||||||
verbose: !machine,
|
verbose: !machine,
|
||||||
@ -386,6 +392,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
|
|||||||
packagesPath: buildInfo.packagesPath,
|
packagesPath: buildInfo.packagesPath,
|
||||||
resolver: await CoverageCollector.getResolver(buildInfo.packagesPath),
|
resolver: await CoverageCollector.getResolver(buildInfo.packagesPath),
|
||||||
testTimeRecorder: testTimeRecorder,
|
testTimeRecorder: testTimeRecorder,
|
||||||
|
branchCoverage: boolArgDeprecated('branch-coverage'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,9 @@ import 'watcher.dart';
|
|||||||
|
|
||||||
/// A class that collects code coverage data during test runs.
|
/// A class that collects code coverage data during test runs.
|
||||||
class CoverageCollector extends TestWatcher {
|
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.
|
/// True when log messages should be emitted.
|
||||||
final bool verbose;
|
final bool verbose;
|
||||||
@ -38,6 +40,9 @@ class CoverageCollector extends TestWatcher {
|
|||||||
|
|
||||||
final TestTimeRecorder? testTimeRecorder;
|
final TestTimeRecorder? testTimeRecorder;
|
||||||
|
|
||||||
|
/// Whether to collect branch coverage information.
|
||||||
|
bool branchCoverage;
|
||||||
|
|
||||||
static Future<coverage.Resolver> getResolver(String? packagesPath) async {
|
static Future<coverage.Resolver> getResolver(String? packagesPath) async {
|
||||||
try {
|
try {
|
||||||
return await coverage.Resolver.create(packagesPath: packagesPath);
|
return await coverage.Resolver.create(packagesPath: packagesPath);
|
||||||
@ -97,7 +102,8 @@ class CoverageCollector extends TestWatcher {
|
|||||||
/// The returned [Future] completes when the coverage is collected.
|
/// The returned [Future] completes when the coverage is collected.
|
||||||
Future<void> collectCoverageIsolate(Uri observatoryUri) async {
|
Future<void> collectCoverageIsolate(Uri observatoryUri) async {
|
||||||
_logMessage('collecting coverage data from $observatoryUri...');
|
_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) {
|
if (data == null) {
|
||||||
throw Exception('Failed to collect coverage.');
|
throw Exception('Failed to collect coverage.');
|
||||||
}
|
}
|
||||||
@ -136,7 +142,9 @@ class CoverageCollector extends TestWatcher {
|
|||||||
final Future<void> collectionComplete = testDevice.observatoryUri
|
final Future<void> collectionComplete = testDevice.observatoryUri
|
||||||
.then((Uri? observatoryUri) {
|
.then((Uri? observatoryUri) {
|
||||||
_logMessage('collecting coverage data from $testDevice at $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) {
|
.then<void>((Map<String, dynamic> result) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw Exception('Failed to collect coverage.');
|
throw Exception('Failed to collect coverage.');
|
||||||
@ -254,6 +262,10 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, Set<String>? libraryNames,
|
|||||||
String? debugName,
|
String? debugName,
|
||||||
@visibleForTesting bool forceSequential = false,
|
@visibleForTesting bool forceSequential = false,
|
||||||
@visibleForTesting FlutterVmService? serviceOverride,
|
@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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
@Timeout.factor(10)
|
|
||||||
|
|
||||||
import 'dart:convert' show jsonEncode;
|
import 'dart:convert' show jsonEncode;
|
||||||
import 'dart:io' show Directory, File;
|
import 'dart:io' show Directory, File;
|
||||||
|
|
||||||
@ -331,6 +329,97 @@ void main() {
|
|||||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
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 {
|
testWithoutContext('Coverage collector caches read files', () async {
|
||||||
Directory? tempDir;
|
Directory? tempDir;
|
||||||
try {
|
try {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user