@@ -19,7 +19,7 @@ import 'watcher.dart';
19
19
20
20
/// A class that collects code coverage data during test runs.
21
21
class CoverageCollector extends TestWatcher {
22
- CoverageCollector ({this .libraryPredicate , this .verbose = true , @required this .packagesPath});
22
+ CoverageCollector ({this .libraryNames , this .verbose = true , @required this .packagesPath});
23
23
24
24
/// True when log messages should be emitted.
25
25
final bool verbose;
@@ -31,9 +31,9 @@ class CoverageCollector extends TestWatcher {
31
31
/// Map of file path to coverage hit map for that file.
32
32
Map <String , coverage.HitMap > _globalHitmap;
33
33
34
- /// Predicate function that returns true if the specified library URI should
35
- /// be included in the computed coverage .
36
- bool Function ( String ) libraryPredicate ;
34
+ /// The names of the libraries to gather coverage for. If null, all libraries
35
+ /// will be accepted .
36
+ Set < String > libraryNames ;
37
37
38
38
@override
39
39
Future <void > handleFinishedTest (TestDevice testDevice) async {
@@ -83,7 +83,7 @@ class CoverageCollector extends TestWatcher {
83
83
Future <void > collectCoverageIsolate (Uri observatoryUri) async {
84
84
assert (observatoryUri != null );
85
85
_logMessage ('collecting coverage data from $observatoryUri ...' );
86
- final Map <String , dynamic > data = await collect (observatoryUri, libraryPredicate );
86
+ final Map <String , dynamic > data = await collect (observatoryUri, libraryNames );
87
87
if (data == null ) {
88
88
throw Exception ('Failed to collect coverage.' );
89
89
}
@@ -121,7 +121,7 @@ class CoverageCollector extends TestWatcher {
121
121
final Future <void > collectionComplete = testDevice.observatoryUri
122
122
.then ((Uri observatoryUri) {
123
123
_logMessage ('collecting coverage data from $testDevice at $observatoryUri ...' );
124
- return collect (observatoryUri, libraryPredicate )
124
+ return collect (observatoryUri, libraryNames )
125
125
.then <void >((Map <String , dynamic > result) {
126
126
if (result == null ) {
127
127
throw Exception ('Failed to collect coverage.' );
@@ -237,91 +237,106 @@ Future<FlutterVmService> _defaultConnect(Uri serviceUri) {
237
237
serviceUri, compression: CompressionOptions .compressionOff, logger: globals.logger,);
238
238
}
239
239
240
- Future <Map <String , dynamic >> collect (Uri serviceUri, bool Function ( String ) libraryPredicate , {
240
+ Future <Map <String , dynamic >> collect (Uri serviceUri, Set < String > libraryNames , {
241
241
bool waitPaused = false ,
242
242
String debugName,
243
243
Future <FlutterVmService > Function (Uri ) connector = _defaultConnect,
244
244
@visibleForTesting bool forceSequential = false ,
245
245
}) async {
246
246
final FlutterVmService vmService = await connector (serviceUri);
247
- final Map <String , dynamic > result = await _getAllCoverage (vmService.service, libraryPredicate , forceSequential);
247
+ final Map <String , dynamic > result = await _getAllCoverage (vmService.service, libraryNames , forceSequential);
248
248
await vmService.dispose ();
249
249
return result;
250
250
}
251
251
252
252
Future <Map <String , dynamic >> _getAllCoverage (
253
253
vm_service.VmService service,
254
- bool Function ( String ) libraryPredicate ,
254
+ Set < String > libraryNames ,
255
255
bool forceSequential,
256
256
) async {
257
257
final vm_service.Version version = await service.getVersion ();
258
- final bool reportLines = (version.major == 3 && version.minor >= 51 ) || version.major > 3 ;
258
+ final bool libraryFilters = (version.major == 3 && version.minor >= 57 ) || version.major > 3 ;
259
259
final vm_service.VM vm = await service.getVM ();
260
260
final List <Map <String , dynamic >> coverage = < Map <String , dynamic >> [];
261
+ bool libraryPredicate (String libraryName) {
262
+ if (libraryNames == null ) {
263
+ return true ;
264
+ }
265
+ final Uri uri = Uri .parse (libraryName);
266
+ if (uri.scheme != 'package' ) {
267
+ return false ;
268
+ }
269
+ final String scope = uri.path.split ('/' ).first;
270
+ return libraryNames.contains (scope);
271
+ }
261
272
for (final vm_service.IsolateRef isolateRef in vm.isolates) {
262
273
if (isolateRef.isSystemIsolate) {
263
274
continue ;
264
275
}
265
- vm_service.ScriptList scriptList;
266
- try {
267
- scriptList = await service.getScripts (isolateRef.id);
268
- } on vm_service.SentinelException {
269
- continue ;
270
- }
271
-
272
- final List <Future <void >> futures = < Future <void >> [];
273
- final Map <String , vm_service.Script > scripts = < String , vm_service.Script > {};
274
- final Map <String , vm_service.SourceReport > sourceReports = < String , vm_service.SourceReport > {};
275
- // For each ScriptRef loaded into the VM, load the corresponding Script and
276
- // SourceReport object.
277
-
278
- for (final vm_service.ScriptRef script in scriptList.scripts) {
279
- final String libraryUri = script.uri;
280
- if (! libraryPredicate (libraryUri)) {
281
- continue ;
282
- }
283
- final String scriptId = script.id;
284
- final Future <void > getSourceReport = service.getSourceReport (
285
- isolateRef.id,
286
- < String > ['Coverage' ],
287
- scriptId: scriptId,
288
- forceCompile: true ,
289
- reportLines: reportLines ? true : null ,
290
- )
291
- .then ((vm_service.SourceReport report) {
292
- sourceReports[scriptId] = report;
293
- });
294
- if (forceSequential) {
295
- await null ;
296
- }
297
- futures.add (getSourceReport);
298
- if (reportLines) {
276
+ if (libraryFilters) {
277
+ final vm_service.SourceReport sourceReport = await service.getSourceReport (
278
+ isolateRef.id,
279
+ < String > ['Coverage' ],
280
+ forceCompile: true ,
281
+ reportLines: true ,
282
+ libraryFilters: libraryNames == null ? null : List <String >.from (
283
+ libraryNames.map ((String name) => 'package:$name /' )),
284
+ );
285
+ _buildCoverageMap (
286
+ < String , vm_service.Script > {},
287
+ < vm_service.SourceReport > [sourceReport],
288
+ coverage,
289
+ );
290
+ } else {
291
+ vm_service.ScriptList scriptList;
292
+ try {
293
+ scriptList = await service.getScripts (isolateRef.id);
294
+ } on vm_service.SentinelException {
299
295
continue ;
300
296
}
301
- final Future <void > getObject = service
302
- .getObject (isolateRef.id, scriptId)
303
- .then ((vm_service.Obj response) {
304
- final vm_service.Script script = response as vm_service.Script ;
305
- scripts[scriptId] = script;
297
+
298
+ final List <Future <void >> futures = < Future <void >> [];
299
+ final Map <String , vm_service.Script > scripts = < String , vm_service.Script > {};
300
+ final List <vm_service.SourceReport > sourceReports = < vm_service.SourceReport > [];
301
+
302
+ // For each ScriptRef loaded into the VM, load the corresponding Script
303
+ // and SourceReport object.
304
+ for (final vm_service.ScriptRef script in scriptList.scripts) {
305
+ final String libraryUri = script.uri;
306
+ if (! libraryPredicate (libraryUri)) {
307
+ continue ;
308
+ }
309
+ final String scriptId = script.id;
310
+ final Future <void > getSourceReport = service.getSourceReport (
311
+ isolateRef.id,
312
+ < String > ['Coverage' ],
313
+ scriptId: scriptId,
314
+ forceCompile: true ,
315
+ reportLines: true ,
316
+ )
317
+ .then ((vm_service.SourceReport report) {
318
+ sourceReports.add (report);
306
319
});
307
- futures.add (getObject);
320
+ if (forceSequential) {
321
+ await null ;
322
+ }
323
+ futures.add (getSourceReport);
324
+ }
325
+ await Future .wait (futures);
326
+ _buildCoverageMap (scripts, sourceReports, coverage);
308
327
}
309
- await Future .wait (futures);
310
- _buildCoverageMap (scripts, sourceReports, coverage, reportLines);
311
328
}
312
329
return < String , dynamic > {'type' : 'CodeCoverage' , 'coverage' : coverage};
313
330
}
314
331
315
332
// Build a hitmap of Uri -> Line -> Hit Count for each script object.
316
333
void _buildCoverageMap (
317
334
Map <String , vm_service.Script > scripts,
318
- Map < String , vm_service.SourceReport > sourceReports,
335
+ List < vm_service.SourceReport > sourceReports,
319
336
List <Map <String , dynamic >> coverage,
320
- bool reportLines,
321
337
) {
322
338
final Map <String , Map <int , int >> hitMaps = < String , Map <int , int >> {};
323
- for (final String scriptId in sourceReports.keys) {
324
- final vm_service.SourceReport sourceReport = sourceReports[scriptId];
339
+ for (final vm_service.SourceReport sourceReport in sourceReports) {
325
340
for (final vm_service.SourceReportRange range in sourceReport.ranges) {
326
341
final vm_service.SourceReportCoverage coverage = range.coverage;
327
342
// Coverage reports may sometimes be null for a Script.
@@ -335,24 +350,14 @@ void _buildCoverageMap(
335
350
final Map <int , int > hitMap = hitMaps[uri];
336
351
final List <int > hits = coverage.hits;
337
352
final List <int > misses = coverage.misses;
338
- final List <dynamic > tokenPositions = scripts[scriptRef.id]? .tokenPosTable;
339
- // The token positions can be null if the script has no lines that may be
340
- // covered. It will also be null if reportLines is true.
341
- if (tokenPositions == null && ! reportLines) {
342
- continue ;
343
- }
344
353
if (hits != null ) {
345
- for (final int hit in hits) {
346
- final int line =
347
- reportLines ? hit : _lineAndColumn (hit, tokenPositions)[0 ];
354
+ for (final int line in hits) {
348
355
final int current = hitMap[line] ?? 0 ;
349
356
hitMap[line] = current + 1 ;
350
357
}
351
358
}
352
359
if (misses != null ) {
353
- for (final int miss in misses) {
354
- final int line =
355
- reportLines ? miss : _lineAndColumn (miss, tokenPositions)[0 ];
360
+ for (final int line in misses) {
356
361
hitMap[line] ?? = 0 ;
357
362
}
358
363
}
@@ -363,29 +368,6 @@ void _buildCoverageMap(
363
368
});
364
369
}
365
370
366
- // Binary search the token position table for the line and column which
367
- // corresponds to each token position.
368
- // The format of this table is described in https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#script
369
- List <int > _lineAndColumn (int position, List <dynamic > tokenPositions) {
370
- int min = 0 ;
371
- int max = tokenPositions.length;
372
- while (min < max) {
373
- final int mid = min + ((max - min) >> 1 );
374
- final List <int > row = (tokenPositions[mid] as List <dynamic >).cast <int >();
375
- if (row[1 ] > position) {
376
- max = mid;
377
- } else {
378
- for (int i = 1 ; i < row.length; i += 2 ) {
379
- if (row[i] == position) {
380
- return < int > [row.first, row[i + 1 ]];
381
- }
382
- }
383
- min = mid + 1 ;
384
- }
385
- }
386
- throw StateError ('Unreachable' );
387
- }
388
-
389
371
// Returns a JSON hit map backward-compatible with pre-1.16.0 SDKs.
390
372
Map <String , dynamic > _toScriptCoverageJson (String scriptUri, Map <int , int > hitMap) {
391
373
final Map <String , dynamic > json = < String , dynamic > {};
0 commit comments