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'; ...@@ -11,6 +11,14 @@ import 'common.dart';
import 'io.dart' as io; import 'io.dart' as io;
import 'logger.dart'; 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 /// Helper class to launch a [dds.DartDevelopmentService]. Allows for us to
/// mock out this functionality for testing purposes. /// mock out this functionality for testing purposes.
class DartDevelopmentService { class DartDevelopmentService {
...@@ -44,7 +52,7 @@ class DartDevelopmentService { ...@@ -44,7 +52,7 @@ class DartDevelopmentService {
'connecting to VM service at $observatoryUri.', 'connecting to VM service at $observatoryUri.',
); );
try { try {
_ddsInstance = await dds.DartDevelopmentService.startDartDevelopmentService( _ddsInstance = await ddsLauncherCallback(
observatoryUri, observatoryUri,
serviceUri: ddsUri, serviceUri: ddsUri,
enableAuthCodes: !disableServiceAuthCodes, enableAuthCodes: !disableServiceAuthCodes,
...@@ -59,9 +67,21 @@ class DartDevelopmentService { ...@@ -59,9 +67,21 @@ class DartDevelopmentService {
} on dds.DartDevelopmentServiceException catch (e) { } on dds.DartDevelopmentServiceException catch (e) {
logger.printTrace('Warning: Failed to start DDS: ${e.message}'); logger.printTrace('Warning: Failed to start DDS: ${e.message}');
if (e.errorCode == dds.DartDevelopmentServiceException.existingDdsInstanceError) { if (e.errorCode == dds.DartDevelopmentServiceException.existingDdsInstanceError) {
_existingDdsUri = Uri.parse( try {
e.message.split(' ').firstWhere((String e) => e.startsWith('http')) _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) { if (!_completer.isCompleted) {
_completer.complete(); _completer.complete();
......
...@@ -236,10 +236,10 @@ class FlutterDevice { ...@@ -236,10 +236,10 @@ class FlutterDevice {
bool existingDds = false; bool existingDds = false;
vm_service.VmService service; vm_service.VmService service;
if (!disableDds) { if (!disableDds) {
void handleError(Exception e) { void handleError(Exception e, StackTrace st) {
globals.printTrace('Fail to connect to service protocol: $observatoryUri: $e'); globals.printTrace('Fail to connect to service protocol: $observatoryUri: $e');
if (!completer.isCompleted) { 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 // This first try block is meant to catch errors that occur during DDS startup
...@@ -252,16 +252,16 @@ class FlutterDevice { ...@@ -252,16 +252,16 @@ class FlutterDevice {
ipv6, ipv6,
disableServiceAuthCodes, disableServiceAuthCodes,
); );
} on dds.DartDevelopmentServiceException catch (e) { } on dds.DartDevelopmentServiceException catch (e, st) {
if (!allowExistingDdsInstance || if (!allowExistingDdsInstance ||
(e.errorCode != dds.DartDevelopmentServiceException.existingDdsInstanceError)) { (e.errorCode != dds.DartDevelopmentServiceException.existingDdsInstanceError)) {
handleError(e); handleError(e, st);
return; return;
} else { } else {
existingDds = true; existingDds = true;
} }
} on Exception catch (e) { } on Exception catch (e, st) {
handleError(e); handleError(e, st);
return; return;
} }
} }
......
...@@ -2683,6 +2683,7 @@ void main() { ...@@ -2683,6 +2683,7 @@ void main() {
when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader); when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader);
when(mockDevice.dds).thenReturn(mockDds); when(mockDevice.dds).thenReturn(mockDds);
when(mockDds.startDartDevelopmentService(any, any, any, any)).thenThrow(FakeDartDevelopmentServiceException()); when(mockDds.startDartDevelopmentService(any, any, any, any)).thenThrow(FakeDartDevelopmentServiceException());
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:1234'));
when(mockDds.done).thenAnswer((_) => noopCompleter.future); when(mockDds.done).thenAnswer((_) => noopCompleter.future);
final TestFlutterDevice flutterDevice = TestFlutterDevice( final TestFlutterDevice flutterDevice = TestFlutterDevice(
...@@ -2703,6 +2704,36 @@ void main() { ...@@ -2703,6 +2704,36 @@ void main() {
}) async => mockVMService, }) 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', () { testUsingContext('nextPlatform moves through expected platforms', () {
expect(nextPlatform('android', TestFeatureFlags()), 'iOS'); expect(nextPlatform('android', TestFeatureFlags()), 'iOS');
...@@ -2726,11 +2757,14 @@ class MockProcessManager extends Mock implements ProcessManager {} ...@@ -2726,11 +2757,14 @@ class MockProcessManager extends Mock implements ProcessManager {}
class MockResidentCompiler extends Mock implements ResidentCompiler {} class MockResidentCompiler extends Mock implements ResidentCompiler {}
class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceException { class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceException {
FakeDartDevelopmentServiceException({this.message = defaultMessage});
@override @override
final int errorCode = dds.DartDevelopmentServiceException.existingDdsInstanceError; final int errorCode = dds.DartDevelopmentServiceException.existingDdsInstanceError;
@override @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 { 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