blob: 222de541c99d8082a60db9794ab06f415080d8f7 [file] [log] [blame]
// Copyright (c) 2015, 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.
@Retry(3)
import 'dart:async';
import 'dart:convert' show json, LineSplitter, utf8;
import 'dart:io';
import 'package:coverage/coverage.dart';
import 'package:coverage/src/util.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'test_util.dart';
final _isolateLibPath = p.join('test', 'test_files', 'test_app_isolate.dart');
final _collectAppPath = p.join('bin', 'collect_coverage.dart');
final _sampleAppFileUri = p.toUri(p.absolute(testAppPath)).toString();
final _isolateLibFileUri = p.toUri(p.absolute(_isolateLibPath)).toString();
void main() {
test('collect_coverage', () async {
final resultString = await _getCoverageResult();
// analyze the output json
final jsonResult = json.decode(resultString) as Map<String, dynamic>;
expect(jsonResult.keys, unorderedEquals(<String>['type', 'coverage']));
expect(jsonResult, containsPair('type', 'CodeCoverage'));
final coverage = jsonResult['coverage'] as List;
expect(coverage, isNotEmpty);
final sources = coverage.fold<Map<String, dynamic>>(<String, dynamic>{},
(Map<String, dynamic> map, dynamic value) {
final sourceUri = value['source'] as String;
map.putIfAbsent(sourceUri, () => <Map>[]).add(value);
return map;
});
for (var sampleCoverageData in sources[_sampleAppFileUri]) {
expect(sampleCoverageData['hits'], isNotNull);
}
for (var sampleCoverageData in sources[_isolateLibFileUri]) {
expect(sampleCoverageData['hits'], isNotEmpty);
}
});
test('createHitmap returns a sorted hitmap', () async {
final coverage = [
{
'source': 'foo',
'script': '{type: @Script, fixedId: true, '
'id: bar.dart, uri: bar.dart, _kind: library}',
'hits': [
45,
1,
46,
1,
49,
0,
50,
0,
15,
1,
16,
2,
17,
2,
]
}
];
// ignore: deprecated_member_use_from_same_package
final hitMap = await createHitmap(
coverage.cast<Map<String, dynamic>>(),
);
final expectedHits = {15: 1, 16: 2, 17: 2, 45: 1, 46: 1, 49: 0, 50: 0};
expect(hitMap['foo'], expectedHits);
});
test('HitMap.parseJson returns a sorted hitmap', () async {
final coverage = [
{
'source': 'foo',
'script': '{type: @Script, fixedId: true, '
'id: bar.dart, uri: bar.dart, _kind: library}',
'hits': [
45,
1,
46,
1,
49,
0,
50,
0,
15,
1,
16,
2,
17,
2,
]
}
];
final hitMap = await HitMap.parseJson(
coverage.cast<Map<String, dynamic>>(),
);
final expectedHits = {15: 1, 16: 2, 17: 2, 45: 1, 46: 1, 49: 0, 50: 0};
expect(hitMap['foo']?.lineHits, expectedHits);
});
test('HitMap.parseJson', () async {
final resultString = await _collectCoverage(true, true);
final jsonResult = json.decode(resultString) as Map<String, dynamic>;
final coverage = jsonResult['coverage'] as List;
final hitMap = await HitMap.parseJson(
coverage.cast<Map<String, dynamic>>(),
);
expect(hitMap, contains(_sampleAppFileUri));
final isolateFile = hitMap[_isolateLibFileUri];
final expectedHits = {
11: 1,
12: 1,
13: 1,
15: 0,
19: 1,
23: 1,
24: 2,
28: 1,
29: 1,
30: 1,
32: 0,
38: 1,
39: 1,
41: 1,
42: 3,
43: 1,
44: 3,
45: 1,
48: 1,
49: 1,
51: 1,
54: 1,
55: 1,
56: 1,
59: 1,
60: 1,
62: 1,
63: 1,
64: 1,
66: 1,
67: 1,
68: 1
};
if (Platform.version.startsWith('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.
// See: https://github.com/dart-lang/coverage/issues/196
expectedHits[23] = 0;
} else {
// Dart VMs version 2.0.0-dev.6.0 mark the opening brace of a function as
// coverable.
expectedHits[11] = 1;
expectedHits[28] = 1;
expectedHits[38] = 1;
expectedHits[42] = 3;
}
expect(isolateFile?.lineHits, expectedHits);
expect(isolateFile?.funcHits, {11: 1, 19: 1, 21: 0, 23: 1, 28: 1, 38: 1});
expect(isolateFile?.funcNames, {
11: 'fooSync',
19: 'BarClass.BarClass',
21: 'BarClass.x=',
23: 'BarClass.baz',
28: 'fooAsync',
38: 'isolateTask'
});
expect(isolateFile?.branchHits,
{11: 1, 12: 1, 15: 0, 19: 1, 23: 1, 28: 1, 32: 0, 38: 1, 42: 1});
}, skip: !platformVersionCheck(2, 17));
test('HitMap.parseJson, old VM without branch coverage', () async {
final resultString = await _collectCoverage(true, true);
final jsonResult = json.decode(resultString) as Map<String, dynamic>;
final coverage = jsonResult['coverage'] as List;
final hitMap = await HitMap.parseJson(
coverage.cast<Map<String, dynamic>>(),
);
expect(hitMap, contains(_sampleAppFileUri));
final isolateFile = hitMap[_isolateLibFileUri];
final expectedHits = {
11: 1,
12: 1,
13: 1,
15: 0,
19: 1,
23: 1,
24: 2,
28: 1,
29: 1,
30: 1,
32: 0,
38: 1,
39: 1,
41: 1,
42: 3,
43: 1,
44: 3,
45: 1,
48: 1,
49: 1,
51: 1,
54: 1,
55: 1,
56: 1,
59: 1,
60: 1,
62: 1,
63: 1,
64: 1,
66: 1,
67: 1,
68: 1
};
if (Platform.version.startsWith('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.
// See: https://github.com/dart-lang/coverage/issues/196
expectedHits[23] = 0;
} else {
// Dart VMs version 2.0.0-dev.6.0 mark the opening brace of a function as
// coverable.
expectedHits[11] = 1;
expectedHits[28] = 1;
expectedHits[38] = 1;
expectedHits[42] = 3;
}
expect(isolateFile?.lineHits, expectedHits);
expect(isolateFile?.funcHits, {11: 1, 19: 1, 21: 0, 23: 1, 28: 1, 38: 1});
expect(isolateFile?.funcNames, {
11: 'fooSync',
19: 'BarClass.BarClass',
21: 'BarClass.x=',
23: 'BarClass.baz',
28: 'fooAsync',
38: 'isolateTask'
});
}, skip: platformVersionCheck(2, 17));
test('parseCoverage', () async {
final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
try {
final outputFile = File(p.join(tempDir.path, 'coverage.json'));
final coverageResults = await _getCoverageResult();
await outputFile.writeAsString(coverageResults, flush: true);
// ignore: deprecated_member_use_from_same_package
final parsedResult = await parseCoverage([outputFile], 1);
expect(parsedResult, contains(_sampleAppFileUri));
expect(parsedResult, contains(_isolateLibFileUri));
} finally {
await tempDir.delete(recursive: true);
}
});
test('HitMap.parseFiles', () async {
final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
try {
final outputFile = File(p.join(tempDir.path, 'coverage.json'));
final coverageResults = await _getCoverageResult();
await outputFile.writeAsString(coverageResults, flush: true);
final parsedResult = await HitMap.parseFiles([outputFile]);
expect(parsedResult, contains(_sampleAppFileUri));
expect(parsedResult, contains(_isolateLibFileUri));
} finally {
await tempDir.delete(recursive: true);
}
});
test('HitMap.parseFiles with packagesPath and checkIgnoredLines', () async {
final tempDir = await Directory.systemTemp.createTemp('coverage.test.');
try {
final outputFile = File(p.join(tempDir.path, 'coverage.json'));
final coverageResults = await _getCoverageResult();
await outputFile.writeAsString(coverageResults, flush: true);
final parsedResult = await HitMap.parseFiles([outputFile],
packagesPath: '.dart_tool/package_config.json',
checkIgnoredLines: true);
// This file has ignore:coverage-file.
expect(parsedResult, isNot(contains(_sampleAppFileUri)));
expect(parsedResult, contains(_isolateLibFileUri));
} finally {
await tempDir.delete(recursive: true);
}
});
test('mergeHitmaps', () {
final resultMap = <String, Map<int, int>>{
"foo.dart": {10: 2, 20: 0},
"bar.dart": {10: 3, 20: 1, 30: 0},
};
final newMap = <String, Map<int, int>>{
"bar.dart": {10: 2, 20: 0, 40: 3},
"baz.dart": {10: 1, 20: 0, 30: 1},
};
// ignore: deprecated_member_use_from_same_package
mergeHitmaps(newMap, resultMap);
expect(resultMap, <String, Map<int, int>>{
"foo.dart": {10: 2, 20: 0},
"bar.dart": {10: 5, 20: 1, 30: 0, 40: 3},
"baz.dart": {10: 1, 20: 0, 30: 1},
});
});
test('FileHitMaps.merge', () {
final resultMap = <String, HitMap>{
"foo.dart":
HitMap({10: 2, 20: 0}, {15: 0, 25: 1}, {15: "bobble", 25: "cobble"}),
"bar.dart": HitMap(
{10: 3, 20: 1, 30: 0}, {15: 5, 25: 0}, {15: "gobble", 25: "wobble"}),
};
final newMap = <String, HitMap>{
"bar.dart": HitMap(
{10: 2, 20: 0, 40: 3}, {15: 1, 35: 4}, {15: "gobble", 35: "dobble"}),
"baz.dart": HitMap(
{10: 1, 20: 0, 30: 1}, {15: 0, 25: 2}, {15: "lobble", 25: "zobble"}),
};
resultMap.merge(newMap);
expect(resultMap["foo.dart"]?.lineHits, <int, int>{10: 2, 20: 0});
expect(resultMap["foo.dart"]?.funcHits, <int, int>{15: 0, 25: 1});
expect(resultMap["foo.dart"]?.funcNames,
<int, String>{15: "bobble", 25: "cobble"});
expect(resultMap["bar.dart"]?.lineHits,
<int, int>{10: 5, 20: 1, 30: 0, 40: 3});
expect(resultMap["bar.dart"]?.funcHits, <int, int>{15: 6, 25: 0, 35: 4});
expect(resultMap["bar.dart"]?.funcNames,
<int, String>{15: "gobble", 25: "wobble", 35: "dobble"});
expect(resultMap["baz.dart"]?.lineHits, <int, int>{10: 1, 20: 0, 30: 1});
expect(resultMap["baz.dart"]?.funcHits, <int, int>{15: 0, 25: 2});
expect(resultMap["baz.dart"]?.funcNames,
<int, String>{15: "lobble", 25: "zobble"});
});
}
String? _coverageData;
Future<String> _getCoverageResult() async =>
_coverageData ??= await _collectCoverage(false, false);
Future<String> _collectCoverage(
bool functionCoverage, bool branchCoverage) async {
expect(FileSystemEntity.isFileSync(testAppPath), isTrue);
final openPort = await getOpenPort();
// Run the sample app with the right flags.
final sampleProcess = await runTestApp(openPort);
// Capture the VM service URI.
final serviceUriCompleter = Completer<Uri>();
sampleProcess.stdout
.transform(utf8.decoder)
.transform(LineSplitter())
.listen((line) {
if (!serviceUriCompleter.isCompleted) {
final serviceUri = extractVMServiceUri(line);
if (serviceUri != null) {
serviceUriCompleter.complete(serviceUri);
}
}
});
final serviceUri = await serviceUriCompleter.future;
// Run the collection tool.
// TODO: need to get all of this functionality in the lib
final toolResult = await Process.run(Platform.resolvedExecutable, [
_collectAppPath,
if (functionCoverage) '--function-coverage',
if (branchCoverage) '--branch-coverage',
'--uri',
'$serviceUri',
'--resume-isolates',
'--wait-paused'
]).timeout(timeout, onTimeout: () {
throw 'We timed out waiting for the tool to finish.';
});
if (toolResult.exitCode != 0) {
print(toolResult.stdout);
print(toolResult.stderr);
fail('Tool failed with exit code ${toolResult.exitCode}.');
}
await sampleProcess.exitCode;
await sampleProcess.stderr.drain();
return toolResult.stdout as String;
}