| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:coverage/coverage.dart'; |
| import 'package:mockito/annotations.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:test/test.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| |
| import 'collect_coverage_mock_test.mocks.dart'; |
| |
| @GenerateMocks([VmService]) |
| SourceReportRange _range(int scriptIndex, SourceReportCoverage coverage) => |
| SourceReportRange(scriptIndex: scriptIndex, coverage: coverage); |
| |
| Script _script(List<List<int>> tokenPosTable) => |
| Script(id: 'script', tokenPosTable: tokenPosTable); |
| |
| IsolateRef _isoRef(String id, String isoGroupId) => |
| IsolateRef(id: id, isolateGroupId: isoGroupId); |
| |
| IsolateGroupRef _isoGroupRef(String id) => IsolateGroupRef(id: id); |
| |
| IsolateGroup _isoGroup(String id, List<IsolateRef> isolates) => |
| IsolateGroup(id: id, isolates: isolates); |
| |
| class FakeSentinelException implements SentinelException { |
| @override |
| dynamic noSuchMethod(Invocation invocation) {} |
| } |
| |
| MockVmService _mockService( |
| int majorVersion, |
| int minorVersion, { |
| Map<String, List<String>> isolateGroups = const { |
| 'isolateGroup': ['isolate'], |
| }, |
| }) { |
| final service = MockVmService(); |
| final isoRefs = <IsolateRef>[]; |
| final isoGroupRefs = <IsolateGroupRef>[]; |
| final isoGroups = <IsolateGroup>[]; |
| for (final group in isolateGroups.entries) { |
| isoGroupRefs.add(_isoGroupRef(group.key)); |
| final isosOfGroup = <IsolateRef>[]; |
| for (final isoId in group.value) { |
| isosOfGroup.add(_isoRef(isoId, group.key)); |
| } |
| isoGroups.add(_isoGroup(group.key, isosOfGroup)); |
| isoRefs.addAll(isosOfGroup); |
| } |
| when(service.getVM()).thenAnswer( |
| (_) async => VM(isolates: isoRefs, isolateGroups: isoGroupRefs)); |
| for (final group in isoGroups) { |
| when(service.getIsolateGroup(group.id)).thenAnswer((_) async => group); |
| } |
| when(service.getVersion()).thenAnswer( |
| (_) async => Version(major: majorVersion, minor: minorVersion)); |
| return service; |
| } |
| |
| void main() { |
| group('Mock VM Service', () { |
| test('Collect coverage', () async { |
| final service = _mockService(3, 0); |
| when(service.getSourceReport('isolate', ['Coverage'], forceCompile: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [15], |
| misses: [32], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [75], |
| misses: [34], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| when(service.getObject('isolate', 'foo')) |
| .thenAnswer((_) async => _script([ |
| [12, 15, 7], |
| [47, 32, 19], |
| ])); |
| when(service.getObject('isolate', 'bar')) |
| .thenAnswer((_) async => _script([ |
| [52, 34, 10], |
| [95, 75, 3], |
| ])); |
| |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 2); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| }); |
| |
| test('Collect coverage, report lines', () async { |
| final service = _mockService(3, 51); |
| when(service.getSourceReport('isolate', ['Coverage'], |
| forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [12], |
| misses: [47], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [95], |
| misses: [52], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 2); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| }); |
| |
| test('Collect coverage, scoped output, no library filters', () async { |
| final service = _mockService(3, 0); |
| when(service.getScripts('isolate')).thenAnswer((_) async => ScriptList( |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| when(service.getSourceReport('isolate', ['Coverage'], |
| scriptId: 'foo', forceCompile: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [15], |
| misses: [32], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ], |
| )); |
| when(service.getObject('isolate', 'foo')) |
| .thenAnswer((_) async => _script([ |
| [12, 15, 7], |
| [47, 32, 19], |
| ])); |
| |
| final jsonResult = await collect(Uri(), false, false, false, {'foo'}, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 1); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| }); |
| |
| test('Collect coverage, scoped output, library filters', () async { |
| final service = _mockService(3, 57); |
| when(service.getSourceReport('isolate', ['Coverage'], |
| forceCompile: true, |
| reportLines: true, |
| libraryFilters: ['package:foo/'])) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [12], |
| misses: [47], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ], |
| )); |
| |
| final jsonResult = await collect(Uri(), false, false, false, {'foo'}, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 1); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| }); |
| |
| test('Collect coverage, old isolate group deduping', () async { |
| final service = _mockService(3, 60, isolateGroups: { |
| 'isolateGroupA': ['isolate1', 'isolate2'], |
| 'isolateGroupB': ['isolate3'], |
| }); |
| when(service.getSourceReport('isolate1', ['Coverage'], |
| forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [12], |
| misses: [47], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [95], |
| misses: [52], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| when(service.getSourceReport('isolate3', ['Coverage'], |
| forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [34], |
| misses: [61], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:baz/baz.dart', |
| id: 'baz', |
| ), |
| ], |
| )); |
| |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 3); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| expect(result['package:baz/baz.dart']?.lineHits, {34: 1, 61: 0}); |
| verifyNever(service.getSourceReport('isolate2', ['Coverage'], |
| forceCompile: true, reportLines: true)); |
| verify(service.getIsolateGroup('isolateGroupA')); |
| verify(service.getIsolateGroup('isolateGroupB')); |
| }); |
| |
| test('Collect coverage, fast isolate group deduping', () async { |
| final service = _mockService(3, 61, isolateGroups: { |
| 'isolateGroupA': ['isolate1', 'isolate2'], |
| 'isolateGroupB': ['isolate3'], |
| }); |
| when(service.getSourceReport('isolate1', ['Coverage'], |
| forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [12], |
| misses: [47], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [95], |
| misses: [52], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| when(service.getSourceReport('isolate3', ['Coverage'], |
| forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [34], |
| misses: [61], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:baz/baz.dart', |
| id: 'baz', |
| ), |
| ], |
| )); |
| |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 3); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| expect(result['package:baz/baz.dart']?.lineHits, {34: 1, 61: 0}); |
| verifyNever(service.getSourceReport('isolate2', ['Coverage'], |
| forceCompile: true, reportLines: true)); |
| verifyNever(service.getIsolateGroup('isolateGroupA')); |
| verifyNever(service.getIsolateGroup('isolateGroupB')); |
| }); |
| |
| test( |
| 'Collect coverage, scoped output, no library filters, ' |
| 'handles SentinelException from getScripts', () async { |
| final service = _mockService(3, 0); |
| when(service.getScripts('isolate')).thenThrow(FakeSentinelException()); |
| |
| final jsonResult = await collect( |
| Uri(), false, false, false, {'foo', 'bar'}, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 0); |
| }); |
| |
| test( |
| 'Collect coverage, scoped output, no library filters, ' |
| 'handles SentinelException from getSourceReport', () async { |
| final service = _mockService(3, 51); |
| when(service.getScripts('isolate')).thenAnswer((_) async => ScriptList( |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| when(service.getSourceReport('isolate', ['Coverage'], |
| scriptId: 'foo', forceCompile: true, reportLines: true)) |
| .thenThrow(FakeSentinelException()); |
| when(service.getSourceReport('isolate', ['Coverage'], |
| scriptId: 'bar', forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [95], |
| misses: [52], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| |
| final jsonResult = await collect( |
| Uri(), false, false, false, {'foo', 'bar'}, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| expect(result.length, 1); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| }); |
| |
| test( |
| 'Collect coverage, no scoped output, ' |
| 'handles SentinelException from getSourceReport', () async { |
| final service = _mockService(3, 0); |
| when(service.getSourceReport('isolate', ['Coverage'], forceCompile: true)) |
| .thenThrow(FakeSentinelException()); |
| |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 0); |
| }); |
| |
| test('Collect coverage, coverableLineCache, old vm service', () async { |
| // Expect that getSourceReport's librariesAlreadyCompiled param is not set |
| // when coverableLineCache is non-null but the service version is too old. |
| final service = _mockService(4, 12); |
| when(service.getSourceReport('isolate', ['Coverage'], |
| forceCompile: true, reportLines: true)) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [12], |
| misses: [47], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [95], |
| misses: [52], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| |
| final coverableLineCache = <String, Set<int>>{}; |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| coverableLineCache: coverableLineCache, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 2); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| }); |
| |
| test('Collect coverage, coverableLineCache', () async { |
| // Expect that on the first getSourceReport call, librariesAlreadyCompiled |
| // is empty. |
| final service = _mockService(4, 13); |
| when( |
| service.getSourceReport('isolate', ['Coverage'], |
| forceCompile: true, |
| reportLines: true, |
| librariesAlreadyCompiled: [])) |
| .thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [12], |
| misses: [47], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [95], |
| misses: [52], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ], |
| )); |
| |
| final coverableLineCache = <String, Set<int>>{}; |
| final jsonResult = await collect(Uri(), false, false, false, null, |
| coverableLineCache: coverableLineCache, |
| serviceOverrideForTesting: service); |
| final result = await HitMap.parseJson( |
| jsonResult['coverage'] as List<Map<String, dynamic>>); |
| |
| expect(result.length, 2); |
| expect(result['package:foo/foo.dart']?.lineHits, {12: 1, 47: 0}); |
| expect(result['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| |
| // The coverableLineCache should now be filled with all the lines that |
| // were hit or missed. |
| expect(coverableLineCache, { |
| 'package:foo/foo.dart': {12, 47}, |
| 'package:bar/bar.dart': {95, 52}, |
| }); |
| |
| // The second getSourceReport call should now list all the libraries we've |
| // seen. The response won't contain any misses for these libraries, |
| // because they won't be force compiled. We'll also return a 3rd library, |
| // which will contain misses, as it hasn't been compiled yet. |
| when( |
| service.getSourceReport('isolate', ['Coverage'], |
| forceCompile: true, |
| reportLines: true, |
| librariesAlreadyCompiled: [ |
| 'package:foo/foo.dart', |
| 'package:bar/bar.dart' |
| ])).thenAnswer((_) async => SourceReport( |
| ranges: [ |
| _range( |
| 0, |
| SourceReportCoverage( |
| hits: [47], |
| ), |
| ), |
| _range( |
| 1, |
| SourceReportCoverage( |
| hits: [95], |
| ), |
| ), |
| _range( |
| 2, |
| SourceReportCoverage( |
| hits: [36], |
| misses: [81], |
| ), |
| ), |
| ], |
| scripts: [ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: 'foo', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: 'bar', |
| ), |
| ScriptRef( |
| uri: 'package:baz/baz.dart', |
| id: 'baz', |
| ), |
| ], |
| )); |
| |
| final jsonResult2 = await collect(Uri(), false, false, false, null, |
| coverableLineCache: coverableLineCache, |
| serviceOverrideForTesting: service); |
| final result2 = await HitMap.parseJson( |
| jsonResult2['coverage'] as List<Map<String, dynamic>>); |
| |
| // The missed lines still appear in foo and bar, even though they weren't |
| // returned in the response. They were read from the cache. |
| expect(result2.length, 3); |
| expect(result2['package:foo/foo.dart']?.lineHits, {12: 0, 47: 1}); |
| expect(result2['package:bar/bar.dart']?.lineHits, {95: 1, 52: 0}); |
| expect(result2['package:baz/baz.dart']?.lineHits, {36: 1, 81: 0}); |
| |
| // The coverableLineCache should now also contain the baz library. |
| expect(coverableLineCache, { |
| 'package:foo/foo.dart': {12, 47}, |
| 'package:bar/bar.dart': {95, 52}, |
| 'package:baz/baz.dart': {36, 81}, |
| }); |
| }); |
| }); |
| } |