diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 36f510cae6..71bb4522c7 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -341,10 +341,27 @@ abstract class FlutterCommand extends Command { bool get disableDds => boolArg('disable-dds'); + bool get _hostVmServicePortProvided => argResults.wasParsed('observatory-port') || + argResults.wasParsed('host-vmservice-port'); + + int _tryParseHostVmservicePort() { + try { + return int.parse(stringArg('observatory-port') ?? stringArg('host-vmservice-port')); + } on FormatException catch (error) { + throwToolExit('Invalid port for `--observatory-port/--host-vmservice-port`: $error'); + } + return null; + } + int get ddsPort { - if (argResults.wasParsed('dds-port')) { + if (!argResults.wasParsed('dds-port') && _hostVmServicePortProvided) { + // If an explicit DDS port is _not_ provided, use the host-vmservice-port for DDS. + return _tryParseHostVmservicePort(); + } else if (argResults.wasParsed('dds-port')) { + // If an explicit DDS port is provided, use dds-port for DDS. return int.tryParse(stringArg('dds-port')) ?? 0; } + // Otherwise, DDS can bind to a random port. return 0; } @@ -356,9 +373,7 @@ abstract class FlutterCommand extends Command { /// /// If no port is set, returns null. int get hostVmservicePort { - if (!_usesPortOption || - (argResults['observatory-port'] == null && - argResults['host-vmservice-port'] == null)) { + if (!_usesPortOption || !_hostVmServicePortProvided) { return null; } if (argResults.wasParsed('observatory-port') && @@ -366,12 +381,13 @@ abstract class FlutterCommand extends Command { throwToolExit('Only one of "--observatory-port" and ' '"--host-vmservice-port" may be specified.'); } - try { - return int.parse(stringArg('observatory-port') ?? stringArg('host-vmservice-port')); - } on FormatException catch (error) { - throwToolExit('Invalid port for `--observatory-port/--host-vmservice-port`: $error'); + // If DDS is enabled and no explicit DDS port is provided, use the + // host-vmservice-port for DDS instead and bind the VM service to a random + // port. + if (!disableDds && !argResults.wasParsed('dds-port')) { + return null; } - return null; + return _tryParseHostVmservicePort(); } /// Gets the vmservice port provided to in the 'device-vmservice-port' option. diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 8ed8229478..6603f201f3 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -526,6 +526,9 @@ void main() { '$devicePort', '--observatory-port', '$hostPort', + // Ensure DDS doesn't use hostPort by binding to a random port. + '--dds-port', + '0', ], ); await completer.future; @@ -558,6 +561,9 @@ void main() { '--observatory-port', '$hostPort', '--ipv6', + // Ensure DDS doesn't use hostPort by binding to a random port. + '--dds-port', + '0', ], ); await completer.future; diff --git a/packages/flutter_tools/test/integration.shard/observatory_port_test.dart b/packages/flutter_tools/test/integration.shard/observatory_port_test.dart new file mode 100644 index 0000000000..8128610e3a --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/observatory_port_test.dart @@ -0,0 +1,116 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/convert.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; +import 'test_data/basic_project.dart'; +import 'test_utils.dart'; + +Future getFreePort() async { + int port = 0; + final ServerSocket serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); + port = serverSocket.port; + await serverSocket.close(); + return port; +} + +Future waitForObservatoryMessage(Process process, int port) async { + final Completer completer = Completer(); + process.stdout + .transform(utf8.decoder) + .listen((String line) { + print(line); + if (line.contains('An Observatory debugger and profiler on Flutter test device is available at')) { + if (line.contains('http://127.0.0.1:$port')) { + completer.complete(); + } else { + completer.completeError(Exception('Did not forward to provided port $port, instead found $line')); + } + } + }); + process.stderr + .transform(utf8.decoder) + .listen(print); + return completer.future; +} + +void main() { + Directory tempDir; + final BasicProject _project = BasicProject(); + + setUp(() async { + tempDir = createResolvedTempDirectorySync('run_test.'); + await _project.setUpIn(tempDir); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + testUsingContext('flutter run --observatory-port', () async { + final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + final int port = await getFreePort(); + // If only --observatory-port is provided, --observatory-port will be used by DDS + // and the VM service will bind to a random port. + final Process process = await processManager.start([ + flutterBin, + 'run', + '--show-test-device', + '--observatory-port=$port', + '-d', + 'flutter-tester', + ], workingDirectory: tempDir.path); + await waitForObservatoryMessage(process, port); + process.kill(); + await process.exitCode; + }); + + testUsingContext('flutter run --dds-port --observatory-port', () async { + final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + final int observatoryPort = await getFreePort(); + int ddsPort = await getFreePort(); + while(ddsPort == observatoryPort) { + ddsPort = await getFreePort(); + } + // If both --dds-port and --observatory-port are provided, --dds-port will be used by + // DDS and --observatory-port will be used by the VM service. + final Process process = await processManager.start([ + flutterBin, + 'run', + '--show-test-device', + '--observatory-port=$observatoryPort', + '--dds-port=$ddsPort', + '-d', + 'flutter-tester', + ], workingDirectory: tempDir.path); + await waitForObservatoryMessage(process, ddsPort); + process.kill(); + await process.exitCode; + }); + + testUsingContext('flutter run --dds-port', () async { + final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + final int ddsPort = await getFreePort(); + // If only --dds-port is provided, --dds-port will be used by DDS and the VM service + // will bind to a random port. + final Process process = await processManager.start([ + flutterBin, + 'run', + '--show-test-device', + '--dds-port=$ddsPort', + '-d', + 'flutter-tester', + ], workingDirectory: tempDir.path); + await waitForObservatoryMessage(process, ddsPort); + process.kill(); + await process.exitCode; + }); + +}