Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
Add coverableLineCache param to collect
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe committed Oct 17, 2023
1 parent bcfd888 commit 06d2897
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 21 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.7.0

- Update `package:vm_service` constraints to '>=12.0.0 <13.0.0'.
- Add `coverableLineCache` parameter to `collect`. This allows the set of
coverable lines to be cached between calls to `collect`, avoiding the need to
force compile the same libraries repeatedly. This is only useful when running
multiple coverage collections over the same libraries.

## 1.6.4

- allow omitting space between `//` and `coverage` in coverage ignore comments
Expand Down
89 changes: 70 additions & 19 deletions lib/src/collect.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE');
/// If [scopedOutput] is non-empty, coverage will be restricted so that only
/// scripts that start with any of the provided paths are considered.
///
/// if [isolateIds] is set, the coverage gathering will be restricted to only
/// If [isolateIds] is set, the coverage gathering will be restricted to only
/// those VM isolates.
///
/// If [coverableLineCache] is set, the collector will avoid recompiling
/// libraries it has already seen (see VmService.getSourceReport's
/// librariesAlreadyCompiled parameter). This is only useful when doing more
/// than one [collect] call over the same libraries. Pass an empty map to the
/// first call, and then pass the same map to all subsequent calls.
///
/// [serviceOverrideForTesting] is for internal testing only, and should not be
/// set by users.
Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
Expand All @@ -52,6 +58,7 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
Duration? timeout,
bool functionCoverage = false,
bool branchCoverage = false,
Map<String, Set<int>>? coverableLineCache,
VmService? serviceOverrideForTesting}) async {
scopedOutput ??= <String>{};

Expand Down Expand Up @@ -92,7 +99,7 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
}

return await _getAllCoverage(service, includeDart, functionCoverage,
branchCoverage, scopedOutput, isolateIds);
branchCoverage, scopedOutput, isolateIds, coverableLineCache);
} finally {
if (resume) {
await _resumeIsolates(service);
Expand All @@ -115,7 +122,8 @@ Future<Map<String, dynamic>> _getAllCoverage(
bool functionCoverage,
bool branchCoverage,
Set<String>? scopedOutput,
Set<String>? isolateIds) async {
Set<String>? isolateIds,
Map<String, Set<int>>? coverableLineCache) async {
scopedOutput ??= <String>{};
final vm = await service.getVM();
final allCoverage = <Map<String, dynamic>>[];
Expand All @@ -124,16 +132,22 @@ Future<Map<String, dynamic>> _getAllCoverage(
final branchCoverageSupported = _versionCheck(version, 3, 56);
final libraryFilters = _versionCheck(version, 3, 57);
final fastIsoGroups = _versionCheck(version, 3, 61);
final lineCacheSupported = _versionCheck(version, 4, 13);

if (branchCoverage && !branchCoverageSupported) {
branchCoverage = false;
stderr.writeln('Branch coverage was requested, but is not supported'
' by the VM version. Try updating to a newer version of Dart');
}

final sourceReportKinds = [
SourceReportKind.kCoverage,
if (branchCoverage) SourceReportKind.kBranchCoverage,
];

final librariesAlreadyCompiled =
lineCacheSupported ? coverableLineCache?.keys.toList() : null;

// Program counters are shared between isolates in the same group. So we need
// to make sure we're only gathering coverage data for one isolate in each
// group, otherwise we'll double count the hits.
Expand Down Expand Up @@ -173,15 +187,24 @@ Future<Map<String, dynamic>> _getAllCoverage(
late final SourceReport scriptReport;
try {
scriptReport = await service.getSourceReport(
isolateRef.id!, sourceReportKinds,
forceCompile: true,
scriptId: script.id,
reportLines: reportLines ? true : null);
isolateRef.id!,
sourceReportKinds,
forceCompile: true,
scriptId: script.id,
reportLines: reportLines ? true : null,
librariesAlreadyCompiled: librariesAlreadyCompiled,
);
} on SentinelException {
continue;
}
final coverage = await _getCoverageJson(service, isolateRef,
scriptReport, includeDart, functionCoverage, reportLines);
final coverage = await _processSourceReport(
service,
isolateRef,
scriptReport,
includeDart,
functionCoverage,
reportLines,
coverableLineCache);
allCoverage.addAll(coverage);
}
} else {
Expand All @@ -195,12 +218,19 @@ Future<Map<String, dynamic>> _getAllCoverage(
libraryFilters: scopedOutput.isNotEmpty && libraryFilters
? List.from(scopedOutput.map((filter) => 'package:$filter/'))
: null,
librariesAlreadyCompiled: librariesAlreadyCompiled,
);
} on SentinelException {
continue;
}
final coverage = await _getCoverageJson(service, isolateRef,
isolateReport, includeDart, functionCoverage, reportLines);
final coverage = await _processSourceReport(
service,
isolateRef,
isolateReport,
includeDart,
functionCoverage,
reportLines,
coverableLineCache);
allCoverage.addAll(coverage);
}
}
Expand Down Expand Up @@ -276,13 +306,14 @@ int? _getLineFromTokenPos(Script script, int tokenPos) {
}

/// Returns a JSON coverage list backward-compatible with pre-1.16.0 SDKs.
Future<List<Map<String, dynamic>>> _getCoverageJson(
Future<List<Map<String, dynamic>>> _processSourceReport(
VmService service,
IsolateRef isolateRef,
SourceReport report,
bool includeDart,
bool functionCoverage,
bool reportLines) async {
bool reportLines,
Map<String, Set<int>>? coverableLineCache) async {
final hitMaps = <Uri, HitMap>{};
final scripts = <ScriptRef, Script>{};
final libraries = <LibraryRef>{};
Expand Down Expand Up @@ -333,7 +364,18 @@ Future<List<Map<String, dynamic>>> _getCoverageJson(

for (var range in report.ranges!) {
final scriptRef = report.scripts![range.scriptIndex!];
final scriptUri = Uri.parse(scriptRef.uri!);
final scriptUriString = scriptRef.uri!;
final scriptUri = Uri.parse(scriptUriString);

// If we have a coverableLineCache, use it in the same way we use
// SourceReportCoverage.misses: to add zeros to the coverage result for all
// the lines that don't have a hit. Afterwards, add all the lines that were
// hit or missed to the cache, so that the next coverage collection won't
// need to compile this libarry.
Set<int>? coverableLines;
if (coverableLineCache != null) {
coverableLines = coverableLineCache[scriptUriString] ??= <int>{};
}

// Not returned in scripts section of source report.
if (scriptUri.scheme == 'evaluate') continue;
Expand Down Expand Up @@ -379,7 +421,8 @@ Future<List<Map<String, dynamic>>> _getCoverageJson(

if (coverage == null) continue;

void forEachLine(List<int> tokenPositions, void Function(int line) body) {
void forEachLine(List<int>? tokenPositions, void Function(int line) body) {
if (tokenPositions == null) return;
for (final pos in tokenPositions) {
final line = reportLines ? pos : _getLineFromTokenPos(script!, pos);
if (line == null) {
Expand All @@ -393,14 +436,22 @@ Future<List<Map<String, dynamic>>> _getCoverageJson(
}
}

forEachLine(coverage.hits!, (line) {
if (coverableLines != null) {
for (final line in coverableLines) {
hits.lineHits.putIfAbsent(line, () => 0);
}
}

forEachLine(coverage.hits, (line) {
hits.lineHits.increment(line);
coverableLines?.add(line);
if (hits.funcNames != null && hits.funcNames!.containsKey(line)) {
hits.funcHits!.increment(line);
}
});
forEachLine(coverage.misses!, (line) {
forEachLine(coverage.misses, (line) {
hits.lineHits.putIfAbsent(line, () => 0);
coverableLines?.add(line);
});
hits.funcNames?.forEach((line, funcName) {
hits.funcHits?.putIfAbsent(line, () => 0);
Expand All @@ -409,10 +460,10 @@ Future<List<Map<String, dynamic>>> _getCoverageJson(
final branchCoverage = range.branchCoverage;
if (branchCoverage != null) {
hits.branchHits ??= <int, int>{};
forEachLine(branchCoverage.hits!, (line) {
forEachLine(branchCoverage.hits, (line) {
hits.branchHits!.increment(line);
});
forEachLine(branchCoverage.misses!, (line) {
forEachLine(branchCoverage.misses, (line) {
hits.branchHits!.putIfAbsent(line, () => 0);
});
}
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: coverage
version: 1.6.4
version: 1.7.0
description: Coverage data manipulation and formatting
repository: https://github.com/dart-lang/coverage

Expand All @@ -13,7 +13,7 @@ dependencies:
path: ^1.8.0
source_maps: ^0.10.10
stack_trace: ^1.10.0
vm_service: '>=11.9.0 <13.0.0'
vm_service: '>=12.0.0 <13.0.0'

dev_dependencies:
benchmark_harness: ^2.2.0
Expand Down
Loading

0 comments on commit 06d2897

Please sign in to comment.