Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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 1 commit
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
17 changes: 16 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,16 @@ 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);
print(env.checkIgnore);
pasindud marked this conversation as resolved.
Show resolved Hide resolved
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 +132,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 +228,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;
}
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
8 changes: 7 additions & 1 deletion test/run_and_collect_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:coverage/coverage.dart';
Expand Down Expand Up @@ -40,7 +41,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 +61,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-end
}
Loading