
All temporary directory start with `flutter_` and have their random component separated from the name by a period, as in `flutter_test_bundle.YFYQMY`. I've tried to find some of the places where we didn't cleanly delete temporary directories, too. This greatly reduces, though it does not entirely eliminate, the directories we leave behind when running tests, especially `flutter_tools` tests. While I was at it I standardized on `tempDir` as the variable name for temporary directories, since it was the most common, removing occurrences of `temp` and `tmp`, among others. Also I factored out some common code that used to catch exceptions that happen on Windows, and made more places use that pattern.
157 lines
5.9 KiB
Dart
157 lines
5.9 KiB
Dart
// Copyright 2016 The Chromium Authors. 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 'package:coverage/coverage.dart' as coverage;
|
|
|
|
import '../base/file_system.dart';
|
|
import '../base/io.dart';
|
|
import '../base/logger.dart';
|
|
import '../base/os.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/process_manager.dart';
|
|
import '../dart/package_map.dart';
|
|
import '../globals.dart';
|
|
|
|
import 'watcher.dart';
|
|
|
|
/// A class that's used to collect coverage data during tests.
|
|
class CoverageCollector extends TestWatcher {
|
|
Map<String, dynamic> _globalHitmap;
|
|
|
|
@override
|
|
Future<void> handleFinishedTest(ProcessEvent event) async {
|
|
printTrace('test ${event.childIndex}: collecting coverage');
|
|
await collectCoverage(event.process, event.observatoryUri);
|
|
}
|
|
|
|
void _addHitmap(Map<String, dynamic> hitmap) {
|
|
if (_globalHitmap == null)
|
|
_globalHitmap = hitmap;
|
|
else
|
|
coverage.mergeHitmaps(hitmap, _globalHitmap);
|
|
}
|
|
|
|
/// Collects coverage for the given [Process] using the given `port`.
|
|
///
|
|
/// This should be called when the code whose coverage data is being collected
|
|
/// has been run to completion so that all coverage data has been recorded.
|
|
///
|
|
/// The returned [Future] completes when the coverage is collected.
|
|
Future<Null> collectCoverage(Process process, Uri observatoryUri) async {
|
|
assert(process != null);
|
|
assert(observatoryUri != null);
|
|
|
|
final int pid = process.pid;
|
|
int exitCode;
|
|
// Synchronization is enforced by the API contract. Error handling
|
|
// synchronization is done in the code below where `exitCode` is checked.
|
|
// Callback cannot throw.
|
|
process.exitCode.then<Null>((int code) { // ignore: unawaited_futures
|
|
exitCode = code;
|
|
});
|
|
if (exitCode != null)
|
|
throw new Exception('Failed to collect coverage, process terminated before coverage could be collected.');
|
|
|
|
printTrace('pid $pid: collecting coverage data from $observatoryUri...');
|
|
final Map<String, dynamic> data = await coverage
|
|
.collect(observatoryUri, false, false)
|
|
.timeout(
|
|
const Duration(minutes: 2),
|
|
onTimeout: () {
|
|
throw new Exception('Timed out while collecting coverage.');
|
|
},
|
|
);
|
|
printTrace(() {
|
|
final StringBuffer buf = new StringBuffer()
|
|
..write('pid $pid ($observatoryUri): ')
|
|
..write(exitCode == null
|
|
? 'collected coverage data; merging...'
|
|
: 'process terminated prematurely with exit code $exitCode; aborting');
|
|
return buf.toString();
|
|
}());
|
|
if (exitCode != null)
|
|
throw new Exception('Failed to collect coverage, process terminated while coverage was being collected.');
|
|
_addHitmap(coverage.createHitmap(data['coverage']));
|
|
printTrace('pid $pid ($observatoryUri): done merging coverage data into global coverage map.');
|
|
}
|
|
|
|
/// Returns a future that will complete with the formatted coverage data
|
|
/// (using [formatter]) once all coverage data has been collected.
|
|
///
|
|
/// This will not start any collection tasks. It us up to the caller of to
|
|
/// call [collectCoverage] for each process first.
|
|
///
|
|
/// If [timeout] is specified, the future will timeout (with a
|
|
/// [TimeoutException]) after the specified duration.
|
|
Future<String> finalizeCoverage({
|
|
coverage.Formatter formatter,
|
|
Duration timeout,
|
|
}) async {
|
|
printTrace('formating coverage data');
|
|
if (_globalHitmap == null)
|
|
return null;
|
|
if (formatter == null) {
|
|
final coverage.Resolver resolver = new coverage.Resolver(packagesPath: PackageMap.globalPackagesPath);
|
|
final String packagePath = fs.currentDirectory.path;
|
|
final List<String> reportOn = <String>[fs.path.join(packagePath, 'lib')];
|
|
formatter = new coverage.LcovFormatter(resolver, reportOn: reportOn, basePath: packagePath);
|
|
}
|
|
final String result = await formatter.format(_globalHitmap);
|
|
_globalHitmap = null;
|
|
return result;
|
|
}
|
|
|
|
Future<bool> collectCoverageData(String coveragePath, { bool mergeCoverageData = false }) async {
|
|
final Status status = logger.startProgress('Collecting coverage information...');
|
|
final String coverageData = await finalizeCoverage(
|
|
timeout: const Duration(seconds: 30),
|
|
);
|
|
status.stop();
|
|
printTrace('coverage information collection complete');
|
|
if (coverageData == null)
|
|
return false;
|
|
|
|
final File coverageFile = fs.file(coveragePath)
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(coverageData, flush: true);
|
|
printTrace('wrote coverage data to $coveragePath (size=${coverageData.length})');
|
|
|
|
const String baseCoverageData = 'coverage/lcov.base.info';
|
|
if (mergeCoverageData) {
|
|
if (!fs.isFileSync(baseCoverageData)) {
|
|
printError('Missing "$baseCoverageData". Unable to merge coverage data.');
|
|
return false;
|
|
}
|
|
|
|
if (os.which('lcov') == null) {
|
|
String installMessage = 'Please install lcov.';
|
|
if (platform.isLinux)
|
|
installMessage = 'Consider running "sudo apt-get install lcov".';
|
|
else if (platform.isMacOS)
|
|
installMessage = 'Consider running "brew install lcov".';
|
|
printError('Missing "lcov" tool. Unable to merge coverage data.\n$installMessage');
|
|
return false;
|
|
}
|
|
|
|
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_test_coverage.');
|
|
try {
|
|
final File sourceFile = coverageFile.copySync(fs.path.join(tempDir.path, 'lcov.source.info'));
|
|
final ProcessResult result = processManager.runSync(<String>[
|
|
'lcov',
|
|
'--add-tracefile', baseCoverageData,
|
|
'--add-tracefile', sourceFile.path,
|
|
'--output-file', coverageFile.path,
|
|
]);
|
|
if (result.exitCode != 0)
|
|
return false;
|
|
} finally {
|
|
tempDir.deleteSync(recursive: true);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|