Revert the 0.13.11 revert and properly model it as a breaking change (#305)
* Revert "Revert breaking change in 13.10 (#304)"
This reverts commit 49219fc713fa1ad6f64278151d5c6a673b8c4525.
* update version and changelog
* pin test
* remove overrides
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f932ddf..5351d7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,13 @@
-## 0.13.11 - 2020-03-09
+## 0.14.0 - 2020-06-04
+
+* 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.11 - 2020-06-04
* Revert breaking change in 13.10
diff --git a/README.md b/README.md
index 0791043..b321342 100644
--- a/README.md
+++ b/README.md
@@ -55,3 +55,9 @@
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.
diff --git a/bin/format_coverage.dart b/bin/format_coverage.dart
index 3e2b659..978b061 100644
--- a/bin/format_coverage.dart
+++ b/bin/format_coverage.dart
@@ -24,6 +24,7 @@
bool prettyPrint;
bool lcov;
bool expectMarkers;
+ bool checkIgnore;
bool verbose;
}
@@ -39,10 +40,15 @@
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) {
@@ -125,6 +131,13 @@
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);
@@ -214,6 +227,7 @@
fail('Invalid worker count: $e');
}
+ env.checkIgnore = args['check-ignore'] as bool;
env.verbose = args['verbose'] as bool;
return env;
}
diff --git a/lib/src/hitmap.dart b/lib/src/hitmap.dart
index 5d6bca0..a562abc 100644
--- a/lib/src/hitmap.dart
+++ b/lib/src/hitmap.dart
@@ -6,11 +6,21 @@
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>>{};
@@ -26,6 +36,24 @@
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:
@@ -36,6 +64,8 @@
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.
@@ -43,6 +73,8 @@
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 {
@@ -53,6 +85,29 @@
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) {
@@ -73,7 +128,10 @@
/// 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();
@@ -81,7 +139,10 @@
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,
);
}
diff --git a/lib/src/util.dart b/lib/src/util.dart
index f0a3d7e..62cf870 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -109,3 +109,69 @@
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;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index b3ed99c..3f6a77f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: coverage
-version: 0.13.11
+version: 0.14.0
description: Coverage data manipulation and formatting
homepage: https://github.com/dart-lang/coverage
@@ -23,3 +23,4 @@
executables:
collect_coverage:
format_coverage:
+
diff --git a/test/collect_coverage_test.dart b/test/collect_coverage_test.dart
index 0ce58da..bd2b234 100644
--- a/test/collect_coverage_test.dart
+++ b/test/collect_coverage_test.dart
@@ -53,7 +53,10 @@
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];
@@ -70,6 +73,8 @@
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
diff --git a/test/run_and_collect_test.dart b/test/run_and_collect_test.dart
index 54596d6..aa0fe2a 100644
--- a/test/run_and_collect_test.dart
+++ b/test/run_and_collect_test.dart
@@ -40,7 +40,10 @@
expect(sampleCoverageData['hits'], isNotEmpty);
}
- final hitMap = createHitmap(coverage);
+ final hitMap = await createHitmap(
+ coverage,
+ checkIgnoredLines: true,
+ );
expect(hitMap, contains(_sampleAppFileUri));
final actualHits = hitMap[_isolateLibFileUri];
@@ -57,6 +60,8 @@
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.
diff --git a/test/test_files/test_app_isolate.dart b/test/test_files/test_app_isolate.dart
index 0f72d27..eac9c2d 100644
--- a/test/test_files/test_app_isolate.dart
+++ b/test/test_files/test_app_isolate.dart
@@ -34,4 +34,24 @@
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
}
diff --git a/test/util_test.dart b/test/util_test.dart
index b98ffe2..b8bf823 100644
--- a/test/util_test.dart
+++ b/test/util_test.dart
@@ -129,4 +129,106 @@
Uri.parse('http://foo.bar:9999/cG90YXRv/'));
});
});
+
+ group('getIgnoredLines', () {
+ const invalidSources = [
+ '''final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ ''',
+ '''final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ final str = '';
+ final str = ''; // coverage:ignore-start
+ ''',
+ '''final str = ''; // coverage:ignore-end
+ ''',
+ '''final str = ''; // coverage:ignore-start
+ ''',
+ ];
+
+ test('returns empty when the annotations are not balanced', () {
+ for (final content in invalidSources) {
+ expect(getIgnoredLines(content.split('\n')), isEmpty);
+ }
+ });
+
+ test(
+ 'returns [[0,lines.length]] when the annotations are not '
+ 'balanced but the whole file is ignored', () {
+ for (final content in invalidSources) {
+ final lines = content.split('\n');
+ lines.add(' // coverage:ignore-file');
+ expect(getIgnoredLines(lines), [
+ [0, lines.length]
+ ]);
+ }
+ });
+
+ test('Returns [[0,lines.length]] when the whole file is ignored', () {
+ final lines = '''final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-end
+ final str = ''; // coverage:ignore-file
+ '''
+ .split('\n');
+
+ expect(getIgnoredLines(lines), [
+ [0, lines.length]
+ ]);
+ });
+
+ test('return the correct range of lines ignored', () {
+ final lines = '''
+ final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-end
+ final str = ''; // coverage:ignore-start
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-end
+ '''
+ .split('\n');
+
+ expect(getIgnoredLines(lines), [
+ [1, 3],
+ [4, 6],
+ ]);
+ });
+
+ test('return the correct list of lines ignored', () {
+ final lines = '''
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-line
+ final str = ''; // coverage:ignore-line
+ '''
+ .split('\n');
+
+ expect(getIgnoredLines(lines), [
+ [1, 1],
+ [2, 2],
+ [3, 3],
+ ]);
+ });
+ });
}
diff --git a/tool/travis.sh b/tool/travis.sh
index 1037a88..b0358e3 100755
--- a/tool/travis.sh
+++ b/tool/travis.sh
@@ -31,5 +31,6 @@
--in=var/coverage.json \
--out=var/lcov.info \
--packages=.packages \
- --report-on=lib
+ --report-on=lib \
+ --check-ignore
fi