Unverified Commit c9b28338 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

ensure test isolate is paused before collecting coverage (#35188)

parent 919dcf53
...@@ -47,16 +47,16 @@ class CoverageCollector extends TestWatcher { ...@@ -47,16 +47,16 @@ class CoverageCollector extends TestWatcher {
/// has been run to completion so that all coverage data has been recorded. /// has been run to completion so that all coverage data has been recorded.
/// ///
/// The returned [Future] completes when the coverage is collected. /// The returned [Future] completes when the coverage is collected.
Future<void> collectCoverageIsolate(Uri observatoryUri) async { Future<void> collectCoverageIsolate(Uri observatoryUri, String debugName) async {
assert(observatoryUri != null); assert(observatoryUri != null);
printTrace('collecting coverage data from $observatoryUri...'); print('collecting coverage data from $observatoryUri...');
final Map<String, dynamic> data = await collect(observatoryUri, (String libraryName) { final Map<String, dynamic> data = await collect(observatoryUri, (String libraryName) {
// If we have a specified coverage directory or could not find the package name, then // If we have a specified coverage directory or could not find the package name, then
// accept all libraries. // accept all libraries.
return (coverageDirectory != null) return (coverageDirectory != null)
|| (flutterProject == null) || (flutterProject == null)
|| libraryName.contains(flutterProject.manifest.appName); || libraryName.contains(flutterProject.manifest.appName);
}); }, waitPaused: true, debugName: debugName);
if (data == null) { if (data == null) {
throw Exception('Failed to collect coverage.'); throw Exception('Failed to collect coverage.');
} }
...@@ -185,13 +185,39 @@ class CoverageCollector extends TestWatcher { ...@@ -185,13 +185,39 @@ class CoverageCollector extends TestWatcher {
} }
Future<VMService> _defaultConnect(Uri serviceUri) { Future<VMService> _defaultConnect(Uri serviceUri) {
return VMService.connect(serviceUri, compression: CompressionOptions.compressionOff); return VMService.connect(
serviceUri, compression: CompressionOptions.compressionOff);
} }
Future<Map<String, dynamic>> collect(Uri serviceUri, bool Function(String) libraryPredicate, Future<Map<String, dynamic>> collect(Uri serviceUri, bool Function(String) libraryPredicate, {
[Future<VMService> Function(Uri) connector = _defaultConnect]) async { bool waitPaused = false,
String debugName,
Future<VMService> Function(Uri) connector = _defaultConnect,
}) async {
final VMService vmService = await connector(serviceUri); final VMService vmService = await connector(serviceUri);
await vmService.getVM(); await vmService.getVM();
final Isolate isolate = vmService.vm.isolates.firstWhere((Isolate isolate) => isolate.name == debugName);
if (!waitPaused) {
return _getAllCoverage(vmService, libraryPredicate);
}
const int kPollAttempts = 20;
int i = 0;
while (i < kPollAttempts) {
await isolate.load();
if (isolate.pauseEvent?.kind == ServiceEvent.kPauseStart) {
break;
}
await Future<void>.delayed(const Duration(milliseconds: 50));
i += 1;
}
if (i == kPollAttempts) {
print('Isolate $debugName was never paused, refusing to collect coverage');
return const <String, dynamic>{
'type': 'CodeCoverage',
'coverage': <Object>[]
};
}
print('isolate is paused, collecting coverage...');
return _getAllCoverage(vmService, libraryPredicate); return _getAllCoverage(vmService, libraryPredicate);
} }
......
...@@ -23,7 +23,7 @@ void main() { ...@@ -23,7 +23,7 @@ void main() {
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
return <String, Object>{'type': 'Sentinel', 'kind': 'Collected', 'valueAsString': '<collected>'}; return <String, Object>{'type': 'Sentinel', 'kind': 'Collected', 'valueAsString': '<collected>'};
}); });
final Map<String, Object> result = await collect(null, (String predicate) => true, (Uri uri) async { final Map<String, Object> result = await collect(null, (String predicate) => true, connector: (Uri uri) async {
return mockVMService; return mockVMService;
}); });
......
...@@ -77,8 +77,10 @@ class VMPlatform extends PlatformPlugin { ...@@ -77,8 +77,10 @@ class VMPlatform extends PlatformPlugin {
final dynamic channel = IsolateChannel<Object>.connectReceive(receivePort) final dynamic channel = IsolateChannel<Object>.connectReceive(receivePort)
.transformStream(StreamTransformer<Object, Object>.fromHandlers(handleDone: (EventSink<Object> sink) async { .transformStream(StreamTransformer<Object, Object>.fromHandlers(handleDone: (EventSink<Object> sink) async {
try { try {
// Pause the isolate so it is ready for coverage collection.
isolate.pause();
// this will throw if collection fails. // this will throw if collection fails.
await coverageCollector.collectCoverageIsolate(info.serverUri); await coverageCollector.collectCoverageIsolate(info.serverUri, path);
} finally { } finally {
isolate.kill(priority: Isolate.immediate); isolate.kill(priority: Isolate.immediate);
isolate = null; isolate = null;
...@@ -115,6 +117,7 @@ class VMPlatform extends PlatformPlugin { ...@@ -115,6 +117,7 @@ class VMPlatform extends PlatformPlugin {
return await Isolate.spawnUri(p.toUri(testPath), <String>[], message, return await Isolate.spawnUri(p.toUri(testPath), <String>[], message,
packageConfig: p.toUri('.packages'), packageConfig: p.toUri('.packages'),
checked: true, checked: true,
debugName: path,
); );
} }
......
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