mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland: Handle more cases where the tool receives RPCError 112 (#74602)
* Reland: Handle more cases where the tool receives RPCError 112 * Add null-aware access
This commit is contained in:
parent
6be4d1c8bf
commit
2007186d2e
@ -97,19 +97,21 @@ class ScreenshotCommand extends FlutterCommand {
|
||||
outputFile = globals.fs.file(stringArg(_kOut));
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
switch (stringArg(_kType)) {
|
||||
case _kDeviceType:
|
||||
await runScreenshot(outputFile);
|
||||
return FlutterCommandResult.success();
|
||||
break;
|
||||
case _kSkiaType:
|
||||
await runSkia(outputFile);
|
||||
return FlutterCommandResult.success();
|
||||
success = await runSkia(outputFile);
|
||||
break;
|
||||
case _kRasterizerType:
|
||||
await runRasterizer(outputFile);
|
||||
return FlutterCommandResult.success();
|
||||
success = await runRasterizer(outputFile);
|
||||
break;
|
||||
}
|
||||
|
||||
return FlutterCommandResult.success();
|
||||
return success ? FlutterCommandResult.success()
|
||||
: FlutterCommandResult.fail();
|
||||
}
|
||||
|
||||
Future<void> runScreenshot(File outputFile) async {
|
||||
@ -126,10 +128,17 @@ class ScreenshotCommand extends FlutterCommand {
|
||||
_showOutputFileInfo(outputFile);
|
||||
}
|
||||
|
||||
Future<void> runSkia(File outputFile) async {
|
||||
Future<bool> runSkia(File outputFile) async {
|
||||
final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri));
|
||||
final vm_service.VmService vmService = await connectToVmService(observatoryUri);
|
||||
final vm_service.Response skp = await vmService.screenshotSkp();
|
||||
if (skp == null) {
|
||||
globals.printError(
|
||||
'The Skia picture request failed, probably because the device was '
|
||||
'disconnected',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
outputFile ??= globals.fsUtils.getUniqueFile(
|
||||
globals.fs.currentDirectory,
|
||||
'flutter',
|
||||
@ -140,12 +149,20 @@ class ScreenshotCommand extends FlutterCommand {
|
||||
await sink.close();
|
||||
_showOutputFileInfo(outputFile);
|
||||
_ensureOutputIsNotJsonRpcError(outputFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> runRasterizer(File outputFile) async {
|
||||
Future<bool> runRasterizer(File outputFile) async {
|
||||
final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri));
|
||||
final vm_service.VmService vmService = await connectToVmService(observatoryUri);
|
||||
final vm_service.Response response = await vmService.screenshot();
|
||||
if (response == null) {
|
||||
globals.printError(
|
||||
'The screenshot request failed, probably because the device was '
|
||||
'disconnected',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
outputFile ??= globals.fsUtils.getUniqueFile(
|
||||
globals.fs.currentDirectory,
|
||||
'flutter',
|
||||
@ -156,6 +173,7 @@ class ScreenshotCommand extends FlutterCommand {
|
||||
await sink.close();
|
||||
_showOutputFileInfo(outputFile);
|
||||
_ensureOutputIsNotJsonRpcError(outputFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
void _ensureOutputIsNotJsonRpcError(File outputFile) {
|
||||
|
||||
@ -419,8 +419,15 @@ class DevFS {
|
||||
final vm_service.Response response = await _vmService.createDevFS(fsName);
|
||||
_baseUri = Uri.parse(response.json['uri'] as String);
|
||||
} on vm_service.RPCError catch (rpcException) {
|
||||
if (rpcException.code == RPCErrorCodes.kServiceDisappeared) {
|
||||
// This can happen if the device has been disconnected, so translate to
|
||||
// a DevFSException, which the caller will handle.
|
||||
throw DevFSException('Service disconnected', rpcException);
|
||||
}
|
||||
// 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
|
||||
if (rpcException.code != 1001) {
|
||||
// Other RPCErrors are unexpected. Rethrow so it will hit crash
|
||||
// logging.
|
||||
rethrow;
|
||||
}
|
||||
_logger.printTrace('DevFS: Creating failed. Destroying and trying again');
|
||||
|
||||
@ -32,7 +32,7 @@ class Tracing {
|
||||
final Logger _logger;
|
||||
|
||||
Future<void> startTracing() async {
|
||||
await vmService.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
|
||||
await vmService.setTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
|
||||
await vmService.clearVMTimeline();
|
||||
}
|
||||
|
||||
@ -78,8 +78,13 @@ class Tracing {
|
||||
}
|
||||
status.stop();
|
||||
}
|
||||
final vm_service.Timeline timeline = await vmService.getVMTimeline();
|
||||
await vmService.setVMTimelineFlags(<String>[]);
|
||||
final vm_service.Response timeline = await vmService.getTimeline();
|
||||
await vmService.setTimelineFlags(<String>[]);
|
||||
if (timeline == null) {
|
||||
throwToolExit(
|
||||
'The device disconnected before the timeline could be retrieved.',
|
||||
);
|
||||
}
|
||||
return timeline.json;
|
||||
}
|
||||
}
|
||||
|
||||
@ -728,23 +728,12 @@ extension FlutterVmService on vm_service.VmService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Invoke a flutter extension method, if the flutter extension is not
|
||||
/// available, returns null.
|
||||
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
|
||||
Future<vm_service.Response> _checkedCallServiceExtension(
|
||||
String method, {
|
||||
@required String isolateId,
|
||||
Map<String, dynamic> args,
|
||||
}) async {
|
||||
try {
|
||||
|
||||
final vm_service.Response response = await callServiceExtension(
|
||||
method,
|
||||
args: <String, Object>{
|
||||
'isolateId': isolateId,
|
||||
...?args,
|
||||
},
|
||||
);
|
||||
return response.json;
|
||||
return await callServiceExtension(method, args: args);
|
||||
} on vm_service.RPCError catch (err) {
|
||||
// If an application is not using the framework or the VM service
|
||||
// disappears while handling a request, return null.
|
||||
@ -756,6 +745,23 @@ extension FlutterVmService on vm_service.VmService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke a flutter extension method, if the flutter extension is not
|
||||
/// available, returns null.
|
||||
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
|
||||
String method, {
|
||||
@required String isolateId,
|
||||
Map<String, dynamic> args,
|
||||
}) async {
|
||||
final vm_service.Response response = await _checkedCallServiceExtension(
|
||||
method,
|
||||
args: <String, Object>{
|
||||
'isolateId': isolateId,
|
||||
...?args,
|
||||
},
|
||||
);
|
||||
return response?.json;
|
||||
}
|
||||
|
||||
/// List all [FlutterView]s attached to the current VM.
|
||||
///
|
||||
/// If this returns an empty list, it will poll forever unless [returnEarly]
|
||||
@ -799,26 +805,34 @@ extension FlutterVmService on vm_service.VmService {
|
||||
|
||||
/// Create a new development file system on the device.
|
||||
Future<vm_service.Response> createDevFS(String fsName) {
|
||||
return callServiceExtension('_createDevFS', args: <String, dynamic>{'fsName': fsName});
|
||||
// Call the unchecked version of `callServiceExtension` because the caller
|
||||
// has custom handling of certain RPCErrors.
|
||||
return callServiceExtension(
|
||||
'_createDevFS',
|
||||
args: <String, dynamic>{'fsName': fsName},
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete an existing file system.
|
||||
Future<vm_service.Response> deleteDevFS(String fsName) {
|
||||
return callServiceExtension('_deleteDevFS', args: <String, dynamic>{'fsName': fsName});
|
||||
Future<void> deleteDevFS(String fsName) async {
|
||||
await _checkedCallServiceExtension(
|
||||
'_deleteDevFS',
|
||||
args: <String, dynamic>{'fsName': fsName},
|
||||
);
|
||||
}
|
||||
|
||||
Future<vm_service.Response> screenshot() {
|
||||
return callServiceExtension(kScreenshotMethod);
|
||||
return _checkedCallServiceExtension(kScreenshotMethod);
|
||||
}
|
||||
|
||||
Future<vm_service.Response> screenshotSkp() {
|
||||
return callServiceExtension(kScreenshotSkpMethod);
|
||||
return _checkedCallServiceExtension(kScreenshotSkpMethod);
|
||||
}
|
||||
|
||||
/// Set the VM timeline flags.
|
||||
Future<vm_service.Response> setVMTimelineFlags(List<String> recordedStreams) {
|
||||
Future<void> setTimelineFlags(List<String> recordedStreams) async {
|
||||
assert(recordedStreams != null);
|
||||
return callServiceExtension(
|
||||
await _checkedCallServiceExtension(
|
||||
'setVMTimelineFlags',
|
||||
args: <String, dynamic>{
|
||||
'recordedStreams': recordedStreams,
|
||||
@ -826,8 +840,8 @@ extension FlutterVmService on vm_service.VmService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<vm_service.Response> getVMTimeline() {
|
||||
return callServiceExtension('getVMTimeline');
|
||||
Future<vm_service.Response> getTimeline() {
|
||||
return _checkedCallServiceExtension('getVMTimeline');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,20 @@ final FakeVmServiceRequest createDevFSRequest = FakeVmServiceRequest(
|
||||
}
|
||||
);
|
||||
|
||||
const FakeVmServiceRequest failingCreateDevFSRequest = FakeVmServiceRequest(
|
||||
method: '_createDevFS',
|
||||
args: <String, Object>{
|
||||
'fsName': 'test',
|
||||
},
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
);
|
||||
|
||||
const FakeVmServiceRequest failingDeleteDevFSRequest = FakeVmServiceRequest(
|
||||
method: '_deleteDevFS',
|
||||
args: <String, dynamic>{'fsName': 'test'},
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWithoutContext('DevFSByteContent', () {
|
||||
final DevFSByteContent content = DevFSByteContent(<int>[4, 5, 6]);
|
||||
@ -93,6 +107,73 @@ void main() {
|
||||
expect(content.isModified, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async {
|
||||
final HttpClient httpClient = MockHttpClient();
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[failingCreateDevFSRequest],
|
||||
);
|
||||
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
|
||||
|
||||
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
|
||||
when(httpRequest.headers).thenReturn(MockHttpHeaders());
|
||||
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
|
||||
return Future<HttpClientRequest>.value(httpRequest);
|
||||
});
|
||||
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
|
||||
when(httpRequest.close()).thenAnswer((Invocation invocation) {
|
||||
return Future<HttpClientResponse>.value(httpClientResponse);
|
||||
});
|
||||
|
||||
final DevFS devFS = DevFS(
|
||||
fakeVmServiceHost.vmService,
|
||||
'test',
|
||||
fileSystem.currentDirectory,
|
||||
osUtils: osUtils,
|
||||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
httpClient: httpClient,
|
||||
);
|
||||
expect(() async => await devFS.create(), throwsA(isA<DevFSException>()));
|
||||
});
|
||||
|
||||
testWithoutContext('DevFS destroy is resiliant to vmservice disconnection', () async {
|
||||
final HttpClient httpClient = MockHttpClient();
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
createDevFSRequest,
|
||||
failingDeleteDevFSRequest,
|
||||
],
|
||||
);
|
||||
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
|
||||
|
||||
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
|
||||
when(httpRequest.headers).thenReturn(MockHttpHeaders());
|
||||
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
|
||||
return Future<HttpClientRequest>.value(httpRequest);
|
||||
});
|
||||
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
|
||||
when(httpRequest.close()).thenAnswer((Invocation invocation) {
|
||||
return Future<HttpClientResponse>.value(httpClientResponse);
|
||||
});
|
||||
|
||||
final DevFS devFS = DevFS(
|
||||
fakeVmServiceHost.vmService,
|
||||
'test',
|
||||
fileSystem.currentDirectory,
|
||||
osUtils: osUtils,
|
||||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
httpClient: httpClient,
|
||||
);
|
||||
|
||||
expect(await devFS.create(), isNotNull);
|
||||
await devFS.destroy(); // Testing that this does not throw.
|
||||
});
|
||||
|
||||
testWithoutContext('DevFS retries uploads when connection reset by peer', () async {
|
||||
final HttpClient httpClient = MockHttpClient();
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
|
||||
@ -130,6 +130,29 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('throws tool exit if the vmservice disconnects', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
|
||||
...vmServiceSetup,
|
||||
const FakeVmServiceRequest(
|
||||
method: 'getVMTimeline',
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: 'setVMTimelineFlags',
|
||||
args: <String, Object>{
|
||||
'recordedStreams': <Object>[],
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
await expectLater(() async => await downloadStartupTrace(fakeVmServiceHost.vmService,
|
||||
output: fileSystem.currentDirectory,
|
||||
logger: logger,
|
||||
), throwsToolExit(message: 'The device disconnected before the timeline could be retrieved.'));
|
||||
});
|
||||
|
||||
testWithoutContext('throws tool exit if timeline is missing the engine start event', () async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
|
||||
@ -315,20 +315,58 @@ void main() {
|
||||
testWithoutContext('Framework service extension invocations return null if service disappears ', () async {
|
||||
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
const FakeVmServiceRequest(method: kGetSkSLsMethod, args: <String, Object>{
|
||||
'viewId': '1234',
|
||||
}, errorCode: RPCErrorCodes.kServiceDisappeared),
|
||||
const FakeVmServiceRequest(method: kListViewsMethod, errorCode: RPCErrorCodes.kServiceDisappeared),
|
||||
const FakeVmServiceRequest(
|
||||
method: kGetSkSLsMethod,
|
||||
args: <String, Object>{
|
||||
'viewId': '1234',
|
||||
},
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: kListViewsMethod,
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: kScreenshotMethod,
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: kScreenshotSkpMethod,
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: 'setVMTimelineFlags',
|
||||
args: <String, dynamic>{
|
||||
'recordedStreams': <String>['test'],
|
||||
},
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
const FakeVmServiceRequest(
|
||||
method: 'getVMTimeline',
|
||||
errorCode: RPCErrorCodes.kServiceDisappeared,
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
final Map<String, Object> skSLs = await fakeVmServiceHost.vmService.getSkSLs(
|
||||
viewId: '1234',
|
||||
);
|
||||
expect(skSLs, null);
|
||||
expect(skSLs, isNull);
|
||||
|
||||
final List<FlutterView> views = await fakeVmServiceHost.vmService.getFlutterViews();
|
||||
expect(views, null);
|
||||
expect(views, isNull);
|
||||
|
||||
final vm_service.Response screenshot = await fakeVmServiceHost.vmService.screenshot();
|
||||
expect(screenshot, isNull);
|
||||
|
||||
final vm_service.Response screenshotSkp = await fakeVmServiceHost.vmService.screenshotSkp();
|
||||
expect(screenshotSkp, isNull);
|
||||
|
||||
// Checking that this doesn't throw.
|
||||
await fakeVmServiceHost.vmService.setTimelineFlags(<String>['test']);
|
||||
|
||||
final vm_service.Response timeline = await fakeVmServiceHost.vmService.getTimeline();
|
||||
expect(timeline, isNull);
|
||||
|
||||
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user