// 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 args) async { CoverageHelper coverageHelper = new CoverageHelper(); List allArgs = []; 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 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 cTimeout = new Completer(); Timer timer = new Timer(new Duration(minutes: 20), () { cTimeout.complete("Timeout"); killProcess(); }); Completer 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 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 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 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 hits = {}; final Set misses = {}; final Set 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; } }