Unverified Commit 620a8284 authored by Ben Konyi's avatar Ben Konyi Committed by GitHub

Catch StateError and output more useful error message when DDS fails to start (#72736)

parent af1c629d
......@@ -11,6 +11,14 @@ import 'common.dart';
import 'io.dart' as io;
import 'logger.dart';
@visibleForTesting
Future<dds.DartDevelopmentService> Function(
Uri,
{bool enableAuthCodes,
bool ipv6,
Uri serviceUri,
}) ddsLauncherCallback = dds.DartDevelopmentService.startDartDevelopmentService;
/// Helper class to launch a [dds.DartDevelopmentService]. Allows for us to
/// mock out this functionality for testing purposes.
class DartDevelopmentService {
......@@ -44,7 +52,7 @@ class DartDevelopmentService {
'connecting to VM service at $observatoryUri.',
);
try {
_ddsInstance = await dds.DartDevelopmentService.startDartDevelopmentService(
_ddsInstance = await ddsLauncherCallback(
observatoryUri,
serviceUri: ddsUri,
enableAuthCodes: !disableServiceAuthCodes,
......@@ -59,9 +67,21 @@ class DartDevelopmentService {
} on dds.DartDevelopmentServiceException catch (e) {
logger.printTrace('Warning: Failed to start DDS: ${e.message}');
if (e.errorCode == dds.DartDevelopmentServiceException.existingDdsInstanceError) {
_existingDdsUri = Uri.parse(
e.message.split(' ').firstWhere((String e) => e.startsWith('http'))
);
try {
_existingDdsUri = Uri.parse(
e.message.split(' ').firstWhere((String e) => e.startsWith('http'))
);
} on StateError {
logger.printError(
'DDS has failed to start and there is not an existing DDS instance '
'available to connect to. Please comment on '
'https://github.com/flutter/flutter/issues/72385 with output from '
'"flutter doctor -v" and the following error message:\n\n ${e.message}.'
);
// Wrap the DDS error message in a StateError so it can be collected
// by the crash handler.
throw StateError(e.message);
}
}
if (!_completer.isCompleted) {
_completer.complete();
......
......@@ -236,10 +236,10 @@ class FlutterDevice {
bool existingDds = false;
vm_service.VmService service;
if (!disableDds) {
void handleError(Exception e) {
void handleError(Exception e, StackTrace st) {
globals.printTrace('Fail to connect to service protocol: $observatoryUri: $e');
if (!completer.isCompleted) {
completer.completeError('failed to connect to $observatoryUri');
completer.completeError('failed to connect to $observatoryUri', st);
}
}
// This first try block is meant to catch errors that occur during DDS startup
......@@ -252,16 +252,16 @@ class FlutterDevice {
ipv6,
disableServiceAuthCodes,
);
} on dds.DartDevelopmentServiceException catch (e) {
} on dds.DartDevelopmentServiceException catch (e, st) {
if (!allowExistingDdsInstance ||
(e.errorCode != dds.DartDevelopmentServiceException.existingDdsInstanceError)) {
handleError(e);
handleError(e, st);
return;
} else {
existingDds = true;
}
} on Exception catch (e) {
handleError(e);
} on Exception catch (e, st) {
handleError(e, st);
return;
}
}
......
......@@ -2683,6 +2683,7 @@ void main() {
when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader);
when(mockDevice.dds).thenReturn(mockDds);
when(mockDds.startDartDevelopmentService(any, any, any, any)).thenThrow(FakeDartDevelopmentServiceException());
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:1234'));
when(mockDds.done).thenAnswer((_) => noopCompleter.future);
final TestFlutterDevice flutterDevice = TestFlutterDevice(
......@@ -2703,6 +2704,36 @@ void main() {
}) async => mockVMService,
}));
testUsingContext('Failed DDS start outputs error message', () => testbed.run(() async {
// See https://github.com/flutter/flutter/issues/72385 for context.
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final MockDevice mockDevice = MockDevice();
when(mockDevice.dds).thenReturn(DartDevelopmentService(logger: testLogger));
ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) {
throw FakeDartDevelopmentServiceException(message: 'No URI');
};
final TestFlutterDevice flutterDevice = TestFlutterDevice(
mockDevice,
observatoryUris: Stream<Uri>.value(testUri),
);
bool caught = false;
final Completer<void>done = Completer<void>();
runZonedGuarded(() {
flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete());
}, (Object e, StackTrace st) {
expect(e is StateError, true);
expect((e as StateError).message, contains('No URI'));
expect(testLogger.errorText, contains(
'DDS has failed to start and there is not an existing DDS instance',
));
done.complete();
caught = true;
});
await done.future;
if (!caught) {
fail('Expected a StateError to be thrown.');
}
}));
testUsingContext('nextPlatform moves through expected platforms', () {
expect(nextPlatform('android', TestFeatureFlags()), 'iOS');
......@@ -2726,11 +2757,14 @@ class MockProcessManager extends Mock implements ProcessManager {}
class MockResidentCompiler extends Mock implements ResidentCompiler {}
class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceException {
FakeDartDevelopmentServiceException({this.message = defaultMessage});
@override
final int errorCode = dds.DartDevelopmentServiceException.existingDdsInstanceError;
@override
final String message = 'A DDS instance is already connected at http://localhost:8181';
final String message;
static const String defaultMessage = 'A DDS instance is already connected at http://localhost:8181';
}
class TestFlutterDevice extends FlutterDevice {
......
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