Support filtering coverage by isolate ID (#270)
Adds an `isolateIDs` parameter to `collect()` that, when specified limits coverage collection to the specified isolate IDs. Defaults to collecting coverage for all IDs.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 015311d..558bb47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.13.4
+ * Added a new named argument to `collect` for filtering the
+ coverage results by a set of VM isolate IDs.
+
## 0.13.3
* Migrates implementation of VM service protocol library from
diff --git a/lib/src/collect.dart b/lib/src/collect.dart
index ee44388..e886824 100644
--- a/lib/src/collect.dart
+++ b/lib/src/collect.dart
@@ -31,9 +31,12 @@
///
/// 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
+/// those VM isolates.
Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
bool waitPaused, bool includeDart, Set<String> scopedOutput,
- {Duration timeout}) async {
+ {Set<String> isolateIds, Duration timeout}) async {
scopedOutput ??= Set<String>();
if (serviceUri == null) throw ArgumentError('serviceUri must not be null');
@@ -63,7 +66,8 @@
await _waitIsolatesPaused(service, timeout: timeout);
}
- return await _getAllCoverage(service, includeDart, scopedOutput);
+ return await _getAllCoverage(
+ service, includeDart, scopedOutput, isolateIds);
} finally {
if (resume) {
await _resumeIsolates(service);
@@ -72,13 +76,14 @@
}
}
-Future<Map<String, dynamic>> _getAllCoverage(
- VmService service, bool includeDart, Set<String> scopedOutput) async {
+Future<Map<String, dynamic>> _getAllCoverage(VmService service,
+ bool includeDart, Set<String> scopedOutput, Set<String> isolateIds) async {
scopedOutput ??= Set<String>();
final vm = await service.getVM();
final allCoverage = <Map<String, dynamic>>[];
for (var isolateRef in vm.isolates) {
+ if (isolateIds != null && !isolateIds.contains(isolateRef.id)) continue;
if (scopedOutput.isNotEmpty) {
final scripts = await service.getScripts(isolateRef.id);
for (var script in scripts.scripts) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 067288b..596a97e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: coverage
-version: 0.13.3-dev
+version: 0.13.4-dev
author: Dart Team <misc@dartlang.org>
description: Coverage data manipulation and formatting
homepage: https://github.com/dart-lang/coverage
diff --git a/test/collect_coverage_api_test.dart b/test/collect_coverage_api_test.dart
index cf9f3d2..701fa5c 100644
--- a/test/collect_coverage_api_test.dart
+++ b/test/collect_coverage_api_test.dart
@@ -68,10 +68,31 @@
expect(uri.path.startsWith('coverage'), isTrue);
}
});
+
+ test('collect_coverage_api with isolateIds', () async {
+ final Map<String, dynamic> json = await _collectCoverage(isolateIds: true);
+ expect(json.keys, unorderedEquals(<String>['type', 'coverage']));
+ expect(json, containsPair('type', 'CodeCoverage'));
+
+ final List coverage = json['coverage'];
+ expect(coverage, isNotEmpty);
+
+ final Map<String, dynamic> testAppCoverage =
+ _getScriptCoverage(coverage, 'test_app.dart');
+ List<int> hits = testAppCoverage['hits'];
+ _expectHitCount(hits, 44, 0);
+ _expectHitCount(hits, 48, 0);
+
+ final Map<String, dynamic> isolateCoverage =
+ _getScriptCoverage(coverage, 'test_app_isolate.dart');
+ hits = isolateCoverage['hits'];
+ _expectHitCount(hits, 9, 1);
+ _expectHitCount(hits, 16, 1);
+ });
}
Future<Map<String, dynamic>> _collectCoverage(
- {Set<String> scopedOutput}) async {
+ {Set<String> scopedOutput, bool isolateIds = false}) async {
scopedOutput ??= Set<String>();
final openPort = await getOpenPort();
@@ -80,6 +101,7 @@
// Capture the VM service URI.
final serviceUriCompleter = Completer<Uri>();
+ final isolateIdCompleter = Completer<String>();
sampleProcess.stdout
.transform(utf8.decoder)
.transform(LineSplitter())
@@ -90,8 +112,40 @@
serviceUriCompleter.complete(serviceUri);
}
}
+ if (line.contains('isolateId = ')) {
+ isolateIdCompleter.complete(line.split(' = ')[1]);
+ }
});
- final Uri serviceUri = await serviceUriCompleter.future;
- return collect(serviceUri, true, true, false, scopedOutput, timeout: timeout);
+ final Uri serviceUri = await serviceUriCompleter.future;
+ final String isolateId = await isolateIdCompleter.future;
+ final Set<String> isolateIdSet = isolateIds ? Set.of([isolateId]) : null;
+
+ return collect(serviceUri, true, true, false, scopedOutput,
+ timeout: timeout, isolateIds: isolateIdSet);
+}
+
+// Returns the first coverage hitmap for the script with with the specified
+// script filename, ignoring leading path.
+Map<String, dynamic> _getScriptCoverage(
+ List<Map<String, dynamic>> coverage, String filename) {
+ for (Map<String, dynamic> isolateCoverage in coverage) {
+ final Uri script = Uri.parse(isolateCoverage['script']['uri']);
+ if (script.pathSegments.last == filename) {
+ return isolateCoverage;
+ }
+ }
+ return null;
+}
+
+/// Tests that the specified hitmap has the specified hit count for the
+/// specified line.
+void _expectHitCount(List<int> hits, int line, int hitCount) {
+ final int hitIndex = hits.indexOf(line);
+ if (hitIndex < 0) {
+ fail('No hit count for line $line');
+ }
+ final int actual = hits[hitIndex + 1];
+ expect(actual, equals(hitCount),
+ reason: 'Expected line $line to have $hitCount hits, but found $actual.');
}
diff --git a/test/lcov_test.dart b/test/lcov_test.dart
index 1c7e79f..d4bddd8 100644
--- a/test/lcov_test.dart
+++ b/test/lcov_test.dart
@@ -27,11 +27,11 @@
final Map<int, int> sampleAppHitMap = hitmap[_sampleAppFileUri];
- expect(sampleAppHitMap, containsPair(40, greaterThanOrEqualTo(1)),
+ expect(sampleAppHitMap, containsPair(44, greaterThanOrEqualTo(1)),
reason: 'be careful if you modify the test file');
- expect(sampleAppHitMap, containsPair(44, 0),
+ expect(sampleAppHitMap, containsPair(48, 0),
reason: 'be careful if you modify the test file');
- expect(sampleAppHitMap, isNot(contains(29)),
+ expect(sampleAppHitMap, isNot(contains(31)),
reason: 'be careful if you modify the test file');
});
diff --git a/test/test_files/test_app.dart b/test/test_files/test_app.dart
index 22204fe..3d7b79c 100644
--- a/test/test_files/test_app.dart
+++ b/test/test_files/test_app.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
+import 'dart:developer';
import 'dart:isolate';
// explicitly using a package import to validate hitmap coverage of packages
@@ -24,6 +25,10 @@
final Isolate isolate =
await Isolate.spawn(isolateTask, [port.sendPort, 1, 2], paused: true);
+ await Service.controlWebServer(enable: true);
+ final isolateID = Service.getIsolateID(isolate);
+ print('isolateId = $isolateID');
+
isolate.addOnExitListener(port.sendPort);
isolate.resume(isolate.pauseCapability);