Unverified Commit 4301731e authored by Jakub Fijałkowski's avatar Jakub Fijałkowski Committed by GitHub

Make Flutter Driver actively wait for runnable isolate (#113969)

* Test the case when main Isolate is in `None` state for long time

* Wait for isolate to become runnable

* Handle `PausePostRequest` as a normal "paused" event

* Use `-= 1` instead of `--`
parent 4fdaf7ae
...@@ -100,6 +100,19 @@ class VMServiceFlutterDriver extends FlutterDriver { ...@@ -100,6 +100,19 @@ class VMServiceFlutterDriver extends FlutterDriver {
} }
} }
// Refreshes the isolate state periodically until the isolate reports as
// being runnable.
Future<vms.Isolate> waitForIsolateToBeRunnable(vms.IsolateRef ref) async {
while (true) {
final vms.Isolate isolate = await client.getIsolate(ref.id!);
if (isolate.pauseEvent!.kind == vms.EventKind.kNone) {
await Future<void>.delayed(_kPauseBetweenIsolateRefresh);
} else {
return isolate;
}
}
}
final vms.IsolateRef isolateRef = (await _warnIfSlow<vms.IsolateRef?>( final vms.IsolateRef isolateRef = (await _warnIfSlow<vms.IsolateRef?>(
future: waitForRootIsolate(), future: waitForRootIsolate(),
timeout: kUnusuallyLongTimeout, timeout: kUnusuallyLongTimeout,
...@@ -108,11 +121,13 @@ class VMServiceFlutterDriver extends FlutterDriver { ...@@ -108,11 +121,13 @@ class VMServiceFlutterDriver extends FlutterDriver {
: 'Isolate $isolateNumber is taking an unusually long time to start.', : 'Isolate $isolateNumber is taking an unusually long time to start.',
))!; ))!;
_log('Isolate found with number: ${isolateRef.number}'); _log('Isolate found with number: ${isolateRef.number}');
vms.Isolate isolate = await client.getIsolate(isolateRef.id!); final vms.Isolate isolate = await _warnIfSlow<vms.Isolate>(
future: waitForIsolateToBeRunnable(isolateRef),
if (isolate.pauseEvent!.kind == vms.EventKind.kNone) { timeout: kUnusuallyLongTimeout,
isolate = await client.getIsolate(isolateRef.id!); message: 'The isolate ${isolateRef.number} is taking unusually long time '
} 'to initialize. It still reports ${vms.EventKind.kNone} as pause '
'event which is incorrect.',
);
final VMServiceFlutterDriver driver = VMServiceFlutterDriver.connectedTo( final VMServiceFlutterDriver driver = VMServiceFlutterDriver.connectedTo(
client, client,
...@@ -201,7 +216,8 @@ class VMServiceFlutterDriver extends FlutterDriver { ...@@ -201,7 +216,8 @@ class VMServiceFlutterDriver extends FlutterDriver {
} else if (isolate.pauseEvent!.kind == vms.EventKind.kPauseExit || } else if (isolate.pauseEvent!.kind == vms.EventKind.kPauseExit ||
isolate.pauseEvent!.kind == vms.EventKind.kPauseBreakpoint || isolate.pauseEvent!.kind == vms.EventKind.kPauseBreakpoint ||
isolate.pauseEvent!.kind == vms.EventKind.kPauseException || isolate.pauseEvent!.kind == vms.EventKind.kPauseException ||
isolate.pauseEvent!.kind == vms.EventKind.kPauseInterrupted) { isolate.pauseEvent!.kind == vms.EventKind.kPauseInterrupted ||
isolate.pauseEvent!.kind == vms.EventKind.kPausePostRequest) {
// If the isolate is paused for any other reason, assume the extension is // If the isolate is paused for any other reason, assume the extension is
// already there. // already there.
_log('Isolate is paused mid-flight.'); _log('Isolate is paused mid-flight.');
...@@ -583,6 +599,9 @@ Future<vms.VmService> _waitAndConnect(String url, Map<String, dynamic>? headers) ...@@ -583,6 +599,9 @@ Future<vms.VmService> _waitAndConnect(String url, Map<String, dynamic>? headers)
/// the VM service. /// the VM service.
const Duration _kPauseBetweenReconnectAttempts = Duration(seconds: 1); const Duration _kPauseBetweenReconnectAttempts = Duration(seconds: 1);
/// The amount of time we wait prior to refreshing the isolate state.
const Duration _kPauseBetweenIsolateRefresh = Duration(milliseconds: 100);
// See `timeline_streams` in // See `timeline_streams` in
// https://github.com/dart-lang/sdk/blob/main/runtime/vm/timeline.cc // https://github.com/dart-lang/sdk/blob/main/runtime/vm/timeline.cc
List<String> _timelineStreamsToString(List<TimelineStream> streams) { List<String> _timelineStreamsToString(List<TimelineStream> streams) {
......
...@@ -189,6 +189,33 @@ void main() { ...@@ -189,6 +189,33 @@ void main() {
); );
}); });
test('Refreshes isolate if it is not started for long time', () async {
fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kNone, timestamp: 0);
fakeClient.onGetIsolate = changeIsolateEventAfter(
5,
vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 1),
);
final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
expect(driver, isNotNull);
expect(
fakeClient.connectionLog,
<String>[
'getIsolate',
'getIsolate',
'getIsolate',
'getIsolate',
'getIsolate',
'setFlag pause_isolates_on_start false',
'resume',
'streamListen Isolate',
'getIsolate',
'onIsolateEvent',
'streamCancel Isolate',
],
);
});
test('Connects to isolate number', () async { test('Connects to isolate number', () async {
fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0); fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '', isolateNumber: int.parse(fakeIsolate.number!)); final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '', isolateNumber: int.parse(fakeIsolate.number!));
...@@ -246,6 +273,14 @@ void main() { ...@@ -246,6 +273,14 @@ void main() {
expectLogContains('Isolate is paused mid-flight'); expectLogContains('Isolate is paused mid-flight');
}); });
test('connects to isolate paused mid-flight after request', () async {
fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPausePostRequest, timestamp: 0);
final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '');
expect(driver, isNotNull);
expectLogContains('Isolate is paused mid-flight');
});
// This test simulates a situation when we believe that the isolate is // This test simulates a situation when we believe that the isolate is
// currently paused, but something else (e.g. a debugger) resumes it before // currently paused, but something else (e.g. a debugger) resumes it before
// we do. There's no need to fail as we should be able to drive the app // we do. There's no need to fail as we should be able to drive the app
...@@ -1055,6 +1090,15 @@ vms.Response? makeFakeResponse( ...@@ -1055,6 +1090,15 @@ vms.Response? makeFakeResponse(
}); });
} }
void Function(vms.Isolate) changeIsolateEventAfter(int gets, vms.Event nextEvent) {
return (vms.Isolate i) {
gets -= 1;
if (gets == 0) {
i.pauseEvent = nextEvent;
}
};
}
class FakeFlutterWebConnection extends Fake implements FlutterWebConnection { class FakeFlutterWebConnection extends Fake implements FlutterWebConnection {
@override @override
bool supportsTimelineAction = false; bool supportsTimelineAction = false;
...@@ -1082,6 +1126,7 @@ class FakeVmService extends Fake implements vms.VmService { ...@@ -1082,6 +1126,7 @@ class FakeVmService extends Fake implements vms.VmService {
FakeVM? vm; FakeVM? vm;
bool failOnSetFlag = false; bool failOnSetFlag = false;
bool failOnResumeWith101 = false; bool failOnResumeWith101 = false;
void Function(vms.Isolate)? onGetIsolate;
final List<String> connectionLog = <String>[]; final List<String> connectionLog = <String>[];
...@@ -1092,6 +1137,7 @@ class FakeVmService extends Fake implements vms.VmService { ...@@ -1092,6 +1137,7 @@ class FakeVmService extends Fake implements vms.VmService {
Future<vms.Isolate> getIsolate(String isolateId) async { Future<vms.Isolate> getIsolate(String isolateId) async {
connectionLog.add('getIsolate'); connectionLog.add('getIsolate');
if (isolateId == vm!.isolate!.id) { if (isolateId == vm!.isolate!.id) {
onGetIsolate?.call(vm!.isolate!);
return vm!.isolate!; return vm!.isolate!;
} }
throw UnimplementedError('getIsolate called with unrecognized $isolateId'); throw UnimplementedError('getIsolate called with unrecognized $isolateId');
......
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