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 `--`
This commit is contained in:
Jakub Fijałkowski 2022-11-16 18:01:22 +01:00 committed by GitHub
parent 4fdaf7ae25
commit 4301731eb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 6 deletions

View file

@ -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?>(
future: waitForRootIsolate(),
timeout: kUnusuallyLongTimeout,
@ -108,11 +121,13 @@ class VMServiceFlutterDriver extends FlutterDriver {
: 'Isolate $isolateNumber is taking an unusually long time to start.',
))!;
_log('Isolate found with number: ${isolateRef.number}');
vms.Isolate isolate = await client.getIsolate(isolateRef.id!);
if (isolate.pauseEvent!.kind == vms.EventKind.kNone) {
isolate = await client.getIsolate(isolateRef.id!);
}
final vms.Isolate isolate = await _warnIfSlow<vms.Isolate>(
future: waitForIsolateToBeRunnable(isolateRef),
timeout: kUnusuallyLongTimeout,
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(
client,
@ -201,7 +216,8 @@ class VMServiceFlutterDriver extends FlutterDriver {
} else if (isolate.pauseEvent!.kind == vms.EventKind.kPauseExit ||
isolate.pauseEvent!.kind == vms.EventKind.kPauseBreakpoint ||
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
// already there.
_log('Isolate is paused mid-flight.');
@ -583,6 +599,9 @@ Future<vms.VmService> _waitAndConnect(String url, Map<String, dynamic>? headers)
/// the VM service.
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
// https://github.com/dart-lang/sdk/blob/main/runtime/vm/timeline.cc
List<String> _timelineStreamsToString(List<TimelineStream> streams) {

View file

@ -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 {
fakeIsolate.pauseEvent = vms.Event(kind: vms.EventKind.kPauseStart, timestamp: 0);
final FlutterDriver driver = await FlutterDriver.connect(dartVmServiceUrl: '', isolateNumber: int.parse(fakeIsolate.number!));
@ -246,6 +273,14 @@ void main() {
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
// 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
@ -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 {
@override
bool supportsTimelineAction = false;
@ -1082,6 +1126,7 @@ class FakeVmService extends Fake implements vms.VmService {
FakeVM? vm;
bool failOnSetFlag = false;
bool failOnResumeWith101 = false;
void Function(vms.Isolate)? onGetIsolate;
final List<String> connectionLog = <String>[];
@ -1092,6 +1137,7 @@ class FakeVmService extends Fake implements vms.VmService {
Future<vms.Isolate> getIsolate(String isolateId) async {
connectionLog.add('getIsolate');
if (isolateId == vm!.isolate!.id) {
onGetIsolate?.call(vm!.isolate!);
return vm!.isolate!;
}
throw UnimplementedError('getIsolate called with unrecognized $isolateId');