@@ -33,6 +33,10 @@ const _retryInterval = Duration(milliseconds: 200);
33
33
/// If [functionCoverage] is true, function coverage information will be
34
34
/// collected.
35
35
///
36
+ /// If [branchCoverage] is true, branch coverage information will be collected.
37
+ /// This will only work correctly if the target VM was run with the
38
+ /// --branch-coverage flag.
39
+ ///
36
40
/// If [scopedOutput] is non-empty, coverage will be restricted so that only
37
41
/// scripts that start with any of the provided paths are considered.
38
42
///
@@ -42,7 +46,8 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
42
46
bool waitPaused, bool includeDart, Set <String >? scopedOutput,
43
47
{Set <String >? isolateIds,
44
48
Duration ? timeout,
45
- bool functionCoverage = false }) async {
49
+ bool functionCoverage = false ,
50
+ bool branchCoverage = false }) async {
46
51
scopedOutput ?? = < String > {};
47
52
48
53
// Create websocket URI. Handle any trailing slashes.
@@ -76,8 +81,8 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
76
81
await _waitIsolatesPaused (service, timeout: timeout);
77
82
}
78
83
79
- return await _getAllCoverage (
80
- service, includeDart, functionCoverage , scopedOutput, isolateIds);
84
+ return await _getAllCoverage (service, includeDart, functionCoverage,
85
+ branchCoverage , scopedOutput, isolateIds);
81
86
} finally {
82
87
if (resume) {
83
88
await _resumeIsolates (service);
@@ -88,19 +93,34 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
88
93
}
89
94
}
90
95
96
+ bool _versionCheck (Version version, int minMajor, int minMinor) {
97
+ final major = version.major ?? 0 ;
98
+ final minor = version.minor ?? 0 ;
99
+ return major > minMajor || (major == minMajor && minor >= minMinor);
100
+ }
101
+
91
102
Future <Map <String , dynamic >> _getAllCoverage (
92
103
VmService service,
93
104
bool includeDart,
94
105
bool functionCoverage,
106
+ bool branchCoverage,
95
107
Set <String >? scopedOutput,
96
108
Set <String >? isolateIds) async {
97
109
scopedOutput ?? = < String > {};
98
110
final vm = await service.getVM ();
99
111
final allCoverage = < Map <String , dynamic >> [];
100
112
final version = await service.getVersion ();
101
- final reportLines =
102
- (version.major == 3 && version.minor != null && version.minor! >= 51 ) ||
103
- (version.major != null && version.major! > 3 );
113
+ final reportLines = _versionCheck (version, 3 , 51 );
114
+ final branchCoverageSupported = _versionCheck (version, 3 , 56 );
115
+ if (branchCoverage && ! branchCoverageSupported) {
116
+ branchCoverage = false ;
117
+ stderr.write ('Branch coverage was requested, but is not supported'
118
+ ' by the VM version. Try updating to a newer version of Dart' );
119
+ }
120
+ final sourceReportKinds = [
121
+ SourceReportKind .kCoverage,
122
+ if (branchCoverage) SourceReportKind .kBranchCoverage,
123
+ ];
104
124
105
125
// Program counters are shared between isolates in the same group. So we need
106
126
// to make sure we're only gathering coverage data for one isolate in each
@@ -130,7 +150,7 @@ Future<Map<String, dynamic>> _getAllCoverage(
130
150
// Skip scripts which should not be included in the report.
131
151
if (! scopedOutput.contains (scope)) continue ;
132
152
final scriptReport = await service.getSourceReport (
133
- isolateRef.id! , < String > [ SourceReportKind .kCoverage] ,
153
+ isolateRef.id! , sourceReportKinds ,
134
154
forceCompile: true ,
135
155
scriptId: script.id,
136
156
reportLines: reportLines ? true : null );
@@ -141,7 +161,7 @@ Future<Map<String, dynamic>> _getAllCoverage(
141
161
} else {
142
162
final isolateReport = await service.getSourceReport (
143
163
isolateRef.id! ,
144
- < String > [ SourceReportKind .kCoverage] ,
164
+ sourceReportKinds ,
145
165
forceCompile: true ,
146
166
reportLines: reportLines ? true : null ,
147
167
);
@@ -231,7 +251,8 @@ Future<void> _processFunction(VmService service, IsolateRef isolateRef,
231
251
final line = _getLineFromTokenPos (script, tokenPos);
232
252
233
253
if (line == null ) {
234
- print ('tokenPos $tokenPos has no line mapping for script ${script .uri !}' );
254
+ stderr.write (
255
+ 'tokenPos $tokenPos has no line mapping for script ${script .uri !}' );
235
256
return ;
236
257
}
237
258
hits.funcNames! [line] = funcName;
@@ -306,28 +327,41 @@ Future<List<Map<String, dynamic>>> _getCoverageJson(
306
327
307
328
if (coverage == null ) continue ;
308
329
309
- for (final pos in coverage.hits! ) {
310
- final line = reportLines ? pos : _getLineFromTokenPos (script! , pos);
311
- if (line == null ) {
312
- print ('tokenPos $pos has no line mapping for script $scriptUri ' );
313
- continue ;
330
+ void forEachLine (List <int > tokenPositions, void Function (int line) body) {
331
+ for (final pos in tokenPositions) {
332
+ final line = reportLines ? pos : _getLineFromTokenPos (script! , pos);
333
+ if (line == null ) {
334
+ stderr
335
+ .write ('tokenPos $pos has no line mapping for script $scriptUri ' );
336
+ continue ;
337
+ }
338
+ body (line);
314
339
}
340
+ }
341
+
342
+ forEachLine (coverage.hits! , (line) {
315
343
_incrementCountForKey (hits.lineHits, line);
316
344
if (hits.funcNames != null && hits.funcNames! .containsKey (line)) {
317
345
_incrementCountForKey (hits.funcHits! , line);
318
346
}
319
- }
320
- for (final pos in coverage.misses! ) {
321
- final line = reportLines ? pos : _getLineFromTokenPos (script! , pos);
322
- if (line == null ) {
323
- print ('tokenPos $pos has no line mapping for script $scriptUri ' );
324
- continue ;
325
- }
347
+ });
348
+ forEachLine (coverage.misses! , (line) {
326
349
hits.lineHits.putIfAbsent (line, () => 0 );
327
- }
350
+ });
328
351
hits.funcNames? .forEach ((line, funcName) {
329
352
hits.funcHits? .putIfAbsent (line, () => 0 );
330
353
});
354
+
355
+ final branchCoverage = range.branchCoverage;
356
+ if (branchCoverage != null ) {
357
+ hits.branchHits ?? = < int , int > {};
358
+ forEachLine (branchCoverage.hits! , (line) {
359
+ _incrementCountForKey (hits.branchHits! , line);
360
+ });
361
+ forEachLine (branchCoverage.misses! , (line) {
362
+ hits.branchHits! .putIfAbsent (line, () => 0 );
363
+ });
364
+ }
331
365
}
332
366
333
367
// Output JSON
0 commit comments