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

Add the ability to ignore lines from coverage depending on the comment #302

Merged
merged 5 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.13.10 - 2020-06-03
* Add flag `--check-ignore` that is used to ignore lines from coverage
depending on the comments.

Use // coverage:ignore-line to ignore one line.
Use // coverage:ignore-start and // coverage:ignore-end to ignore range of lines inclusive.
Use // coverage:ignore-file to ignore the whole file.

## 0.13.9 - 2020-03-09

* Don't crash on empty JSON input files.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ format_coverage --packages=app_package/.packages -i coverage.json

where `app_package` is the path to the package whose coverage is being
collected. If `--sdk-root` is set, Dart SDK coverage will also be output.

#### Ignore lines from coverage

- `// coverage:ignore-line` to ignore one line.
- `// coverage:ignore-start` and `// coverage:ignore-end` to ignore range of lines inclusive.
- `// coverage:ignore-file` to ignore the whole file.
16 changes: 15 additions & 1 deletion bin/format_coverage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Environment {
bool prettyPrint;
bool lcov;
bool expectMarkers;
bool checkIgnore;
bool verbose;
}

Expand All @@ -39,10 +40,15 @@ Future<Null> main(List<String> arguments) async {
print(' package-root: ${env.pkgRoot}');
print(' package-spec: ${env.packagesPath}');
print(' report-on: ${env.reportOn}');
print(' check-ignore: ${env.checkIgnore}');
}

final clock = Stopwatch()..start();
final hitmap = await parseCoverage(files, env.workers);
final hitmap = await parseCoverage(
files,
env.workers,
checkIgnoredLines: env.checkIgnore,
);

// All workers are done. Process the data.
if (env.verbose) {
Expand Down Expand Up @@ -125,6 +131,13 @@ Environment parseArgs(List<String> arguments) {
help: 'convert coverage data to lcov format');
parser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'verbose output');
parser.addFlag(
'check-ignore',
abbr: 'c',
negatable: false,
help: 'check for coverage ignore comments.'
' Not supported in web coverage.',
);
parser.addFlag('help', abbr: 'h', negatable: false, help: 'show this help');

final args = parser.parse(arguments);
Expand Down Expand Up @@ -214,6 +227,7 @@ Environment parseArgs(List<String> arguments) {
fail('Invalid worker count: $e');
}

env.checkIgnore = args['check-ignore'] as bool;
env.verbose = args['verbose'] as bool;
return env;
}
Expand Down
67 changes: 64 additions & 3 deletions lib/src/hitmap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ import 'dart:async';
import 'dart:convert' show json;
import 'dart:io';

import 'package:coverage/src/resolver.dart';
import 'package:coverage/src/util.dart';

/// Creates a single hitmap from a raw json object. Throws away all entries that
/// are not resolvable.
///
/// `jsonResult` is expected to be a List<Map<String, dynamic>>.
Map<String, Map<int, int>> createHitmap(List<Map<String, dynamic>> jsonResult) {
Future<Map<String, Map<int, int>>> createHitmap(
List<Map<String, dynamic>> jsonResult, {
bool checkIgnoredLines = false,
String packagesPath,
}) async {
final resolver = Resolver(packagesPath: packagesPath);
final loader = Loader();

// Map of source file to map of line to hit count for that line.
final globalHitMap = <String, Map<int, int>>{};

Expand All @@ -26,6 +36,24 @@ Map<String, Map<int, int>> createHitmap(List<Map<String, dynamic>> jsonResult) {
continue;
}

var ignoredLinesList = <List<int>>[];

if (checkIgnoredLines) {
final lines = await loader.load(resolver.resolve(source));
ignoredLinesList = getIgnoredLines(lines);

// Ignore the whole file.
if (ignoredLinesList.length == 1 &&
ignoredLinesList[0][0] == 0 &&
ignoredLinesList[0][1] == lines.length) {
continue;
}
}

// Move to the first ignore range.
final ignoredLines = ignoredLinesList.iterator;
ignoredLines.moveNext();

final sourceHitMap = globalHitMap.putIfAbsent(source, () => <int, int>{});
final hits = e['hits'] as List;
// hits is a flat array of the following format:
Expand All @@ -36,13 +64,17 @@ Map<String, Map<int, int>> createHitmap(List<Map<String, dynamic>> jsonResult) {
final k = hits[i];
if (k is int) {
// Single line.
if (_shouldIgnoreLine(ignoredLines, k)) continue;

addToMap(sourceHitMap, k, hits[i + 1] as int);
} else if (k is String) {
// Linerange. We expand line ranges to actual lines at this point.
final splitPos = k.indexOf('-');
final start = int.parse(k.substring(0, splitPos));
final end = int.parse(k.substring(splitPos + 1));
for (var j = start; j <= end; j++) {
if (_shouldIgnoreLine(ignoredLines, j)) continue;

addToMap(sourceHitMap, j, hits[i + 1] as int);
}
} else {
Expand All @@ -53,6 +85,29 @@ Map<String, Map<int, int>> createHitmap(List<Map<String, dynamic>> jsonResult) {
return globalHitMap;
}

bool _shouldIgnoreLine(Iterator<List<int>> ignoredRanges, int line) {
if (ignoredRanges.current == null || ignoredRanges.current.isEmpty) {
return false;
}

if (line < ignoredRanges.current[0]) return false;

while (ignoredRanges.current != null &&
ignoredRanges.current.isNotEmpty &&
ignoredRanges.current[1] < line) {
ignoredRanges.moveNext();
}

if (ignoredRanges.current != null &&
ignoredRanges.current.isNotEmpty &&
ignoredRanges.current[0] <= line &&
line <= ignoredRanges.current[1]) {
return true;
}

return false;
}

/// Merges [newMap] into [result].
void mergeHitmaps(
Map<String, Map<int, int>> newMap, Map<String, Map<int, int>> result) {
Expand All @@ -73,15 +128,21 @@ void mergeHitmaps(

/// Generates a merged hitmap from a set of coverage JSON files.
Future<Map<String, Map<int, int>>> parseCoverage(
Iterable<File> files, int _) async {
Iterable<File> files,
int _, {
bool checkIgnoredLines = false,
}) async {
final globalHitmap = <String, Map<int, int>>{};
for (var file in files) {
final contents = file.readAsStringSync();
final jsonMap = json.decode(contents) as Map<String, dynamic>;
if (jsonMap.containsKey('coverage')) {
final jsonResult = jsonMap['coverage'] as List;
mergeHitmaps(
createHitmap(jsonResult.cast<Map<String, dynamic>>()),
await createHitmap(
jsonResult.cast<Map<String, dynamic>>(),
checkIgnoredLines: checkIgnoredLines,
),
globalHitmap,
);
}
Expand Down
66 changes: 66 additions & 0 deletions lib/src/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,69 @@ int _finish(int hash) {
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}

const muliLineIgnoreStart = '// coverage:ignore-start';
const muliLineIgnoreEnd = '// coverage:ignore-end';
const singleLineIgnore = '// coverage:ignore-line';
const ignoreFile = '// coverage:ignore-file';

/// Return list containing inclusive range of lines to be ignored by coverage.
/// If there is a error in balancing the statements it will ignore nothing,
/// unless `coverage:ignore-file` is found.
/// Return [0, lines.length] if the whole file is ignored.
///
/// ```
/// 1. final str = ''; // coverage:ignore-line
/// 2. final str = '';
/// 3. final str = ''; // coverage:ignore-start
/// 4. final str = '';
/// 5. final str = ''; // coverage:ignore-end
/// ```
///
/// Returns
/// ```
/// [
/// [1,1],
/// [3,5],
/// ]
/// ```
///
List<List<int>> getIgnoredLines(List<String> lines) {
final ignoredLines = <List<int>>[];
if (lines == null) return ignoredLines;

final allLines = [
[0, lines.length]
];

var isError = false;
var i = 0;
while (i < lines.length) {
if (lines[i].contains(ignoreFile)) return allLines;

if (lines[i].contains(muliLineIgnoreEnd)) isError = true;

if (lines[i].contains(singleLineIgnore)) ignoredLines.add([i + 1, i + 1]);

if (lines[i].contains(muliLineIgnoreStart)) {
final start = i;
++i;
while (i < lines.length) {
if (lines[i].contains(ignoreFile)) return allLines;
if (lines[i].contains(muliLineIgnoreStart)) {
isError = true;
break;
}

if (lines[i].contains(muliLineIgnoreEnd)) {
ignoredLines.add([start + 1, i + 1]);
break;
}
++i;
}
}
++i;
}

return isError ? [] : ignoredLines;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: coverage
version: 0.13.9
version: 0.13.10
description: Coverage data manipulation and formatting
homepage: https://github.com/dart-lang/coverage

Expand Down
7 changes: 6 additions & 1 deletion test/collect_coverage_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ void main() {
final resultString = await _getCoverageResult();
final jsonResult = json.decode(resultString) as Map<String, dynamic>;
final coverage = jsonResult['coverage'] as List;
final hitMap = createHitmap(coverage.cast<Map<String, dynamic>>());
final hitMap = await createHitmap(
coverage.cast<Map<String, dynamic>>(),
checkIgnoredLines: true,
);
expect(hitMap, contains(_sampleAppFileUri));

final isolateFile = hitMap[_isolateLibFileUri];
Expand All @@ -70,6 +73,8 @@ void main() {
33: 1,
34: 3,
35: 1,
46: 1,
47: 1,
};
if (Platform.version.startsWith('1.')) {
// Dart VMs prior to 2.0.0-dev.5.0 contain a bug that emits coverage on the
Expand Down
7 changes: 6 additions & 1 deletion test/run_and_collect_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ void main() {
expect(sampleCoverageData['hits'], isNotEmpty);
}

final hitMap = createHitmap(coverage);
final hitMap = await createHitmap(
coverage,
checkIgnoredLines: true,
);
expect(hitMap, contains(_sampleAppFileUri));

final actualHits = hitMap[_isolateLibFileUri];
Expand All @@ -57,6 +60,8 @@ void main() {
33: 1,
34: 3,
35: 1,
46: 1,
47: 1,
};
// Dart VMs prior to 2.0.0-dev.5.0 contain a bug that emits coverage on the
// closing brace of async function blocks.
Expand Down
20 changes: 20 additions & 0 deletions test/test_files/test_app_isolate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,24 @@ void isolateTask(dynamic threeThings) {
final sum = (threeThings[1] + threeThings[2]) as int;
port.send(sum);
});

print('678'); // coverage:ignore-line

// coverage:ignore-start
print('1');
print('2');
print('3');
// coverage:ignore-end

print('4');
print('5');

print('6'); // coverage:ignore-start
print('7');
print('8');
// coverage:ignore-end
print('9'); // coverage:ignore-start
print('10');
print('11'); // coverage:ignore-line
// coverage:ignore-end
}
Loading