Unverified Commit a40ab895 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tool] Observatory connection error handling cleanup (#38353)

parent f5dcbdab
......@@ -243,6 +243,8 @@ class AttachCommand extends FlutterCommand {
// Determine ipv6 status from the scanned logs.
usesIpv6 = observatoryDiscovery.ipv6;
printStatus('Done.'); // FYI, this message is used as a sentinel in tests.
} catch (error) {
throwToolExit('Failed to establish a debug connection with ${device.name}: $error');
} finally {
await observatoryDiscovery?.cancel();
}
......
......@@ -152,9 +152,9 @@ class IOSDevice extends Device {
@override
final String name;
Map<ApplicationPackage, _IOSDeviceLogReader> _logReaders;
Map<ApplicationPackage, DeviceLogReader> _logReaders;
_IOSDevicePortForwarder _portForwarder;
DevicePortForwarder _portForwarder;
@override
Future<bool> get isLocalEmulator async => false;
......@@ -342,65 +342,55 @@ class IOSDevice extends Device {
if (platformArgs['trace-startup'] ?? false)
launchArguments.add('--trace-startup');
int installationResult = -1;
Uri localObservatoryUri;
final Status installStatus = logger.startProgress('Installing and launching...', timeout: timeoutConfiguration.slowOperation);
if (!debuggingOptions.debuggingEnabled) {
// If debugging is not enabled, just launch the application and continue.
printTrace('Debugging is not enabled');
installationResult = await const IOSDeploy().runApp(
deviceId: id,
bundlePath: bundle.path,
launchArguments: launchArguments,
);
} else {
// Debugging is enabled, look for the observatory server port post launch.
printTrace('Debugging is enabled, connecting to observatory');
// TODO(danrubel): The Android device class does something similar to this code below.
// The various Device subclasses should be refactored and common code moved into the superclass.
final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(
getLogReader(app: package),
portForwarder: portForwarder,
hostPort: debuggingOptions.observatoryPort,
ipv6: ipv6,
);
final Future<Uri> forwardObservatoryUri = observatoryDiscovery.uri;
final Status installStatus = logger.startProgress(
'Installing and launching...',
timeout: timeoutConfiguration.slowOperation);
try {
ProtocolDiscovery observatoryDiscovery;
if (debuggingOptions.debuggingEnabled) {
// Debugging is enabled, look for the observatory server port post launch.
printTrace('Debugging is enabled, connecting to observatory');
// TODO(danrubel): The Android device class does something similar to this code below.
// The various Device subclasses should be refactored and common code moved into the superclass.
observatoryDiscovery = ProtocolDiscovery.observatory(
getLogReader(app: package),
portForwarder: portForwarder,
hostPort: debuggingOptions.observatoryPort,
ipv6: ipv6,
);
}
final Future<int> launch = const IOSDeploy().runApp(
final int installationResult = await const IOSDeploy().runApp(
deviceId: id,
bundlePath: bundle.path,
launchArguments: launchArguments,
);
if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id.');
printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
printError(' open ios/Runner.xcworkspace');
printError('');
return LaunchResult.failed();
}
localObservatoryUri = await launch.then<Uri>((int result) async {
installationResult = result;
if (result != 0) {
printTrace('Failed to launch the application on device.');
return null;
}
if (!debuggingOptions.debuggingEnabled) {
return LaunchResult.succeeded();
}
try {
printTrace('Application launched on the device. Waiting for observatory port.');
return await forwardObservatoryUri;
}).whenComplete(() {
observatoryDiscovery.cancel();
});
}
installStatus.stop();
if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id.');
printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
printError(' open ios/Runner.xcworkspace');
printError('');
return LaunchResult.failed();
final Uri localUri = await observatoryDiscovery.uri;
return LaunchResult.succeeded(observatoryUri: localUri);
} catch (error) {
printError('Failed to establish a debug connection with $id: $error');
return LaunchResult.failed();
} finally {
await observatoryDiscovery?.cancel();
}
} finally {
installStatus.stop();
}
return LaunchResult.succeeded(observatoryUri: localObservatoryUri);
}
@override
......@@ -417,13 +407,24 @@ class IOSDevice extends Device {
@override
DeviceLogReader getLogReader({ ApplicationPackage app }) {
_logReaders ??= <ApplicationPackage, _IOSDeviceLogReader>{};
_logReaders ??= <ApplicationPackage, DeviceLogReader>{};
return _logReaders.putIfAbsent(app, () => _IOSDeviceLogReader(this, app));
}
@visibleForTesting
void setLogReader(ApplicationPackage app, DeviceLogReader logReader) {
_logReaders ??= <ApplicationPackage, DeviceLogReader>{};
_logReaders[app] = logReader;
}
@override
DevicePortForwarder get portForwarder => _portForwarder ??= _IOSDevicePortForwarder(this);
@visibleForTesting
set portForwarder(DevicePortForwarder forwarder) {
_portForwarder = forwarder;
}
@override
void clearLogs() { }
......
......@@ -65,9 +65,9 @@ class ProtocolDiscovery {
if (match != null) {
try {
uri = Uri.parse(match[1]);
} catch (error) {
} catch (error, stackTrace) {
_stopScrapingLogs();
_completer.completeError(error);
_completer.completeError(error, stackTrace);
}
}
......
......@@ -52,15 +52,6 @@ void main() {
mockLogReader = MockDeviceLogReader();
portForwarder = MockPortForwarder();
device = MockAndroidDevice();
when(device.getLogReader()).thenAnswer((_) {
// Now that the reader is used, start writing messages to it.
Timer.run(() {
mockLogReader.addLine('Foo');
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
});
return mockLogReader;
});
when(device.portForwarder)
.thenReturn(portForwarder);
when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
......@@ -82,6 +73,14 @@ void main() {
});
testUsingContext('finds observatory port and forwards', () async {
when(device.getLogReader()).thenAnswer((_) {
// Now that the reader is used, start writing messages to it.
Timer.run(() {
mockLogReader.addLine('Foo');
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
});
return mockLogReader;
});
testDeviceManager.addDevice(device);
final Completer<void> completer = Completer<void>();
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
......@@ -102,7 +101,32 @@ void main() {
Logger: () => logger,
});
testUsingContext('Fails with tool exit on bad Observatory uri', () async {
when(device.getLogReader()).thenAnswer((_) {
// Now that the reader is used, start writing messages to it.
Timer.run(() {
mockLogReader.addLine('Foo');
mockLogReader.addLine('Observatory listening on http:/:/127.0.0.1:$devicePort');
});
return mockLogReader;
});
testDeviceManager.addDevice(device);
expect(createTestCommandRunner(AttachCommand()).run(<String>['attach']),
throwsA(isA<ToolExit>()));
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
Logger: () => logger,
});
testUsingContext('accepts filesystem parameters', () async {
when(device.getLogReader()).thenAnswer((_) {
// Now that the reader is used, start writing messages to it.
Timer.run(() {
mockLogReader.addLine('Foo');
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
});
return mockLogReader;
});
testDeviceManager.addDevice(device);
const String filesystemScheme = 'foo';
......@@ -175,6 +199,14 @@ void main() {
});
testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
when(device.getLogReader()).thenAnswer((_) {
// Now that the reader is used, start writing messages to it.
Timer.run(() {
mockLogReader.addLine('Foo');
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
});
return mockLogReader;
});
testDeviceManager.addDevice(device);
final AttachCommand command = AttachCommand();
......@@ -190,6 +222,14 @@ void main() {
},);
testUsingContext('exits when observatory-port is specified and debug-port is not', () async {
when(device.getLogReader()).thenAnswer((_) {
// Now that the reader is used, start writing messages to it.
Timer.run(() {
mockLogReader.addLine('Foo');
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
});
return mockLogReader;
});
testDeviceManager.addDevice(device);
final AttachCommand command = AttachCommand();
......
......@@ -153,7 +153,7 @@ ro.build.version.codename=REL
typedef ProcessFactory = Process Function(List<String> command);
/// A ProcessManager that starts Processes by delegating to a ProcessFactory.
class MockProcessManager implements ProcessManager {
class MockProcessManager extends Mock implements ProcessManager {
ProcessFactory processFactory = (List<String> commands) => MockProcess();
bool canRunSucceeds = true;
bool runSucceeds = true;
......@@ -180,9 +180,6 @@ class MockProcessManager implements ProcessManager {
commands = command;
return Future<Process>.value(processFactory(command));
}
@override
dynamic noSuchMethod(Invocation invocation) => null;
}
/// A process that exits successfully with no output and ignores all input.
......
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