blob: 372bd48b729c737d90d2d74f51f877570094d474 [file] [log] [blame]
// 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);
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(4, 13);
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', () async {
final service = _mockService(4, 13);
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, fast isolate group deduping', () async {
final service = _mockService(4, 13, 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, no scoped output, '
'handles SentinelException from getSourceReport', () async {
final service = _mockService(4, 13);
when(service.getSourceReport(
'isolate',
['Coverage'],
forceCompile: true,
reportLines: 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', () 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},
});
});
test(
'Collect coverage, scoped output, '
'handles SourceReports that contain unfiltered ranges', () async {
// Regression test for https://github.com/dart-lang/tools/issues/530
final service = _mockService(4, 13);
when(service.getSourceReport(
'isolate',
['Coverage'],
forceCompile: true,
reportLines: true,
libraryFilters: ['package:foo/'],
)).thenAnswer((_) async => SourceReport(
ranges: [
_range(
0,
SourceReportCoverage(
hits: [12],
misses: [47],
),
),
_range(
1,
SourceReportCoverage(
hits: [86],
misses: [91],
),
),
],
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, {'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});
});
});
}