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 {
}
for (final FlutterView view in views) {
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(
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
......@@ -378,10 +384,6 @@ class FlutterDevice {
);
})
.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);
});
}
......
......@@ -545,7 +545,7 @@ class FlutterVmService {
'ext.flutter.debugDumpApp',
isolateId: isolateId,
);
return response['data']?.toString();
return response != null ? response['data']?.toString() : '';
}
Future<String> flutterDebugDumpRenderTree({
......@@ -556,7 +556,7 @@ class FlutterVmService {
isolateId: isolateId,
args: <String, Object>{}
);
return response['data']?.toString();
return response != null ? response['data']?.toString() : '';
}
Future<String> flutterDebugDumpLayerTree({
......@@ -566,7 +566,7 @@ class FlutterVmService {
'ext.flutter.debugDumpLayerTree',
isolateId: isolateId,
);
return response['data']?.toString();
return response != null ? response['data']?.toString() : '';
}
Future<String> flutterDebugDumpSemanticsTreeInTraversalOrder({
......@@ -576,7 +576,7 @@ class FlutterVmService {
'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
isolateId: isolateId,
);
return response['data']?.toString();
return response != null ? response['data']?.toString() : '';
}
Future<String> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({
......@@ -586,7 +586,7 @@ class FlutterVmService {
'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
isolateId: isolateId,
);
return response['data']?.toString();
return response != null ? response['data']?.toString() : '';
}
Future<Map<String, dynamic>> _flutterToggle(String name, {
......@@ -701,15 +701,26 @@ class FlutterVmService {
///
/// This method is only supported by certain embedders. This is
/// described by [Device.supportsFlutterExit].
Future<void> flutterExit({
Future<bool> flutterExit({
@required String isolateId,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.exit',
isolateId: isolateId,
).catchError((dynamic error, StackTrace stackTrace) {
// Do nothing on sentinel or exception, the isolate already exited.
}, test: (dynamic error) => error is vm_service.SentinelException || error is vm_service.RPCError);
}) async {
try {
final Map<String, Object> result = await invokeFlutterExtensionRpcRaw(
'ext.flutter.exit',
isolateId: isolateId,
);
// A response of `null` indicates that `invokeFlutterExtensionRpcRaw` caught an 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
......
......@@ -1674,6 +1674,42 @@ void main() {
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 {
final TestFlutterDevice flutterDevice = TestFlutterDevice(
mockDevice,
......
......@@ -331,6 +331,101 @@ void main() {
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 {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
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