@@ -33,6 +33,10 @@ const _retryInterval = Duration(milliseconds: 200);
3333/// If [functionCoverage] is true, function coverage information will be
3434/// collected.
3535///
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+ ///
3640/// If [scopedOutput] is non-empty, coverage will be restricted so that only
3741/// scripts that start with any of the provided paths are considered.
3842///
@@ -42,7 +46,8 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
4246 bool waitPaused, bool includeDart, Set <String >? scopedOutput,
4347 {Set <String >? isolateIds,
4448 Duration ? timeout,
45- bool functionCoverage = false }) async {
49+ bool functionCoverage = false ,
50+ bool branchCoverage = false }) async {
4651 scopedOutput ?? = < String > {};
4752
4853 // Create websocket URI. Handle any trailing slashes.
@@ -76,8 +81,8 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
7681 await _waitIsolatesPaused (service, timeout: timeout);
7782 }
7883
79- return await _getAllCoverage (
80- service, includeDart, functionCoverage , scopedOutput, isolateIds);
84+ return await _getAllCoverage (service, includeDart, functionCoverage,
85+ branchCoverage , scopedOutput, isolateIds);
8186 } finally {
8287 if (resume) {
8388 await _resumeIsolates (service);
@@ -88,19 +93,34 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool resume,
8893 }
8994}
9095
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+
91102Future <Map <String , dynamic >> _getAllCoverage (
92103 VmService service,
93104 bool includeDart,
94105 bool functionCoverage,
106+ bool branchCoverage,
95107 Set <String >? scopedOutput,
96108 Set <String >? isolateIds) async {
97109 scopedOutput ?? = < String > {};
98110 final vm = await service.getVM ();
99111 final allCoverage = < Map <String , dynamic >> [];
100112 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+ ];
104124
105125 // Program counters are shared between isolates in the same group. So we need
106126 // to make sure we're only gathering coverage data for one isolate in each
@@ -130,7 +150,7 @@ Future<Map<String, dynamic>> _getAllCoverage(
130150 // Skip scripts which should not be included in the report.
131151 if (! scopedOutput.contains (scope)) continue ;
132152 final scriptReport = await service.getSourceReport (
133- isolateRef.id! , < String > [ SourceReportKind .kCoverage] ,
153+ isolateRef.id! , sourceReportKinds ,
134154 forceCompile: true ,
135155 scriptId: script.id,
136156 reportLines: reportLines ? true : null );
@@ -141,7 +161,7 @@ Future<Map<String, dynamic>> _getAllCoverage(
141161 } else {
142162 final isolateReport = await service.getSourceReport (
143163 isolateRef.id! ,
144- < String > [ SourceReportKind .kCoverage] ,
164+ sourceReportKinds ,
145165 forceCompile: true ,
146166 reportLines: reportLines ? true : null ,
147167 );
@@ -231,7 +251,8 @@ Future<void> _processFunction(VmService service, IsolateRef isolateRef,
231251 final line = _getLineFromTokenPos (script, tokenPos);
232252
233253 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 !}' );
235256 return ;
236257 }
237258 hits.funcNames! [line] = funcName;
@@ -306,28 +327,41 @@ Future<List<Map<String, dynamic>>> _getCoverageJson(
306327
307328 if (coverage == null ) continue ;
308329
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);
314339 }
340+ }
341+
342+ forEachLine (coverage.hits! , (line) {
315343 _incrementCountForKey (hits.lineHits, line);
316344 if (hits.funcNames != null && hits.funcNames! .containsKey (line)) {
317345 _incrementCountForKey (hits.funcHits! , line);
318346 }
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) {
326349 hits.lineHits.putIfAbsent (line, () => 0 );
327- }
350+ });
328351 hits.funcNames? .forEach ((line, funcName) {
329352 hits.funcHits? .putIfAbsent (line, () => 0 );
330353 });
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+ }
331365 }
332366
333367 // Output JSON
0 commit comments