[flutter_tools] Fix parsing of existing DDS URIs from exceptions (#119506)
* [flutter_tools] Fix parsing of existing DDS URIs from exceptions
Fixes #118609.
* Fix formatting
Co-authored-by: Christopher Fujino <fujino@google.com>
* Fix formatting
Co-authored-by: Christopher Fujino <fujino@google.com>
---------
Co-authored-by: Christopher Fujino <fujino@google.com>
diff --git a/packages/flutter_tools/lib/src/base/dds.dart b/packages/flutter_tools/lib/src/base/dds.dart
index 3c20d98..c7fe0fa 100644
--- a/packages/flutter_tools/lib/src/base/dds.dart
+++ b/packages/flutter_tools/lib/src/base/dds.dart
@@ -71,9 +71,21 @@
logger.printTrace('Warning: Failed to start DDS: ${e.message}');
if (e.errorCode == dds.DartDevelopmentServiceException.existingDdsInstanceError) {
try {
- _existingDdsUri = Uri.parse(
- e.message.split(' ').firstWhere((String e) => e.startsWith('http'))
- );
+ // First try to use the new field to avoid parsing from the message.
+ _existingDdsUri = e is dds.ExistingDartDevelopmentServiceException ? e.ddsUri : null;
+
+ // Otherwise, fall back to parsing from the exception (old DDS).
+ // This is not completely reliable which is why the new field above
+ // was added.
+ if (_existingDdsUri == null) {
+ String parsedUrl = e.message.split(' ').firstWhere((String e) => e.startsWith('http'));
+ // Trim trailing full stops from the message.
+ // https://github.com/flutter/flutter/issues/118609.
+ if (parsedUrl.endsWith('.')) {
+ parsedUrl = parsedUrl.substring(0, parsedUrl.length - 1);
+ }
+ _existingDdsUri ??= Uri.parse(parsedUrl);
+ }
} on StateError {
if (e.message.contains('Existing VM service clients prevent DDS from taking control.')) {
throwToolExit('${e.message}. Please rebuild your application with a newer version of Flutter.');
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 30b1ad3..d0d40ba 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -2158,6 +2158,73 @@
}) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
}));
+ testUsingContext('Uses existing DDS URI from exception field', () => testbed.run(() async {
+ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
+ final FakeDevice device = FakeDevice()
+ ..dds = DartDevelopmentService();
+ ddsLauncherCallback = (Uri uri, {bool enableAuthCodes = true, bool ipv6 = false, Uri? serviceUri, List<String> cachedUserTags = const <String>[], dds.UriConverter? uriConverter}) {
+ throw dds.DartDevelopmentServiceException.existingDdsInstance(
+ 'Existing DDS at http://localhost/existingDdsInMessage.',
+ ddsUri: Uri.parse('http://localhost/existingDdsInField'),
+ );
+ };
+ final TestFlutterDevice flutterDevice = TestFlutterDevice(
+ device,
+ observatoryUris: Stream<Uri>.value(testUri),
+ );
+ final Completer<void> done = Completer<void>();
+ await runZonedGuarded(
+ () => flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete()),
+ (_, __) => done.complete(),
+ );
+ await done.future;
+ expect(device.dds.uri, Uri.parse('http://localhost/existingDdsInField'));
+ }, overrides: <Type, Generator>{
+ VMServiceConnector: () => (Uri httpUri, {
+ ReloadSources? reloadSources,
+ Restart? restart,
+ CompileExpression? compileExpression,
+ GetSkSLMethod? getSkSLMethod,
+ PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
+ io.CompressionOptions? compression,
+ Device? device,
+ required Logger logger,
+ }) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
+ }));
+
+ testUsingContext('Falls back to existing DDS URI from exception message', () => testbed.run(() async {
+ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
+ final FakeDevice device = FakeDevice()
+ ..dds = DartDevelopmentService();
+ ddsLauncherCallback = (Uri uri, {bool enableAuthCodes = true, bool ipv6 = false, Uri? serviceUri, List<String> cachedUserTags = const <String>[], dds.UriConverter? uriConverter}) {
+ throw dds.DartDevelopmentServiceException.existingDdsInstance(
+ 'Existing DDS at http://localhost/existingDdsInMessage.',
+ );
+ };
+ final TestFlutterDevice flutterDevice = TestFlutterDevice(
+ device,
+ observatoryUris: Stream<Uri>.value(testUri),
+ );
+ final Completer<void>done = Completer<void>();
+ await runZonedGuarded(
+ () => flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete()),
+ (_, __) => done.complete(),
+ );
+ await done.future;
+ expect(device.dds.uri, Uri.parse('http://localhost/existingDdsInMessage'));
+ }, overrides: <Type, Generator>{
+ VMServiceConnector: () => (Uri httpUri, {
+ ReloadSources? reloadSources,
+ Restart? restart,
+ CompileExpression? compileExpression,
+ GetSkSLMethod? getSkSLMethod,
+ PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
+ io.CompressionOptions? compression,
+ Device? device,
+ required Logger logger,
+ }) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
+ }));
+
testUsingContext('Host VM service ipv6 defaults', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice()