blob: eb8599597d84333d9fea10f34e9767e3da514bf9 [file] [edit]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// 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.
// TODO(https://github.com/dart-lang/tools/issues/494): Fix and re-enable this.
@TestOn('!windows')
library;
import 'dart:convert';
import 'dart:io';
import 'package:coverage/coverage.dart';
import 'package:coverage/src/util.dart';
import 'package:test/test.dart';
// The scriptId for the main_test.js in the sample report.
const String mainScriptId = '31';
Future<String> sourceMapProvider(String scriptId) async {
if (scriptId != mainScriptId) {
return 'something invalid!';
}
return File('test/test_files/main_test.js.map').readAsString();
}
Future<String?> sourceProvider(String scriptId) async {
if (scriptId != mainScriptId) return null;
return File('test/test_files/main_test.js').readAsString();
}
Future<Uri> sourceUriProvider(String sourceUrl, String scriptId) async =>
Uri.parse(sourceUrl);
void main() {
test('reports correctly', () async {
final preciseCoverage =
json.decode(
await File(
'test/test_files/chrome_precise_report.txt',
).readAsString(),
)
as List;
final report = await parseChromeCoverage(
preciseCoverage.cast(),
sourceProvider,
sourceMapProvider,
sourceUriProvider,
);
final sourceReport = (report['coverage'] as List<Map<String, dynamic>>)
.firstWhere(
(Map<String, dynamic> report) =>
report['source'].toString().contains('main_test.dart'),
);
final expectedHits = {
7: 1,
11: 1,
13: 1,
14: 1,
17: 0,
19: 0,
20: 0,
22: 1,
23: 1,
24: 1,
25: 1,
28: 1,
30: 0,
32: 1,
34: 1,
35: 1,
36: 1,
};
final hitMap = sourceReport['hits'] as List<int>;
expect(hitMap.length, equals(expectedHits.keys.length * 2));
for (var i = 0; i < hitMap.length; i += 2) {
expect(expectedHits[hitMap[i]], equals(hitMap[i + 1]));
}
});
test('HitMap.parseFiles parses Chrome coverage reports', () async {
final preciseCoverage =
json.decode(
await File(
'test/test_files/chrome_precise_report.txt',
).readAsString(),
)
as List;
final report = await parseChromeCoverage(
preciseCoverage.cast(),
sourceProvider,
sourceMapProvider,
sourceUriProvider,
);
final tempDir = Directory.systemTemp.createTempSync('hitmap_chrome_test_');
final tempFile = File('${tempDir.path}/report.chrome.json');
tempFile.writeAsStringSync(jsonEncode(report));
try {
final hitmap = await HitMap.parseFiles([tempFile]);
expect(hitmap.keys, anyElement(contains('main_test.dart')));
final key = hitmap.keys.firstWhere((k) => k.contains('main_test.dart'));
final fileHitMap = hitmap[key]!;
expect(fileHitMap.lineHits[7], equals(1));
expect(fileHitMap.lineHits[11], equals(1));
expect(fileHitMap.lineHits[17], equals(0));
expect(fileHitMap.lineHits[36], equals(1));
} finally {
tempDir.deleteSync(recursive: true);
}
});
test('parseChromeCoverage rejects an entry with a missing scriptId', () {
expect(
parseChromeCoverage(
[
<String, dynamic>{
'url': 'http://localhost/main_test.js',
'functions': <Map<String, dynamic>>[],
},
],
sourceProvider,
sourceMapProvider,
sourceUriProvider,
),
throwsA(isA<FormatException>()),
);
});
test('parseChromeCoverage rejects a non-String scriptId', () {
expect(
parseChromeCoverage(
[
<String, dynamic>{
'scriptId': 31,
'url': 'http://localhost/main_test.js',
'functions': <Map<String, dynamic>>[],
},
],
sourceProvider,
sourceMapProvider,
sourceUriProvider,
),
throwsA(isA<FormatException>()),
);
});
test('HitMap.parseFiles skips non-map entries in a coverage list', () async {
final preciseCoverage =
json.decode(
await File(
'test/test_files/chrome_precise_report.txt',
).readAsString(),
)
as List;
final report = await parseChromeCoverage(
preciseCoverage.cast(),
sourceProvider,
sourceMapProvider,
sourceUriProvider,
);
final tempDir = Directory.systemTemp.createTempSync('hitmap_mixed_test_');
final tempFile = File('${tempDir.path}/report.json');
tempFile.writeAsStringSync(
jsonEncode(<String, dynamic>{
'type': 'CodeCoverage',
'coverage': <dynamic>['not-a-map', 42, ...report['coverage'] as List],
}),
);
try {
final hitmap = await HitMap.parseFiles([tempFile]);
expect(hitmap.keys, anyElement(contains('main_test.dart')));
} finally {
tempDir.deleteSync(recursive: true);
}
});
test('HitMap.parseFiles rejects a raw V8 list', () async {
final preciseCoverage =
json.decode(
await File(
'test/test_files/chrome_precise_report.txt',
).readAsString(),
)
as List;
final tempDir = Directory.systemTemp.createTempSync('hitmap_v8_reject_');
final tempFile = File('${tempDir.path}/raw_v8.json');
tempFile.writeAsStringSync(jsonEncode(preciseCoverage));
try {
expect(
() => HitMap.parseFiles([tempFile]),
throwsA(isA<FormatException>()),
);
} finally {
tempDir.deleteSync(recursive: true);
}
});
// Silently ignoring an unrecognized file would under-report coverage: a
// truncated or corrupt report would look like a run with no hits.
group('HitMap.parseFiles rejects unrecognized JSON', () {
for (final contents in [
'{"coverage": null}',
'{"coverage": {}}',
'{"coverage": "nope"}',
'{}',
'"a string"',
'42',
'null',
]) {
test(contents, () async {
final tempDir = Directory.systemTemp.createTempSync('hitmap_bad_');
final tempFile = File('${tempDir.path}/bad.json')
..writeAsStringSync(contents);
try {
expect(
() => HitMap.parseFiles([tempFile]),
throwsA(
isA<FormatException>().having(
(e) => e.message,
'message',
allOf(contains('Unrecognized coverage JSON'), contains('bad')),
),
),
);
} finally {
tempDir.deleteSync(recursive: true);
}
});
}
});
group('HitMap scopeOutput and ScopedOutput', () {
late Directory tempDir;
late String packagesPath;
setUp(() {
tempDir = Directory.systemTemp.createTempSync('hitmap_scope_test_');
final myPkgLib = Directory('${tempDir.path}/my_pkg/lib')
..createSync(recursive: true);
final otherPkgLib = Directory('${tempDir.path}/other_pkg/lib')
..createSync(recursive: true);
final configFile = File('${tempDir.path}/package_config.json')
..writeAsStringSync(
jsonEncode({
'configVersion': 2,
'packages': [
{
'name': 'my_pkg',
'rootUri': Uri.directory('${tempDir.path}/my_pkg').toString(),
'packageUri': myPkgLib.uri.toString(),
},
{
'name': 'other_pkg',
'rootUri': Uri.directory(
'${tempDir.path}/other_pkg',
).toString(),
'packageUri': otherPkgLib.uri.toString(),
},
],
}),
);
packagesPath = configFile.path;
});
tearDown(() {
tempDir.deleteSync(recursive: true);
});
List<Map<String, dynamic>> sampleJson() {
final myPkgRoot = Uri.directory('${tempDir.path}/my_pkg');
final otherPkgRoot = Uri.directory('${tempDir.path}/other_pkg');
return [
{
'source': 'package:my_pkg/src/foo.dart',
'hits': [1, 2, 2, 0],
},
{
'source': 'package:other_pkg/src/bar.dart',
'hits': [10, 1],
},
{
'source': myPkgRoot.resolve('lib/src/in_lib.dart').toString(),
'hits': [5, 3],
},
{
'source': myPkgRoot.resolve('test/foo_test.dart').toString(),
'hits': [20, 1],
},
{
'source': otherPkgRoot.resolve('test/bar_test.dart').toString(),
'hits': [30, 1],
},
];
}
test('normalizes lib file: URIs to package: URIs and filters to matching '
'scopeOutput', () async {
final resolver = await Resolver.create(packagesPath: packagesPath);
final result = HitMap.parseJsonSync(
sampleJson(),
checkIgnoredLines: false,
ignoredLinesInFilesCache: {},
resolver: resolver,
scopeOutput: {'my_pkg'},
);
expect(
result.keys,
unorderedEquals([
'package:my_pkg/src/foo.dart',
'package:my_pkg/src/in_lib.dart',
]),
);
});
test('includes all entries when scopeOutput is empty', () async {
final resolver = await Resolver.create(packagesPath: packagesPath);
final result = HitMap.parseJsonSync(
sampleJson(),
checkIgnoredLines: false,
ignoredLinesInFilesCache: {},
resolver: resolver,
);
expect(result, hasLength(5));
});
test('ScopedOutput.includesScript handles null and package: URIs', () {
expect({'my_pkg'}.includesScript(null), isFalse);
expect(<String>{}.includesScript('file:///anything.dart'), isTrue);
expect({'my_pkg'}.includesScript('package:my_pkg/foo.dart'), isTrue);
expect({'my_pkg'}.includesScript('package:other_pkg/foo.dart'), isFalse);
expect(
{'my_pkg'}.includesScript('file:///workspace/my_pkg/test/a.dart'),
isFalse,
);
});
});
}