Commit 03ae2cf3 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Catch crashes during or before coverage collection. (#8207)

This assumes a fix to https://github.com/dart-lang/test/issues/542

The timeout added in this patch is a workaround for https://github.com/dart-lang/coverage/issues/165
parent d784a237
...@@ -193,7 +193,11 @@ class TestCommand extends FlutterCommand { ...@@ -193,7 +193,11 @@ class TestCommand extends FlutterCommand {
final String shellPath = tools.getHostToolPath(HostTool.SkyShell) ?? platform.environment['SKY_SHELL']; final String shellPath = tools.getHostToolPath(HostTool.SkyShell) ?? platform.environment['SKY_SHELL'];
if (!fs.isFileSync(shellPath)) if (!fs.isFileSync(shellPath))
throwToolExit('Cannot find Flutter shell at $shellPath'); throwToolExit('Cannot find Flutter shell at $shellPath');
loader.installHook(shellPath: shellPath, collector: collector, debuggerMode: argResults['start-paused']); loader.installHook(
shellPath: shellPath,
collector: collector,
debuggerMode: argResults['start-paused'],
);
Cache.releaseLockEarly(); Cache.releaseLockEarly();
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:coverage/coverage.dart'; import 'package:coverage/coverage.dart' as coverage;
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
...@@ -19,7 +19,7 @@ class CoverageCollector { ...@@ -19,7 +19,7 @@ class CoverageCollector {
if (_globalHitmap == null) if (_globalHitmap == null)
_globalHitmap = hitmap; _globalHitmap = hitmap;
else else
mergeHitmaps(hitmap, _globalHitmap); coverage.mergeHitmaps(hitmap, _globalHitmap);
} }
/// Collects coverage for the given [Process] using the given `port`. /// Collects coverage for the given [Process] using the given `port`.
...@@ -37,13 +37,18 @@ class CoverageCollector { ...@@ -37,13 +37,18 @@ class CoverageCollector {
process.exitCode.then<Null>((int code) { process.exitCode.then<Null>((int code) {
exitCode = code; exitCode = code;
}); });
if (exitCode != null)
throw new Exception('Failed to collect coverage, process terminated before coverage could be collected.');
printTrace('pid $pid (port $port): collecting coverage data...'); printTrace('pid $pid (port $port): collecting coverage data...');
final Map<String, dynamic> data = await collect(host.address, port, false, false); final Map<String, dynamic> data = await coverage.collect(host.address, port, false, false).timeout(
const Duration(seconds: 30),
onTimeout: () { throw new Exception('Failed to collect coverage, it took more than thirty seconds.'); },
);
printTrace('pid $pid (port $port): ${ exitCode != null ? "process terminated prematurely with exit code $exitCode; aborting" : "collected coverage data; merging..." }'); printTrace('pid $pid (port $port): ${ exitCode != null ? "process terminated prematurely with exit code $exitCode; aborting" : "collected coverage data; merging..." }');
if (exitCode != null) if (exitCode != null)
throw new Exception('Failed to collect coverage, process terminated prematurely.'); throw new Exception('Failed to collect coverage, process terminated while coverage was being collected.');
_addHitmap(createHitmap(data['coverage'])); _addHitmap(coverage.createHitmap(data['coverage']));
printTrace('pid $pid (port $port): done merging coverage data into global coverage map.'); printTrace('pid $pid (port $port): done merging coverage data into global coverage map.');
} }
...@@ -56,17 +61,17 @@ class CoverageCollector { ...@@ -56,17 +61,17 @@ class CoverageCollector {
/// If [timeout] is specified, the future will timeout (with a /// If [timeout] is specified, the future will timeout (with a
/// [TimeoutException]) after the specified duration. /// [TimeoutException]) after the specified duration.
Future<String> finalizeCoverage({ Future<String> finalizeCoverage({
Formatter formatter, coverage.Formatter formatter,
Duration timeout, Duration timeout,
}) async { }) async {
printTrace('formating coverage data'); printTrace('formating coverage data');
if (_globalHitmap == null) if (_globalHitmap == null)
return null; return null;
if (formatter == null) { if (formatter == null) {
Resolver resolver = new Resolver(packagesPath: PackageMap.globalPackagesPath); coverage.Resolver resolver = new coverage.Resolver(packagesPath: PackageMap.globalPackagesPath);
String packagePath = fs.currentDirectory.path; String packagePath = fs.currentDirectory.path;
List<String> reportOn = <String>[fs.path.join(packagePath, 'lib')]; List<String> reportOn = <String>[fs.path.join(packagePath, 'lib')];
formatter = new LcovFormatter(resolver, reportOn: reportOn, basePath: packagePath); formatter = new coverage.LcovFormatter(resolver, reportOn: reportOn, basePath: packagePath);
} }
String result = await formatter.format(_globalHitmap); String result = await formatter.format(_globalHitmap);
_globalHitmap = null; _globalHitmap = null;
......
...@@ -71,7 +71,13 @@ enum _TestResult { crashed, harnessBailed, testBailed } ...@@ -71,7 +71,13 @@ enum _TestResult { crashed, harnessBailed, testBailed }
typedef Future<Null> _Finalizer(); typedef Future<Null> _Finalizer();
class _FlutterPlatform extends PlatformPlugin { class _FlutterPlatform extends PlatformPlugin {
_FlutterPlatform({ this.shellPath, this.collector, this.debuggerMode, this.explicitObservatoryPort, this.explicitDiagnosticPort }) { _FlutterPlatform({
this.shellPath,
this.collector,
this.debuggerMode,
this.explicitObservatoryPort,
this.explicitDiagnosticPort,
}) {
assert(shellPath != null); assert(shellPath != null);
} }
...@@ -113,15 +119,15 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -113,15 +119,15 @@ class _FlutterPlatform extends PlatformPlugin {
localController.stream, localController.stream,
remoteSink, remoteSink,
); );
_startTest(testPath, localChannel, ourTestCount).whenComplete(() { testCompleteCompleter.complete(_startTest(testPath, localChannel, ourTestCount));
testCompleteCompleter.complete();
});
return remoteChannel; return remoteChannel;
} }
Future<Null> _startTest(String testPath, StreamChannel<dynamic> controller, int ourTestCount) async { Future<Null> _startTest(String testPath, StreamChannel<dynamic> controller, int ourTestCount) async {
printTrace('test $ourTestCount: starting test $testPath'); printTrace('test $ourTestCount: starting test $testPath');
dynamic outOfBandError; // error that we couldn't send to the harness that we need to send via our future
final List<_Finalizer> finalizers = <_Finalizer>[]; final List<_Finalizer> finalizers = <_Finalizer>[];
bool subprocessActive = false; bool subprocessActive = false;
bool controllerSinkClosed = false; bool controllerSinkClosed = false;
...@@ -334,6 +340,7 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -334,6 +340,7 @@ class _FlutterPlatform extends PlatformPlugin {
controller.sink.addError(error, stack); controller.sink.addError(error, stack);
} else { } else {
printError('unhandled error during test:\n$testPath\n$error'); printError('unhandled error during test:\n$testPath\n$error');
outOfBandError ??= error;
} }
} finally { } finally {
printTrace('test $ourTestCount: cleaning up...'); printTrace('test $ourTestCount: cleaning up...');
...@@ -346,6 +353,7 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -346,6 +353,7 @@ class _FlutterPlatform extends PlatformPlugin {
controller.sink.addError(error, stack); controller.sink.addError(error, stack);
} else { } else {
printError('unhandled error during finalization of test:\n$testPath\n$error'); printError('unhandled error during finalization of test:\n$testPath\n$error');
outOfBandError ??= error;
} }
} }
} }
...@@ -357,6 +365,10 @@ class _FlutterPlatform extends PlatformPlugin { ...@@ -357,6 +365,10 @@ class _FlutterPlatform extends PlatformPlugin {
} }
assert(!subprocessActive); assert(!subprocessActive);
assert(controllerSinkClosed); assert(controllerSinkClosed);
if (outOfBandError != null) {
printTrace('test $ourTestCount: finished with out-of-band failure');
throw outOfBandError;
}
printTrace('test $ourTestCount: finished'); printTrace('test $ourTestCount: finished');
return null; return null;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment