| // Copyright (c) 2020, 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.md file. |
| |
| // @dart = 2.9 |
| |
| import 'dart:async'; |
| |
| import 'vm_service_helper.dart' as vmService; |
| |
| main(List<String> args) async { |
| CoverageHelper coverageHelper = new CoverageHelper(); |
| |
| List<String> allArgs = <String>[]; |
| allArgs.addAll([ |
| "--disable-dart-dev", |
| "--enable-asserts", |
| "--pause_isolates_on_exit", |
| ]); |
| allArgs.addAll(args); |
| |
| coverageHelper.start(allArgs); |
| } |
| |
| class CoverageHelper extends vmService.LaunchingVMServiceHelper { |
| final bool forceCompilation; |
| final bool printHits; |
| |
| CoverageHelper({this.forceCompilation: false, this.printHits: true}); |
| |
| @override |
| Future<void> run() async { |
| vmService.VM vm = await serviceClient.getVM(); |
| if (vm.isolates.length != 1) { |
| throw "Expected 1 isolate, got ${vm.isolates.length}"; |
| } |
| vmService.IsolateRef isolateRef = vm.isolates.single; |
| await waitUntilIsolateIsRunnable(isolateRef.id); |
| await serviceClient.resume(isolateRef.id); |
| Completer<String> cTimeout = new Completer(); |
| Timer timer = new Timer(new Duration(minutes: 20), () { |
| cTimeout.complete("Timeout"); |
| killProcess(); |
| }); |
| |
| Completer<String> cRunDone = new Completer(); |
| // ignore: unawaited_futures |
| waitUntilPaused(isolateRef.id).then((value) => cRunDone.complete("Done")); |
| |
| await Future.any([cRunDone.future, cTimeout.future, cProcessExited.future]); |
| |
| timer.cancel(); |
| |
| if (!await isPausedAtExit(isolateRef.id)) { |
| killProcess(); |
| throw "Expected to be paused at exit, but is just paused!"; |
| } |
| |
| // Get and process coverage information. |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| vmService.SourceReport sourceReport = await serviceClient.getSourceReport( |
| isolateRef.id, [vmService.SourceReportKind.kCoverage], |
| forceCompile: forceCompilation); |
| print("Got source report from VM in ${stopwatch.elapsedMilliseconds} ms"); |
| stopwatch.reset(); |
| Map<Uri, Coverage> coverages = {}; |
| for (vmService.SourceReportRange range in sourceReport.ranges) { |
| vmService.ScriptRef script = sourceReport.scripts[range.scriptIndex]; |
| Uri scriptUri = Uri.parse(script.uri); |
| if (!includeCoverageFor(scriptUri)) continue; |
| Coverage coverage = coverages[scriptUri] ??= new Coverage(); |
| |
| vmService.SourceReportCoverage sourceReportCoverage = range.coverage; |
| if (sourceReportCoverage == null) { |
| // Range not compiled. Record the range if provided. |
| assert(!range.compiled); |
| if (range.startPos >= 0 || range.endPos >= 0) { |
| coverage.notCompiled |
| .add(new StartEndPair(range.startPos, range.endPos)); |
| } |
| continue; |
| } |
| coverage.hits.addAll(sourceReportCoverage.hits); |
| coverage.misses.addAll(sourceReportCoverage.misses); |
| } |
| print("Processed source report from VM in " |
| "${stopwatch.elapsedMilliseconds} ms"); |
| stopwatch.reset(); |
| |
| // It's paused at exit, so resuming should allow us to exit. |
| await serviceClient.resume(isolateRef.id); |
| |
| for (MapEntry<Uri, Coverage> entry in coverages.entries) { |
| assert(entry.value.hits.intersection(entry.value.misses).isEmpty); |
| if (entry.value.hits.isEmpty && |
| entry.value.misses.isEmpty && |
| entry.value.notCompiled.isEmpty) { |
| continue; |
| } |
| print(entry.key); |
| if (printHits) { |
| print("Hits: ${entry.value.hits.toList()..sort()}"); |
| } |
| print("Misses: ${entry.value.misses.toList()..sort()}"); |
| print("Not compiled: ${entry.value.notCompiled.toList()..sort()}"); |
| print(""); |
| } |
| } |
| |
| Completer<String> cProcessExited = new Completer(); |
| void processExited(int exitCode) { |
| cProcessExited.complete("Exit"); |
| } |
| |
| bool includeCoverageFor(Uri uri) { |
| if (uri.scheme == "dart") { |
| return false; |
| } |
| if (uri.scheme == "package") { |
| return uri.pathSegments.first == "front_end" || |
| uri.pathSegments.first == "_fe_analyzer_shared" || |
| uri.pathSegments.first == "kernel"; |
| } |
| return true; |
| } |
| } |
| |
| class Coverage { |
| final Set<int> hits = {}; |
| final Set<int> misses = {}; |
| final Set<StartEndPair> notCompiled = {}; |
| } |
| |
| class StartEndPair implements Comparable { |
| final int startPos; |
| final int endPos; |
| |
| StartEndPair(this.startPos, this.endPos); |
| |
| String toString() => "[$startPos - $endPos]"; |
| |
| @override |
| int compareTo(Object other) { |
| if (other is! StartEndPair) return -1; |
| StartEndPair o = other; |
| return startPos - o.startPos; |
| } |
| } |