diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart index 48a5da2596..8402c0b220 100644 --- a/packages/flutter_tools/lib/src/test/coverage_collector.dart +++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart @@ -37,6 +37,7 @@ class CoverageCollector extends TestWatcher { final coverage.Resolver? resolver; final Map>?> _ignoredLinesInFilesCache = >?>{}; + final Map> _coverableLineCache = >{}; final TestTimeRecorder? testTimeRecorder; @@ -103,7 +104,11 @@ class CoverageCollector extends TestWatcher { Future collectCoverageIsolate(Uri vmServiceUri) async { _logMessage('collecting coverage data from $vmServiceUri...'); final Map data = await collect( - vmServiceUri, libraryNames, branchCoverage: branchCoverage); + vmServiceUri, + libraryNames, + branchCoverage: branchCoverage, + coverableLineCache: _coverableLineCache, + ); _logMessage('($vmServiceUri): collected coverage data; merging...'); _addHitmap(await coverage.HitMap.parseJson( @@ -145,9 +150,12 @@ class CoverageCollector extends TestWatcher { .then((Uri? vmServiceUri) { _logMessage('collecting coverage data from $testDevice at $vmServiceUri...'); return collect( - vmServiceUri!, libraryNames, serviceOverride: serviceOverride, - branchCoverage: branchCoverage) - .then((Map result) { + vmServiceUri!, + libraryNames, + serviceOverride: serviceOverride, + branchCoverage: branchCoverage, + coverableLineCache: _coverableLineCache, + ).then((Map result) { _logMessage('Collected coverage data.'); data = result; }); @@ -267,9 +275,12 @@ Future> collect(Uri serviceUri, Set? libraryNames, @visibleForTesting bool forceSequential = false, @visibleForTesting FlutterVmService? serviceOverride, bool branchCoverage = false, + Map>? coverableLineCache, }) { return coverage.collect( - serviceUri, false, false, false, libraryNames, - serviceOverrideForTesting: serviceOverride?.service, - branchCoverage: branchCoverage); + serviceUri, false, false, false, libraryNames, + serviceOverrideForTesting: serviceOverride?.service, + branchCoverage: branchCoverage, + coverableLineCache: coverableLineCache, + ); } diff --git a/packages/flutter_tools/test/general.shard/coverage_collector_test.dart b/packages/flutter_tools/test/general.shard/coverage_collector_test.dart index 9ecce50743..695ae3bee3 100644 --- a/packages/flutter_tools/test/general.shard/coverage_collector_test.dart +++ b/packages/flutter_tools/test/general.shard/coverage_collector_test.dart @@ -53,6 +53,7 @@ void main() { Uri(), {'foo'}, serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: >{}, ); expect(result, {'type': 'CodeCoverage', 'coverage': []}); @@ -123,6 +124,7 @@ void main() { Uri(), {'foo'}, serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: >{}, ); expect(result, { @@ -151,6 +153,7 @@ void main() { Uri(), null, serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: >{}, ); expect(result, { @@ -237,6 +240,7 @@ void main() { Uri(), {'foo'}, serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: >{}, ); expect(result, { @@ -311,6 +315,7 @@ void main() { Uri(), null, serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: >{}, ); expect(result, { @@ -401,6 +406,7 @@ void main() { {'foo'}, serviceOverride: fakeVmServiceHost.vmService, branchCoverage: true, + coverableLineCache: >{}, ); expect(result, { @@ -601,6 +607,179 @@ void main() { tempDir?.deleteSync(recursive: true); } }); + + testWithoutContext('Coverage collector fills coverableLineCache', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + method: 'getVM', + jsonResponse: (VM.parse({})! + ..isolates = [ + IsolateRef.parse({ + 'id': '1', + })!, + ] + ).toJson(), + ), + FakeVmServiceRequest( + method: 'getVersion', + jsonResponse: Version(major: 4, minor: 13).toJson(), + ), + FakeVmServiceRequest( + method: 'getSourceReport', + args: { + 'isolateId': '1', + 'reports': ['Coverage'], + 'forceCompile': true, + 'reportLines': true, + 'libraryFilters': ['package:foo/'], + 'librariesAlreadyCompiled': [], + }, + jsonResponse: SourceReport( + ranges: [ + SourceReportRange( + scriptIndex: 0, + startPos: 0, + endPos: 0, + compiled: true, + coverage: SourceReportCoverage( + hits: [1, 3], + misses: [2], + ), + ), + ], + scripts: [ + ScriptRef( + uri: 'package:foo/foo.dart', + id: '1', + ), + ], + ).toJson(), + ), + ], + ); + + final Map> coverableLineCache = >{}; + final Map result = await collect( + Uri(), + {'foo'}, + serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: coverableLineCache, + ); + + expect(result, { + 'type': 'CodeCoverage', + 'coverage': [ + { + 'source': 'package:foo/foo.dart', + 'script': { + 'type': '@Script', + 'fixedId': true, + 'id': 'libraries/1/scripts/package%3Afoo%2Ffoo.dart', + 'uri': 'package:foo/foo.dart', + '_kind': 'library', + }, + 'hits': [1, 1, 3, 1, 2, 0], + }, + ], + }); + + // coverableLineCache should contain every line mentioned in the report. + expect(coverableLineCache, >{ + 'package:foo/foo.dart': {1, 2, 3}, + }); + + expect(fakeVmServiceHost.hasRemainingExpectations, false); + }); + + testWithoutContext('Coverage collector avoids recompiling libraries in coverableLineCache', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + method: 'getVM', + jsonResponse: (VM.parse({})! + ..isolates = [ + IsolateRef.parse({ + 'id': '1', + })!, + ] + ).toJson(), + ), + FakeVmServiceRequest( + method: 'getVersion', + jsonResponse: Version(major: 4, minor: 13).toJson(), + ), + + // This collection sets librariesAlreadyCompiled. The response doesn't + // include any misses. + FakeVmServiceRequest( + method: 'getSourceReport', + args: { + 'isolateId': '1', + 'reports': ['Coverage'], + 'forceCompile': true, + 'reportLines': true, + 'libraryFilters': ['package:foo/'], + 'librariesAlreadyCompiled': ['package:foo/foo.dart'], + }, + jsonResponse: SourceReport( + ranges: [ + SourceReportRange( + scriptIndex: 0, + startPos: 0, + endPos: 0, + compiled: true, + coverage: SourceReportCoverage( + hits: [1, 3], + misses: [], + ), + ), + ], + scripts: [ + ScriptRef( + uri: 'package:foo/foo.dart', + id: '1', + ), + ], + ).toJson(), + ), + ], + ); + + final Map> coverableLineCache = >{ + 'package:foo/foo.dart': {1, 2, 3}, + }; + final Map result2 = await collect( + Uri(), + {'foo'}, + serviceOverride: fakeVmServiceHost.vmService, + coverableLineCache: coverableLineCache, + ); + + // Expect that line 2 is marked as missed, even though it wasn't mentioned + // in the getSourceReport response. + expect(result2, { + 'type': 'CodeCoverage', + 'coverage': [ + { + 'source': 'package:foo/foo.dart', + 'script': { + 'type': '@Script', + 'fixedId': true, + 'id': 'libraries/1/scripts/package%3Afoo%2Ffoo.dart', + 'uri': 'package:foo/foo.dart', + '_kind': 'library', + }, + 'hits': [1, 1, 2, 0, 3, 1], + }, + ], + }); + expect(coverableLineCache, >{ + 'package:foo/foo.dart': {1, 2, 3}, + }); + + expect(fakeVmServiceHost.hasRemainingExpectations, false); + }); } File writeFooBarPackagesJson(Directory tempDir) {