| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:convert' show jsonEncode; |
| import 'dart:io' show Directory, File; |
| |
| import 'package:coverage/src/hitmap.dart'; |
| import 'package:flutter_tools/src/test/coverage_collector.dart'; |
| import 'package:flutter_tools/src/test/test_device.dart' show TestDevice; |
| import 'package:flutter_tools/src/test/test_time_recorder.dart'; |
| import 'package:stream_channel/stream_channel.dart' show StreamChannel; |
| import 'package:vm_service/vm_service.dart'; |
| |
| import '../src/common.dart'; |
| import '../src/fake_vm_services.dart'; |
| import '../src/logging_logger.dart'; |
| |
| void main() { |
| testWithoutContext('Coverage collector Can handle coverage SentinelException', () 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: 51).toJson(), |
| ), |
| const FakeVmServiceRequest( |
| method: 'getScripts', |
| args: <String, Object>{ |
| 'isolateId': '1', |
| }, |
| jsonResponse: <String, Object>{ |
| 'type': 'Sentinel', |
| }, |
| ), |
| ], |
| ); |
| |
| final Map<String, Object?> result = await collect( |
| Uri(), |
| <String>{'foo'}, |
| serviceOverride: fakeVmServiceHost.vmService, |
| ); |
| |
| expect(result, <String, Object>{'type': 'CodeCoverage', 'coverage': <Object>[]}); |
| expect(fakeVmServiceHost.hasRemainingExpectations, false); |
| }); |
| |
| testWithoutContext('Coverage collector processes coverage and script data', () 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: 51).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'], |
| '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], |
| ), |
| ), |
| ], |
| scripts: <ScriptRef>[ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: '1', |
| ), |
| ], |
| ).toJson(), |
| ), |
| ], |
| ); |
| |
| final Map<String, Object?> result = await collect( |
| Uri(), |
| <String>{'foo'}, |
| serviceOverride: fakeVmServiceHost.vmService, |
| ); |
| |
| 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], |
| }, |
| ], |
| }); |
| expect(fakeVmServiceHost.hasRemainingExpectations, false); |
| }); |
| |
| testWithoutContext('Coverage collector with null libraryNames accepts all libraries', () async { |
| final FakeVmServiceHost fakeVmServiceHost = createFakeVmServiceHostWithFooAndBar(); |
| |
| final Map<String, Object?> result = await collect( |
| Uri(), |
| null, |
| serviceOverride: fakeVmServiceHost.vmService, |
| ); |
| |
| 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], |
| }, |
| <String, Object>{ |
| 'source': 'package:bar/bar.dart', |
| 'script': <String, Object>{ |
| 'type': '@Script', |
| 'fixedId': true, |
| 'id': 'libraries/1/scripts/package%3Abar%2Fbar.dart', |
| 'uri': 'package:bar/bar.dart', |
| '_kind': 'library', |
| }, |
| 'hits': <Object>[47, 1, 21, 1, 32, 0, 86, 0], |
| }, |
| ], |
| }); |
| expect(fakeVmServiceHost.hasRemainingExpectations, false); |
| }); |
| |
| testWithoutContext('Coverage collector with libraryFilters', () 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: 57).toJson(), |
| ), |
| FakeVmServiceRequest( |
| method: 'getSourceReport', |
| args: <String, Object>{ |
| 'isolateId': '1', |
| 'reports': <Object>['Coverage'], |
| 'forceCompile': true, |
| 'reportLines': true, |
| 'libraryFilters': <Object>['package:foo/'], |
| }, |
| jsonResponse: SourceReport( |
| ranges: <SourceReportRange>[ |
| SourceReportRange( |
| scriptIndex: 0, |
| startPos: 0, |
| endPos: 0, |
| compiled: true, |
| coverage: SourceReportCoverage( |
| hits: <int>[1, 3], |
| misses: <int>[2], |
| ), |
| ), |
| ], |
| scripts: <ScriptRef>[ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: '1', |
| ), |
| ], |
| ).toJson(), |
| ), |
| ], |
| ); |
| |
| final Map<String, Object?> result = await collect( |
| Uri(), |
| <String>{'foo'}, |
| serviceOverride: fakeVmServiceHost.vmService, |
| ); |
| |
| 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], |
| }, |
| ], |
| }); |
| expect(fakeVmServiceHost.hasRemainingExpectations, false); |
| }); |
| |
| testWithoutContext('Coverage collector with libraryFilters and null libraryNames', () 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: 57).toJson(), |
| ), |
| FakeVmServiceRequest( |
| method: 'getSourceReport', |
| args: <String, Object>{ |
| 'isolateId': '1', |
| 'reports': <Object>['Coverage'], |
| '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], |
| ), |
| ), |
| ], |
| scripts: <ScriptRef>[ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: '1', |
| ), |
| ], |
| ).toJson(), |
| ), |
| ], |
| ); |
| |
| final Map<String, Object?> result = await collect( |
| Uri(), |
| null, |
| serviceOverride: fakeVmServiceHost.vmService, |
| ); |
| |
| 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], |
| }, |
| ], |
| }); |
| 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 { |
| tempDir = Directory.systemTemp.createTempSync('flutter_coverage_collector_test.'); |
| final File packagesFile = writeFooBarPackagesJson(tempDir); |
| final Directory fooDir = Directory('${tempDir.path}/foo/'); |
| fooDir.createSync(); |
| final File fooFile = File('${fooDir.path}/foo.dart'); |
| fooFile.writeAsStringSync('hit\nnohit but ignored // coverage:ignore-line\nhit\n'); |
| |
| final String packagesPath = packagesFile.path; |
| final CoverageCollector collector = CoverageCollector( |
| libraryNames: <String>{'foo', 'bar'}, |
| verbose: false, |
| packagesPath: packagesPath, |
| resolver: await CoverageCollector.getResolver(packagesPath) |
| ); |
| await collector.collectCoverage( |
| TestTestDevice(), |
| serviceOverride: createFakeVmServiceHostWithFooAndBar(libraryFilters: <String>['package:foo/', 'package:bar/']).vmService, |
| ); |
| |
| Future<void> getHitMapAndVerify() async { |
| final Map<String, HitMap> gottenHitmap = <String, HitMap>{}; |
| await collector.finalizeCoverage(formatter: (Map<String, HitMap> hitmap) { |
| gottenHitmap.addAll(hitmap); |
| return ''; |
| }); |
| expect(gottenHitmap.keys.toList()..sort(), <String>['package:bar/bar.dart', 'package:foo/foo.dart']); |
| expect(gottenHitmap['package:foo/foo.dart']!.lineHits, <int, int>{1: 1, /* 2: 0, is ignored in file */ 3: 1}); |
| expect(gottenHitmap['package:bar/bar.dart']!.lineHits, <int, int>{21: 1, 32: 0, 47: 1, 86: 0}); |
| } |
| |
| Future<void> verifyHitmapEmpty() async { |
| final Map<String, HitMap> gottenHitmap = <String, HitMap>{}; |
| await collector.finalizeCoverage(formatter: (Map<String, HitMap> hitmap) { |
| gottenHitmap.addAll(hitmap); |
| return ''; |
| }); |
| expect(gottenHitmap.isEmpty, isTrue); |
| } |
| |
| // Get hit map the first time. |
| await getHitMapAndVerify(); |
| |
| // Getting the hitmap clears it so we now doesn't get any data. |
| await verifyHitmapEmpty(); |
| |
| // Collecting again gets us the same data even though the foo file has been deleted. |
| // This means that the fact that line 2 was ignored has been cached. |
| fooFile.deleteSync(); |
| await collector.collectCoverage( |
| TestTestDevice(), |
| serviceOverride: createFakeVmServiceHostWithFooAndBar(libraryFilters: <String>['package:foo/', 'package:bar/']).vmService, |
| ); |
| await getHitMapAndVerify(); |
| } finally { |
| tempDir?.deleteSync(recursive: true); |
| } |
| }); |
| |
| testWithoutContext('Coverage collector respects ignore whole file', () async { |
| Directory? tempDir; |
| try { |
| tempDir = Directory.systemTemp.createTempSync('flutter_coverage_collector_test.'); |
| final File packagesFile = writeFooBarPackagesJson(tempDir); |
| final Directory fooDir = Directory('${tempDir.path}/foo/'); |
| fooDir.createSync(); |
| final File fooFile = File('${fooDir.path}/foo.dart'); |
| fooFile.writeAsStringSync('hit\nnohit but ignored // coverage:ignore-file\nhit\n'); |
| |
| final String packagesPath = packagesFile.path; |
| final CoverageCollector collector = CoverageCollector( |
| libraryNames: <String>{'foo', 'bar'}, |
| verbose: false, |
| packagesPath: packagesPath, |
| resolver: await CoverageCollector.getResolver(packagesPath) |
| ); |
| await collector.collectCoverage( |
| TestTestDevice(), |
| serviceOverride: createFakeVmServiceHostWithFooAndBar(libraryFilters: <String>['package:foo/', 'package:bar/']).vmService, |
| ); |
| |
| final Map<String, HitMap> gottenHitmap = <String, HitMap>{}; |
| await collector.finalizeCoverage(formatter: (Map<String, HitMap> hitmap) { |
| gottenHitmap.addAll(hitmap); |
| return ''; |
| }); |
| expect(gottenHitmap.keys.toList()..sort(), <String>['package:bar/bar.dart']); |
| expect(gottenHitmap['package:bar/bar.dart']!.lineHits, <int, int>{21: 1, 32: 0, 47: 1, 86: 0}); |
| } finally { |
| tempDir?.deleteSync(recursive: true); |
| } |
| }); |
| |
| testWithoutContext('Coverage collector records test timings when provided TestTimeRecorder', () async { |
| Directory? tempDir; |
| try { |
| tempDir = Directory.systemTemp.createTempSync('flutter_coverage_collector_test.'); |
| final File packagesFile = writeFooBarPackagesJson(tempDir); |
| final Directory fooDir = Directory('${tempDir.path}/foo/'); |
| fooDir.createSync(); |
| final File fooFile = File('${fooDir.path}/foo.dart'); |
| fooFile.writeAsStringSync('hit\nnohit but ignored // coverage:ignore-line\nhit\n'); |
| |
| final String packagesPath = packagesFile.path; |
| final LoggingLogger logger = LoggingLogger(); |
| final TestTimeRecorder testTimeRecorder = TestTimeRecorder(logger); |
| final CoverageCollector collector = CoverageCollector( |
| libraryNames: <String>{'foo', 'bar'}, |
| verbose: false, |
| packagesPath: packagesPath, |
| resolver: await CoverageCollector.getResolver(packagesPath), |
| testTimeRecorder: testTimeRecorder |
| ); |
| await collector.collectCoverage( |
| TestTestDevice(), |
| serviceOverride: createFakeVmServiceHostWithFooAndBar(libraryFilters: <String>['package:foo/', 'package:bar/']).vmService, |
| ); |
| |
| // Expect one message for each phase. |
| final List<String> logPhaseMessages = testTimeRecorder.getPrintAsListForTesting().where((String m) => m.startsWith('Runtime for phase ')).toList(); |
| expect(logPhaseMessages, hasLength(TestTimePhases.values.length)); |
| |
| // Several phases actually does something, but here we just expect at |
| // least one phase to take a non-zero amount of time. |
| final List<String> logPhaseMessagesNonZero = logPhaseMessages.where((String m) => !m.contains(Duration.zero.toString())).toList(); |
| expect(logPhaseMessagesNonZero, isNotEmpty); |
| } finally { |
| tempDir?.deleteSync(recursive: true); |
| } |
| }); |
| } |
| |
| File writeFooBarPackagesJson(Directory tempDir) { |
| final File file = File('${tempDir.path}/packages.json'); |
| file.writeAsStringSync(jsonEncode(<String, dynamic>{ |
| 'configVersion': 2, |
| 'packages': <Map<String, String>>[ |
| <String, String>{ |
| 'name': 'foo', |
| 'rootUri': 'foo', |
| }, |
| <String, String>{ |
| 'name': 'bar', |
| 'rootUri': 'bar', |
| }, |
| ], |
| })); |
| return file; |
| } |
| |
| FakeVmServiceHost createFakeVmServiceHostWithFooAndBar({ |
| List<String>? libraryFilters, |
| }) { |
| return 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: 61).toJson(), |
| ), |
| FakeVmServiceRequest( |
| method: 'getSourceReport', |
| args: <String, Object>{ |
| 'isolateId': '1', |
| 'reports': <Object>['Coverage'], |
| 'forceCompile': true, |
| 'reportLines': true, |
| if (libraryFilters != null) 'libraryFilters': libraryFilters, |
| }, |
| jsonResponse: SourceReport( |
| ranges: <SourceReportRange>[ |
| SourceReportRange( |
| scriptIndex: 0, |
| startPos: 0, |
| endPos: 0, |
| compiled: true, |
| coverage: SourceReportCoverage( |
| hits: <int>[1, 3], |
| misses: <int>[2], |
| ), |
| ), |
| SourceReportRange( |
| scriptIndex: 1, |
| startPos: 0, |
| endPos: 0, |
| compiled: true, |
| coverage: SourceReportCoverage( |
| hits: <int>[47, 21], |
| misses: <int>[32, 86], |
| ), |
| ), |
| ], |
| scripts: <ScriptRef>[ |
| ScriptRef( |
| uri: 'package:foo/foo.dart', |
| id: '1', |
| ), |
| ScriptRef( |
| uri: 'package:bar/bar.dart', |
| id: '2', |
| ), |
| ], |
| ).toJson(), |
| ), |
| ], |
| ); |
| } |
| |
| class TestTestDevice extends TestDevice { |
| @override |
| Future<void> get finished => Future<void>.delayed(const Duration(seconds: 1)); |
| |
| @override |
| Future<void> kill() => Future<void>.value(); |
| |
| @override |
| Future<Uri?> get observatoryUri => Future<Uri?>.value(Uri()); |
| |
| @override |
| Future<StreamChannel<String>> start(String entrypointPath) { |
| throw UnimplementedError(); |
| } |
| } |