diff --git a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart index 9784c8f4f3..eff1bea7e0 100644 --- a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart +++ b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart @@ -127,27 +127,6 @@ class VMServiceFlutterDriver extends FlutterDriver { driver._dartVmReconnectUrl = dartVmServiceUrl; - // Attempts to resume the isolate, but does not crash if it fails because - // the isolate is already resumed. There could be a race with other tools, - // such as a debugger, any of which could have resumed the isolate. - Future resumeLeniently() { - _log('Attempting to resume isolate'); - return isolate.resume().catchError((dynamic e) { - const int vmMustBePausedCode = 101; - if (e is rpc.RpcException && e.code == vmMustBePausedCode) { - // No biggie; something else must have resumed the isolate - _log( - 'Attempted to resume an already resumed isolate. This may happen ' - 'when we lose a race with another tool (usually a debugger) that ' - 'is connected to the same isolate.' - ); - } else { - // Failed to resume due to another reason. Fail hard. - throw e; - } - }); - } - /// Waits for a signal from the VM service that the extension is registered. /// /// Looks at the list of loaded extensions for the current [isolateRef], as @@ -195,11 +174,18 @@ class VMServiceFlutterDriver extends FlutterDriver { }); } + // The Dart VM may be running with --pause-isolates-on-start. + // Set a listener to unpause new isolates as they are ready to run, + // otherwise they'll hang indefinitely. + client.onIsolateRunnable.listen((VMIsolateRef isolateRef) async { + _resumeLeniently(await isolateRef.load()); + }); + // Attempt to resume isolate if it was paused if (isolate.pauseEvent is VMPauseStartEvent) { _log('Isolate is paused at start.'); - await resumeLeniently(); + await _resumeLeniently(isolate); } else if (isolate.pauseEvent is VMPauseExitEvent || isolate.pauseEvent is VMPauseBreakpointEvent || isolate.pauseEvent is VMPauseExceptionEvent || @@ -207,7 +193,7 @@ class VMServiceFlutterDriver extends FlutterDriver { // If the isolate is paused for any other reason, assume the extension is // already there. _log('Isolate is paused mid-flight.'); - await resumeLeniently(); + await _resumeLeniently(isolate); } else if (isolate.pauseEvent is VMResumeEvent) { _log('Isolate is not paused. Assuming application is ready.'); } else { @@ -240,6 +226,27 @@ class VMServiceFlutterDriver extends FlutterDriver { return driver; } + /// Attempts to resume the isolate, but does not crash if it fails because + /// the isolate is already resumed. There could be a race with other tools, + /// such as a debugger, any of which could have resumed the isolate. + static Future _resumeLeniently(VMIsolate isolate) { + _log('Attempting to resume isolate'); + return isolate.resume().catchError((dynamic e) { + const int vmMustBePausedCode = 101; + if (e is rpc.RpcException && e.code == vmMustBePausedCode) { + // No biggie; something else must have resumed the isolate + _log( + 'Attempted to resume an already resumed isolate. This may happen ' + 'when we lose a race with another tool (usually a debugger) that ' + 'is connected to the same isolate.' + ); + } else { + // Failed to resume due to another reason. Fail hard. + throw e; + } + }); + } + static int _nextDriverId = 0; static const String _flutterExtensionMethodName = 'ext.flutter.driver'; diff --git a/packages/flutter_driver/test/flutter_driver_test.dart b/packages/flutter_driver/test/flutter_driver_test.dart index 5a2844d393..bcb0898dc5 100644 --- a/packages/flutter_driver/test/flutter_driver_test.dart +++ b/packages/flutter_driver/test/flutter_driver_test.dart @@ -35,6 +35,7 @@ void main() { MockVM mockVM; MockIsolate mockIsolate; MockPeer mockPeer; + MockIsolate otherIsolate; void expectLogContains(String message) { expect(log, anyElement(contains(message))); @@ -45,8 +46,12 @@ void main() { mockClient = MockVMServiceClient(); mockVM = MockVM(); mockIsolate = MockIsolate(); + otherIsolate = MockIsolate(); mockPeer = MockPeer(); when(mockClient.getVM()).thenAnswer((_) => Future.value(mockVM)); + when(mockClient.onIsolateRunnable).thenAnswer((Invocation invocation) { + return Stream.fromIterable([otherIsolate]); + }); when(mockVM.isolates).thenReturn([mockIsolate]); when(mockIsolate.loadRunnable()).thenAnswer((_) => Future.value(mockIsolate)); when(mockIsolate.extensionRpcs).thenReturn([]); @@ -60,6 +65,10 @@ void main() { VMServiceClientConnection(mockClient, mockPeer) ); }; + when(otherIsolate.load()).thenAnswer((_) => Future.value(otherIsolate)); + when(otherIsolate.resume()).thenAnswer((Invocation invocation) { + return Future.value(null); + }); }); tearDown(() async { @@ -77,15 +86,20 @@ void main() { connectionLog.add('resume'); return Future.value(null); }); + when(otherIsolate.pauseEvent).thenReturn(MockVMPauseStartEvent()); when(mockIsolate.onExtensionAdded).thenAnswer((Invocation invocation) { connectionLog.add('onExtensionAdded'); return Stream.fromIterable(['ext.flutter.driver']); }); + when(otherIsolate.resume()).thenAnswer((Invocation invocation) { + connectionLog.add('other-resume'); + return Future.value(null); + }); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: ''); expect(driver, isNotNull); expectLogContains('Isolate is paused at start'); - expect(connectionLog, ['resume', 'streamListen', 'onExtensionAdded']); + expect(connectionLog, ['resume', 'streamListen', 'other-resume', 'onExtensionAdded']); }); test('connects to isolate paused mid-flight', () async {