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

[flutter_tools] handle missing method on exit, debugDumpX (#80890)

parent 35bffd20
...@@ -364,10 +364,16 @@ class FlutterDevice { ...@@ -364,10 +364,16 @@ class FlutterDevice {
} }
for (final FlutterView view in views) { for (final FlutterView view in views) {
if (view != null && view.uiIsolate != null) { if (view != null && view.uiIsolate != null) {
// If successful, there will be no response from flutterExit. // If successful, there will be no response from flutterExit. If the exit
// method is not registered, this will complete with `false`.
unawaited(vmService.flutterExit( unawaited(vmService.flutterExit(
isolateId: view.uiIsolate.id, isolateId: view.uiIsolate.id,
)); ).then((bool exited) async {
// If exiting the app failed, fall back to stopApp
if (!exited) {
await device.stopApp(package, userIdentifier: userIdentifier);
}
}));
} }
} }
return vmService.service.onDone return vmService.service.onDone
...@@ -378,10 +384,6 @@ class FlutterDevice { ...@@ -378,10 +384,6 @@ class FlutterDevice {
); );
}) })
.timeout(timeoutDelay, onTimeout: () { .timeout(timeoutDelay, onTimeout: () {
// TODO(jonahwilliams): this only seems to fail on CI in the
// flutter_attach_android_test. This log should help verify this
// is where the tool is getting stuck.
globals.logger.printTrace('error: vm service shutdown failed');
return device.stopApp(package, userIdentifier: userIdentifier); return device.stopApp(package, userIdentifier: userIdentifier);
}); });
} }
......
...@@ -545,7 +545,7 @@ class FlutterVmService { ...@@ -545,7 +545,7 @@ class FlutterVmService {
'ext.flutter.debugDumpApp', 'ext.flutter.debugDumpApp',
isolateId: isolateId, isolateId: isolateId,
); );
return response['data']?.toString(); return response != null ? response['data']?.toString() : '';
} }
Future<String> flutterDebugDumpRenderTree({ Future<String> flutterDebugDumpRenderTree({
...@@ -556,7 +556,7 @@ class FlutterVmService { ...@@ -556,7 +556,7 @@ class FlutterVmService {
isolateId: isolateId, isolateId: isolateId,
args: <String, Object>{} args: <String, Object>{}
); );
return response['data']?.toString(); return response != null ? response['data']?.toString() : '';
} }
Future<String> flutterDebugDumpLayerTree({ Future<String> flutterDebugDumpLayerTree({
...@@ -566,7 +566,7 @@ class FlutterVmService { ...@@ -566,7 +566,7 @@ class FlutterVmService {
'ext.flutter.debugDumpLayerTree', 'ext.flutter.debugDumpLayerTree',
isolateId: isolateId, isolateId: isolateId,
); );
return response['data']?.toString(); return response != null ? response['data']?.toString() : '';
} }
Future<String> flutterDebugDumpSemanticsTreeInTraversalOrder({ Future<String> flutterDebugDumpSemanticsTreeInTraversalOrder({
...@@ -576,7 +576,7 @@ class FlutterVmService { ...@@ -576,7 +576,7 @@ class FlutterVmService {
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
isolateId: isolateId, isolateId: isolateId,
); );
return response['data']?.toString(); return response != null ? response['data']?.toString() : '';
} }
Future<String> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({ Future<String> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({
...@@ -586,7 +586,7 @@ class FlutterVmService { ...@@ -586,7 +586,7 @@ class FlutterVmService {
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
isolateId: isolateId, isolateId: isolateId,
); );
return response['data']?.toString(); return response != null ? response['data']?.toString() : '';
} }
Future<Map<String, dynamic>> _flutterToggle(String name, { Future<Map<String, dynamic>> _flutterToggle(String name, {
...@@ -701,15 +701,26 @@ class FlutterVmService { ...@@ -701,15 +701,26 @@ class FlutterVmService {
/// ///
/// This method is only supported by certain embedders. This is /// This method is only supported by certain embedders. This is
/// described by [Device.supportsFlutterExit]. /// described by [Device.supportsFlutterExit].
Future<void> flutterExit({ Future<bool> flutterExit({
@required String isolateId, @required String isolateId,
}) { }) async {
return invokeFlutterExtensionRpcRaw( try {
final Map<String, Object> result = await invokeFlutterExtensionRpcRaw(
'ext.flutter.exit', 'ext.flutter.exit',
isolateId: isolateId, isolateId: isolateId,
).catchError((dynamic error, StackTrace stackTrace) { );
// Do nothing on sentinel or exception, the isolate already exited. // A response of `null` indicates that `invokeFlutterExtensionRpcRaw` caught an RPCError
}, test: (dynamic error) => error is vm_service.SentinelException || error is vm_service.RPCError); // with a missing method code. This can happen when attempting to quit a flutter app
// that never registered the methods in the bindings.
if (result == null) {
return false;
}
} on vm_service.SentinelException {
// Do nothing on sentinel, the isolate already exited.
} on vm_service.RPCError {
// Do nothing on RPCError, the isolate already exited.
}
return true;
} }
/// Return the current platform override for the flutter view running with /// Return the current platform override for the flutter view running with
......
...@@ -1674,6 +1674,42 @@ void main() { ...@@ -1674,6 +1674,42 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
})); }));
testUsingContext('FlutterDevice will exit an isolate that did not register the exit extension method', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
FakeVmServiceRequest(
method: '_flutter.listViews',
jsonResponse: <String, Object>{
'views': <Object>[
fakeFlutterView.toJson(),
],
},
),
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.exit',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
errorCode: RPCErrorCodes.kMethodNotFound,
),
]);
final TestFlutterDevice flutterDevice = TestFlutterDevice(
mockDevice,
);
flutterDevice.vmService = fakeVmServiceHost.vmService;
await flutterDevice.exitApps(timeoutDelay: Duration.zero);
expect(mockDevice.appStopped, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('FlutterDevice can exit from a release mode isolate with no VmService', () => testbed.run(() async { testUsingContext('FlutterDevice can exit from a release mode isolate with no VmService', () => testbed.run(() async {
final TestFlutterDevice flutterDevice = TestFlutterDevice( final TestFlutterDevice flutterDevice = TestFlutterDevice(
mockDevice, mockDevice,
......
...@@ -331,6 +331,101 @@ void main() { ...@@ -331,6 +331,101 @@ void main() {
expect(fakeVmServiceHost.hasRemainingExpectations, false); expect(fakeVmServiceHost.hasRemainingExpectations, false);
}); });
testWithoutContext('flutterDebugDumpSemanticsTreeInTraversalOrder handles missing method', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{
'isolateId': '1'
},
errorCode: RPCErrorCodes.kMethodNotFound,
),
]
);
expect(await fakeVmServiceHost.vmService.flutterDebugDumpSemanticsTreeInTraversalOrder(
isolateId: '1',
), '');
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('flutterDebugDumpSemanticsTreeInInverseHitTestOrder handles missing method', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{
'isolateId': '1'
},
errorCode: RPCErrorCodes.kMethodNotFound,
),
]
);
expect(await fakeVmServiceHost.vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
isolateId: '1',
), '');
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('flutterDebugDumpLayerTree handles missing method', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{
'isolateId': '1'
},
errorCode: RPCErrorCodes.kMethodNotFound,
),
]
);
expect(await fakeVmServiceHost.vmService.flutterDebugDumpLayerTree(
isolateId: '1',
), '');
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('flutterDebugDumpRenderTree handles missing method', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1'
},
errorCode: RPCErrorCodes.kMethodNotFound,
),
]
);
expect(await fakeVmServiceHost.vmService.flutterDebugDumpRenderTree(
isolateId: '1',
), '');
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('flutterDebugDumpApp handles missing method', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1'
},
errorCode: RPCErrorCodes.kMethodNotFound,
),
]
);
expect(await fakeVmServiceHost.vmService.flutterDebugDumpApp(
isolateId: '1',
), '');
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
testWithoutContext('Framework service extension invocations return null if service disappears ', () async { testWithoutContext('Framework service extension invocations return null if service disappears ', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[ requests: <VmServiceExpectation>[
......
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