diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 8e7aef05857..26907596cf5 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'runner.dart' as runner; -import 'src/artifacts.dart'; import 'src/base/context.dart'; import 'src/base/io.dart'; import 'src/base/logger.dart'; @@ -110,7 +109,7 @@ Future main(List args) async { // devtools source code. DevtoolsLauncher: () => DevtoolsServerLauncher( processManager: globals.processManager, - dartExecutable: globals.artifacts!.getArtifactPath(Artifact.engineDartBinary), + artifacts: globals.artifacts!, logger: globals.logger, botDetector: globals.botDetector, ), diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index f4137326a26..0e0e12ecf40 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -218,7 +218,7 @@ Future runInContext( ), DevtoolsLauncher: () => DevtoolsServerLauncher( processManager: globals.processManager, - dartExecutable: globals.artifacts!.getArtifactPath(Artifact.engineDartBinary), + artifacts: globals.artifacts!, logger: globals.logger, botDetector: globals.botDetector, ), diff --git a/packages/flutter_tools/lib/src/devtools_launcher.dart b/packages/flutter_tools/lib/src/devtools_launcher.dart index 25811c73a76..6f28bef246e 100644 --- a/packages/flutter_tools/lib/src/devtools_launcher.dart +++ b/packages/flutter_tools/lib/src/devtools_launcher.dart @@ -2,13 +2,12 @@ // 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:meta/meta.dart'; import 'package:process/process.dart'; +import 'artifacts.dart'; import 'base/bot_detector.dart'; import 'base/common.dart'; import 'base/io.dart' as io; @@ -16,21 +15,22 @@ import 'base/logger.dart'; import 'convert.dart'; import 'resident_runner.dart'; -/// An implementation of the devtools launcher that uses `pub global activate` to -/// start a server instance. +/// An implementation of the devtools launcher that uses `dart devtools` to +/// start a DevTools server instance. class DevtoolsServerLauncher extends DevtoolsLauncher { DevtoolsServerLauncher({ required ProcessManager processManager, - required String dartExecutable, required Logger logger, required BotDetector botDetector, + required Artifacts artifacts, }) : _processManager = processManager, - _dartExecutable = dartExecutable, _logger = logger, - _botDetector = botDetector; + _botDetector = botDetector, + _artifacts = artifacts; final ProcessManager _processManager; - final String _dartExecutable; + final Artifacts _artifacts; + late final String _dartExecutable = _artifacts.getArtifactPath(Artifact.engineDartBinary); final Logger _logger; final BotDetector _botDetector; final Completer _processStartCompleter = Completer(); @@ -42,6 +42,8 @@ class DevtoolsServerLauncher extends DevtoolsLauncher { static final RegExp _serveDevToolsPattern = RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+?)\.?$'); + static final RegExp _serveDtdPattern = + RegExp(r'Serving the Dart Tooling Daemon at (ws:\/\/[a-zA-Z0-9:/=_\-\.\[\]]+?)\.?$'); @override Future get processStart => _processStartCompleter.future; @@ -55,21 +57,28 @@ class DevtoolsServerLauncher extends DevtoolsLauncher { _dartExecutable, 'devtools', '--no-launch-browser', + if (printDtdUri) '--print-dtd', if (vmServiceUri != null) '--vm-uri=$vmServiceUri', ...?additionalArguments, ]); _processStartCompleter.complete(); - final Completer completer = Completer(); + + final Completer devToolsCompleter = Completer(); _devToolsProcess!.stdout .transform(utf8.decoder) .transform(const LineSplitter()) .listen((String line) { - final Match? match = _serveDevToolsPattern.firstMatch(line); - if (match != null) { - final String url = match[1]!; - completer.complete(Uri.parse(url)); - } - }); + final Match? dtdMatch = _serveDtdPattern.firstMatch(line); + if (dtdMatch != null) { + final String uri = dtdMatch[1]!; + dtdUri = Uri.parse(uri); + } + final Match? devToolsMatch = _serveDevToolsPattern.firstMatch(line); + if (devToolsMatch != null) { + final String url = devToolsMatch[1]!; + devToolsCompleter.complete(Uri.parse(url)); + } + }); _devToolsProcess!.stderr .transform(utf8.decoder) .transform(const LineSplitter()) @@ -84,7 +93,11 @@ class DevtoolsServerLauncher extends DevtoolsLauncher { } ); - devToolsUrl = await completer.future; + // We do not need to wait for a [Completer] holding the DTD URI because + // the DTD URI will be output to stdout before the DevTools URI. Awaiting + // a [Completer] for the DevTools URI ensures both values will be + // populated before returning. + devToolsUrl = await devToolsCompleter.future; } on Exception catch (e, st) { _logger.printError('Failed to launch DevTools: $e', stackTrace: st); } diff --git a/packages/flutter_tools/lib/src/resident_devtools_handler.dart b/packages/flutter_tools/lib/src/resident_devtools_handler.dart index 5eac9320ac0..d304451db72 100644 --- a/packages/flutter_tools/lib/src/resident_devtools_handler.dart +++ b/packages/flutter_tools/lib/src/resident_devtools_handler.dart @@ -24,6 +24,19 @@ abstract class ResidentDevtoolsHandler { /// The current devtools server, or null if one is not running. DevToolsServerAddress? get activeDevToolsServer; + /// The Dart Tooling Daemon (DTD) URI for the DTD instance being hosted by + /// DevTools server. + /// + /// This will be null if the DevTools server is not served through Flutter + /// tools (e.g. if it is served from an IDE). + Uri? get dtdUri; + + /// Whether to print the Dart Tooling Daemon URI. + /// + /// This will always return false when there is not a DTD instance being + /// served from the DevTools server. + bool get printDtdUri; + /// Whether it's ok to announce the [activeDevToolsServer]. /// /// This should only return true once all the devices have been notified @@ -63,6 +76,12 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { return _devToolsLauncher?.activeDevToolsServer; } + @override + Uri? get dtdUri => _devToolsLauncher?.dtdUri; + + @override + bool get printDtdUri => _devToolsLauncher?.printDtdUri ?? false; + @override bool get readyToAnnounce => _readyToAnnounce; bool _readyToAnnounce = false; @@ -337,6 +356,12 @@ class NoOpDevtoolsHandler implements ResidentDevtoolsHandler { wasShutdown = true; return; } + + @override + Uri? get dtdUri => null; + + @override + bool get printDtdUri => false; } /// Convert a [URI] with query parameters into a display format instead diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 6679e654919..87a2e513ecd 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1526,6 +1526,12 @@ abstract class ResidentRunner extends ResidentHandlers { ); } if (includeDevtools) { + if (_residentDevtoolsHandler!.printDtdUri) { + final Uri? dtdUri = residentDevtoolsHandler!.dtdUri; + if (dtdUri != null) { + globals.printStatus('The Dart Tooling Daemon is available at: $dtdUri\n'); + } + } final Uri? uri = devToolsServerAddress!.uri?.replace( queryParameters: {'uri': '${device.vmService!.httpAddress}'}, ); @@ -1945,6 +1951,26 @@ abstract class DevtoolsLauncher { } } + /// The Dart Tooling Daemon (DTD) URI for the DTD instance being hosted by + /// DevTools server. + /// + /// This will be null if the DevTools server is not served through Flutter + /// tools (e.g. if it is served from an IDE). + Uri? get dtdUri => _dtdUri; + Uri? _dtdUri; + @protected + set dtdUri(Uri? value) => _dtdUri = value; + + /// Whether to print the Dart Tooling Daemon URI. + /// + /// This will always return false when there is not a DTD instance being + /// served from the DevTools server. + bool get printDtdUri => _printDtdUri ?? false; + bool? _printDtdUri; + set printDtdUri(bool value) { + _printDtdUri = value; + } + /// The URL of the current DevTools server. /// /// Returns null if [ready] is not complete. diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 2970950a607..eb4d283b299 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -17,6 +17,7 @@ import '../base/utils.dart'; import '../cache.dart'; import '../convert.dart'; import '../globals.dart' as globals; +import '../resident_runner.dart'; import '../tester/flutter_tester.dart'; import '../version.dart'; import '../web/web_device.dart'; @@ -35,6 +36,7 @@ abstract final class FlutterGlobalOptions { static const String kMachineFlag = 'machine'; static const String kPackagesOption = 'packages'; static const String kPrefixedErrorsFlag = 'prefixed-errors'; + static const String kPrintDtd = 'print-dtd'; static const String kQuietFlag = 'quiet'; static const String kShowTestDeviceFlag = 'show-test-device'; static const String kShowWebServerDeviceFlag = 'show-web-server-device'; @@ -116,6 +118,12 @@ class FlutterCommandRunner extends CommandRunner { argParser.addOption(FlutterGlobalOptions.kPackagesOption, hide: !verboseHelp, help: 'Path to your "package_config.json" file.'); + argParser.addFlag( + FlutterGlobalOptions.kPrintDtd, + negatable: false, + help: 'Print the address of the Dart Tooling Daemon, if one is hosted by the Flutter CLI.', + hide: !verboseHelp, + ); if (verboseHelp) { argParser.addSeparator('Local build selection options (not normally required):'); } @@ -357,6 +365,10 @@ class FlutterCommandRunner extends CommandRunner { if (machineFlag && topLevelResults.command?.name != 'analyze') { throwToolExit('The "--machine" flag is only valid with the "--version" flag or the "analyze --suggestions" command.', exitCode: 2); } + + final bool shouldPrintDtdUri = topLevelResults[FlutterGlobalOptions.kPrintDtd] as bool? ?? false; + DevtoolsLauncher.instance!.printDtdUri = shouldPrintDtdUri; + await super.runCommand(topLevelResults); }, ); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index d467da24828..d930dfe674a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -824,7 +824,7 @@ void main() { expect(result['host'], '127.0.0.1'); expect(result['port'], 1234); }, overrides: { - DevtoolsLauncher: () => FakeDevtoolsLauncher(DevToolsServerAddress('127.0.0.1', 1234)), + DevtoolsLauncher: () => FakeDevtoolsLauncher(serverAddress: DevToolsServerAddress('127.0.0.1', 1234)), }); testUsingContext('devtools.serve command should return null fields if null returned', () async { @@ -840,7 +840,7 @@ void main() { expect(result['host'], null); expect(result['port'], null); }, overrides: { - DevtoolsLauncher: () => FakeDevtoolsLauncher(null), + DevtoolsLauncher: () => FakeDevtoolsLauncher(), }); testUsingContext('proxy.connect tries to connect to an ipv4 address and proxies the connection correctly', () async { @@ -1286,18 +1286,6 @@ class FakeDeviceLogReader implements DeviceLogReader { } -class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher { - FakeDevtoolsLauncher(this._serverAddress); - - final DevToolsServerAddress? _serverAddress; - - @override - Future serve() async => _serverAddress; - - @override - Future close() async {} -} - class FakeApplicationPackageFactory implements ApplicationPackageFactory { TargetPlatform? platformRequested; File? applicationBinaryRequested; diff --git a/packages/flutter_tools/test/general.shard/devtools_launcher_test.dart b/packages/flutter_tools/test/general.shard/devtools_launcher_test.dart index 94770357bea..2952af5378d 100644 --- a/packages/flutter_tools/test/general.shard/devtools_launcher_test.dart +++ b/packages/flutter_tools/test/general.shard/devtools_launcher_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/cache.dart'; @@ -15,52 +16,87 @@ import '../src/fake_process_manager.dart'; import '../src/fakes.dart'; void main() { - late BufferLogger logger; - Cache.flutterRoot = ''; - setUp(() { - logger = BufferLogger.test(); - }); + (BufferLogger, Artifacts) getTestState() => (BufferLogger.test(), Artifacts.test()); testWithoutContext('DevtoolsLauncher launches DevTools from the SDK and saves the URI', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); final Completer completer = Completer(); final DevtoolsLauncher launcher = DevtoolsServerLauncher( - dartExecutable: 'dart', + artifacts: artifacts, logger: logger, botDetector: const FakeBotDetector(false), processManager: FakeProcessManager.list([ FakeCommand( command: const [ - 'dart', + 'Artifact.engineDartBinary', 'devtools', '--no-launch-browser', ], - stdout: 'Serving DevTools at http://127.0.0.1:9100\n', + stdout: 'Serving DevTools at http://127.0.0.1:9100.\n', completer: completer, ), ]), ); + expect(launcher.dtdUri, isNull); + expect(launcher.printDtdUri, false); final DevToolsServerAddress? address = await launcher.serve(); expect(address?.host, '127.0.0.1'); expect(address?.port, 9100); + expect(launcher.dtdUri, isNull); + expect(launcher.printDtdUri, false); }); - testWithoutContext('DevtoolsLauncher does not launch a new DevTools instance if one is already active', () async { + testWithoutContext('DevtoolsLauncher saves the Dart Tooling Daemon uri', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); final Completer completer = Completer(); final DevtoolsLauncher launcher = DevtoolsServerLauncher( - dartExecutable: 'dart', + artifacts: artifacts, logger: logger, botDetector: const FakeBotDetector(false), processManager: FakeProcessManager.list([ FakeCommand( command: const [ - 'dart', + 'Artifact.engineDartBinary', + 'devtools', + '--no-launch-browser', + '--print-dtd', + ], + stdout: ''' +Serving the Dart Tooling Daemon at ws://127.0.0.1:53449/ +Serving DevTools at http://127.0.0.1:9100. +''', + completer: completer, + ), + ]), + )..printDtdUri = true; + + expect(launcher.dtdUri, isNull); + expect(launcher.printDtdUri, true); + final DevToolsServerAddress? address = await launcher.serve(); + expect(address?.host, '127.0.0.1'); + expect(address?.port, 9100); + expect(launcher.dtdUri?.toString(), 'ws://127.0.0.1:53449/'); + expect(launcher.printDtdUri, true); + }); + + testWithoutContext('DevtoolsLauncher does not launch a new DevTools instance if one is already active', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); + final Completer completer = Completer(); + final DevtoolsLauncher launcher = DevtoolsServerLauncher( + artifacts: artifacts, + logger: logger, + botDetector: const FakeBotDetector(false), + processManager: FakeProcessManager.list([ + FakeCommand( + command: const [ + 'Artifact.engineDartBinary', 'devtools', '--no-launch-browser', ], - stdout: 'Serving DevTools at http://127.0.0.1:9100\n', + stdout: 'Serving DevTools at http://127.0.0.1:9100.\n', completer: completer, ), ]), @@ -70,27 +106,28 @@ void main() { expect(address?.host, '127.0.0.1'); expect(address?.port, 9100); - // Call `serve` again and verify that the already running server is returned. + // Call `serve` again and verify that the already-running server is returned. address = await launcher.serve(); expect(address?.host, '127.0.0.1'); expect(address?.port, 9100); }); testWithoutContext('DevtoolsLauncher can launch devtools with a memory profile', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); final FakeProcessManager processManager = FakeProcessManager.list([ const FakeCommand( command: [ - 'dart', + 'Artifact.engineDartBinary', 'devtools', '--no-launch-browser', '--vm-uri=localhost:8181/abcdefg', '--profile-memory=foo', ], - stdout: 'Serving DevTools at http://127.0.0.1:9100\n', + stdout: 'Serving DevTools at http://127.0.0.1:9100.\n', ), ]); final DevtoolsLauncher launcher = DevtoolsServerLauncher( - dartExecutable: 'dart', + artifacts: artifacts, logger: logger, botDetector: const FakeBotDetector(false), processManager: processManager, @@ -103,14 +140,15 @@ void main() { }); testWithoutContext('DevtoolsLauncher prints error if exception is thrown during launch', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); final DevtoolsLauncher launcher = DevtoolsServerLauncher( - dartExecutable: 'dart', + artifacts: artifacts, logger: logger, botDetector: const FakeBotDetector(false), processManager: FakeProcessManager.list([ const FakeCommand( command: [ - 'dart', + 'Artifact.engineDartBinary', 'devtools', '--no-launch-browser', '--vm-uri=http://127.0.0.1:1234/abcdefg', @@ -126,19 +164,20 @@ void main() { }); testWithoutContext('DevtoolsLauncher handles failure of DevTools process on a bot', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); final Completer completer = Completer(); final DevtoolsServerLauncher launcher = DevtoolsServerLauncher( - dartExecutable: 'dart', + artifacts: artifacts, logger: logger, botDetector: const FakeBotDetector(true), processManager: FakeProcessManager.list([ FakeCommand( command: const [ - 'dart', + 'Artifact.engineDartBinary', 'devtools', '--no-launch-browser', ], - stdout: 'Serving DevTools at http://127.0.0.1:9100\n', + stdout: 'Serving DevTools at http://127.0.0.1:9100.\n', completer: completer, exitCode: 255, ), diff --git a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart index f0bdc98b312..c2c114cbb92 100644 --- a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart +++ b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart @@ -28,7 +28,6 @@ import '../../src/context.dart'; import '../../src/fake_vm_services.dart'; import '../../src/fakes.dart'; - final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( id: '1', pauseEvent: vm_service.Event( @@ -565,22 +564,3 @@ class FakeDartDevelopmentService extends Fake implements DartDevelopmentService disposed = true; } } - -class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher { - bool closed = false; - final Completer _processStarted = Completer(); - - @override - Future launch(Uri vmServiceUri, {List? additionalArguments}) { - _processStarted.complete(); - return Completer().future; - } - - @override - Future get processStart => _processStarted.future; - - @override - Future close() async { - closed = true; - } -} diff --git a/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart b/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart index 8e07a195760..fb2c7242401 100644 --- a/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; @@ -61,6 +62,8 @@ final FakeVmServiceRequest listViews = FakeVmServiceRequest( void main() { Cache.flutterRoot = ''; + (BufferLogger, Artifacts) getTestState() => (BufferLogger.test(), Artifacts.test()); + testWithoutContext('Does not serve devtools if launcher is null', () async { final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler( null, @@ -86,10 +89,11 @@ void main() { }); testWithoutContext('Can use devtools with existing devtools URI', () async { + final (BufferLogger logger, Artifacts artifacts) = getTestState(); final DevtoolsServerLauncher launcher = DevtoolsServerLauncher( processManager: FakeProcessManager.empty(), - dartExecutable: 'dart', - logger: BufferLogger.test(), + artifacts: artifacts, + logger: logger, botDetector: const FakeBotDetector(false), ); final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler( @@ -429,22 +433,6 @@ void main() { }); } -class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher { - @override - DevToolsServerAddress? activeDevToolsServer; - - @override - Uri? devToolsUrl; - - @override - Future serve() async => null; - - @override - Future get ready => readyCompleter.future; - - Completer readyCompleter = Completer()..complete(); -} - class FakeResidentRunner extends Fake implements ResidentRunner { @override bool supportsServiceProtocol = true; diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart index 538a9c76c48..f90440c6361 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_runner_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/reporting/reporting.dart'; +import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:flutter_tools/src/version.dart'; @@ -175,116 +176,134 @@ void main() { Analytics: () => fakeAnalytics, }); - testUsingContext("Doesn't crash on invalid .packages file", () async { - final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; - fileSystem.file('pubspec.yaml').createSync(); - fileSystem.file('.packages') - ..createSync() - ..writeAsStringSync('Not a valid package'); + group('${FlutterGlobalOptions.kPrintDtd} flag', () { + testUsingContext('sets DevtoolsLauncher.printDtdUri to false when not present', () async { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + await runner.run([]); + expect(DevtoolsLauncher.instance!.printDtdUri, false); + }, overrides: { + DevtoolsLauncher: () => FakeDevtoolsLauncher()..dtdUri = Uri(), + }); - await runner.run(['dummy']); - - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - Platform: () => platform, - OutputPreferences: () => OutputPreferences.test(), - }); - - group('getRepoPackages', () { - late String? oldFlutterRoot; - - setUp(() { - oldFlutterRoot = Cache.flutterRoot; - Cache.flutterRoot = _kFlutterRoot; - fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'examples')) - .createSync(recursive: true); - fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'packages')) - .createSync(recursive: true); - fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')) - .createSync(recursive: true); - - fileSystem.file(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'pubspec.yaml')) - .createSync(); - fileSystem.file(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool', 'pubspec.yaml')) - .createSync(); + testUsingContext('sets DevtoolsLauncher.printDtdUri to true when present', () async { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + await runner.run(['--${FlutterGlobalOptions.kPrintDtd}']); + expect(DevtoolsLauncher.instance!.printDtdUri, true); + }, overrides: { + DevtoolsLauncher: () => FakeDevtoolsLauncher()..dtdUri = Uri(), + }); }); - tearDown(() { - Cache.flutterRoot = oldFlutterRoot; - }); - - testUsingContext('', () { + testUsingContext("Doesn't crash on invalid .packages file", () async { final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; - final List packagePaths = runner.getRepoPackages() - .map((Directory d) => d.path).toList(); - expect(packagePaths, [ - fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')).path, - fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools')).path, - ]); + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('.packages') + ..createSync() + ..writeAsStringSync('Not a valid package'); + + await runner.run(['dummy']); + }, overrides: { FileSystem: () => fileSystem, ProcessManager: () => FakeProcessManager.any(), Platform: () => platform, - FlutterVersion: () => FakeFlutterVersion(), OutputPreferences: () => OutputPreferences.test(), }); + + group('getRepoPackages', () { + late String? oldFlutterRoot; + + setUp(() { + oldFlutterRoot = Cache.flutterRoot; + Cache.flutterRoot = _kFlutterRoot; + fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'examples')) + .createSync(recursive: true); + fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'packages')) + .createSync(recursive: true); + fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')) + .createSync(recursive: true); + + fileSystem.file(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'pubspec.yaml')) + .createSync(); + fileSystem.file(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool', 'pubspec.yaml')) + .createSync(); + }); + + tearDown(() { + Cache.flutterRoot = oldFlutterRoot; + }); + + testUsingContext('', () { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + final List packagePaths = runner.getRepoPackages() + .map((Directory d) => d.path).toList(); + expect(packagePaths, [ + fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')).path, + fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools')).path, + ]); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + Platform: () => platform, + FlutterVersion: () => FakeFlutterVersion(), + OutputPreferences: () => OutputPreferences.test(), + }); + }); + + group('wrapping', () { + testUsingContext('checks that output wrapping is turned on when writing to a terminal', () async { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); + runner.addCommand(fakeCommand); + await runner.run(['fake']); + expect(fakeCommand.preferences.wrapText, isTrue); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + Stdio: () => FakeStdio(hasFakeTerminal: true), + OutputPreferences: () => OutputPreferences.test(), + }, initializeFlutterRoot: false); + + testUsingContext('checks that output wrapping is turned off when not writing to a terminal', () async { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); + runner.addCommand(fakeCommand); + await runner.run(['fake']); + expect(fakeCommand.preferences.wrapText, isFalse); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + Stdio: () => FakeStdio(hasFakeTerminal: false), + OutputPreferences: () => OutputPreferences.test(), + }, initializeFlutterRoot: false); + + testUsingContext('checks that output wrapping is turned off when set on the command line and writing to a terminal', () async { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); + runner.addCommand(fakeCommand); + await runner.run(['--no-wrap', 'fake']); + expect(fakeCommand.preferences.wrapText, isFalse); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + Stdio: () => FakeStdio(hasFakeTerminal: true), + OutputPreferences: () => OutputPreferences.test(), + }, initializeFlutterRoot: false); + + testUsingContext('checks that output wrapping is turned on when set on the command line, but not writing to a terminal', () async { + final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; + final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); + runner.addCommand(fakeCommand); + await runner.run(['--wrap', 'fake']); + expect(fakeCommand.preferences.wrapText, isTrue); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + Stdio: () => FakeStdio(hasFakeTerminal: false), + OutputPreferences: () => OutputPreferences.test(), + }, initializeFlutterRoot: false); + }); }); - - group('wrapping', () { - testUsingContext('checks that output wrapping is turned on when writing to a terminal', () async { - final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; - final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); - runner.addCommand(fakeCommand); - await runner.run(['fake']); - expect(fakeCommand.preferences.wrapText, isTrue); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - Stdio: () => FakeStdio(hasFakeTerminal: true), - OutputPreferences: () => OutputPreferences.test(), - }, initializeFlutterRoot: false); - - testUsingContext('checks that output wrapping is turned off when not writing to a terminal', () async { - final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; - final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); - runner.addCommand(fakeCommand); - await runner.run(['fake']); - expect(fakeCommand.preferences.wrapText, isFalse); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - Stdio: () => FakeStdio(hasFakeTerminal: false), - OutputPreferences: () => OutputPreferences.test(), - }, initializeFlutterRoot: false); - - testUsingContext('checks that output wrapping is turned off when set on the command line and writing to a terminal', () async { - final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; - final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); - runner.addCommand(fakeCommand); - await runner.run(['--no-wrap', 'fake']); - expect(fakeCommand.preferences.wrapText, isFalse); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - Stdio: () => FakeStdio(hasFakeTerminal: true), - OutputPreferences: () => OutputPreferences.test(), - }, initializeFlutterRoot: false); - - testUsingContext('checks that output wrapping is turned on when set on the command line, but not writing to a terminal', () async { - final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; - final FakeFlutterCommand fakeCommand = FakeFlutterCommand(); - runner.addCommand(fakeCommand); - await runner.run(['--wrap', 'fake']); - expect(fakeCommand.preferences.wrapText, isTrue); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - Stdio: () => FakeStdio(hasFakeTerminal: false), - OutputPreferences: () => OutputPreferences.test(), - }, initializeFlutterRoot: false); - }); - }); }); } diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index feddd15fba2..41257b92563 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -20,6 +20,7 @@ import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:test/fake.dart'; @@ -686,3 +687,48 @@ class FakeJava extends Fake implements Java { return _canRun; } } + +class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher { + FakeDevtoolsLauncher({DevToolsServerAddress? serverAddress}) + : _serverAddress = serverAddress; + + @override + Future get processStart => _processStarted.future; + + final Completer _processStarted = Completer(); + + @override + Future get ready => readyCompleter.future; + + Completer readyCompleter = Completer()..complete(); + + @override + DevToolsServerAddress? activeDevToolsServer; + + @override + Uri? devToolsUrl; + + @override + Uri? dtdUri; + + @override + bool printDtdUri = false; + + final DevToolsServerAddress? _serverAddress; + + @override + Future serve() async => _serverAddress; + + @override + Future launch(Uri vmServiceUri, {List? additionalArguments}) { + _processStarted.complete(); + return Completer().future; + } + + bool closed = false; + + @override + Future close() async { + closed = true; + } +}