Add a --print-dtd flag to print the DTD address served by DevTools server (#144272)

This commit is contained in:
Kenzie Davisson 2024-03-25 13:04:18 -07:00 committed by GitHub
parent dbdcead932
commit 31f4f2b6c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 326 additions and 191 deletions

View file

@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'runner.dart' as runner; import 'runner.dart' as runner;
import 'src/artifacts.dart';
import 'src/base/context.dart'; import 'src/base/context.dart';
import 'src/base/io.dart'; import 'src/base/io.dart';
import 'src/base/logger.dart'; import 'src/base/logger.dart';
@ -110,7 +109,7 @@ Future<void> main(List<String> args) async {
// devtools source code. // devtools source code.
DevtoolsLauncher: () => DevtoolsServerLauncher( DevtoolsLauncher: () => DevtoolsServerLauncher(
processManager: globals.processManager, processManager: globals.processManager,
dartExecutable: globals.artifacts!.getArtifactPath(Artifact.engineDartBinary), artifacts: globals.artifacts!,
logger: globals.logger, logger: globals.logger,
botDetector: globals.botDetector, botDetector: globals.botDetector,
), ),

View file

@ -218,7 +218,7 @@ Future<T> runInContext<T>(
), ),
DevtoolsLauncher: () => DevtoolsServerLauncher( DevtoolsLauncher: () => DevtoolsServerLauncher(
processManager: globals.processManager, processManager: globals.processManager,
dartExecutable: globals.artifacts!.getArtifactPath(Artifact.engineDartBinary), artifacts: globals.artifacts!,
logger: globals.logger, logger: globals.logger,
botDetector: globals.botDetector, botDetector: globals.botDetector,
), ),

View file

@ -2,13 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'artifacts.dart';
import 'base/bot_detector.dart'; import 'base/bot_detector.dart';
import 'base/common.dart'; import 'base/common.dart';
import 'base/io.dart' as io; import 'base/io.dart' as io;
@ -16,21 +15,22 @@ import 'base/logger.dart';
import 'convert.dart'; import 'convert.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
/// An implementation of the devtools launcher that uses `pub global activate` to /// An implementation of the devtools launcher that uses `dart devtools` to
/// start a server instance. /// start a DevTools server instance.
class DevtoolsServerLauncher extends DevtoolsLauncher { class DevtoolsServerLauncher extends DevtoolsLauncher {
DevtoolsServerLauncher({ DevtoolsServerLauncher({
required ProcessManager processManager, required ProcessManager processManager,
required String dartExecutable,
required Logger logger, required Logger logger,
required BotDetector botDetector, required BotDetector botDetector,
required Artifacts artifacts,
}) : _processManager = processManager, }) : _processManager = processManager,
_dartExecutable = dartExecutable,
_logger = logger, _logger = logger,
_botDetector = botDetector; _botDetector = botDetector,
_artifacts = artifacts;
final ProcessManager _processManager; final ProcessManager _processManager;
final String _dartExecutable; final Artifacts _artifacts;
late final String _dartExecutable = _artifacts.getArtifactPath(Artifact.engineDartBinary);
final Logger _logger; final Logger _logger;
final BotDetector _botDetector; final BotDetector _botDetector;
final Completer<void> _processStartCompleter = Completer<void>(); final Completer<void> _processStartCompleter = Completer<void>();
@ -42,6 +42,8 @@ class DevtoolsServerLauncher extends DevtoolsLauncher {
static final RegExp _serveDevToolsPattern = static final RegExp _serveDevToolsPattern =
RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+?)\.?$'); 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 @override
Future<void> get processStart => _processStartCompleter.future; Future<void> get processStart => _processStartCompleter.future;
@ -55,21 +57,28 @@ class DevtoolsServerLauncher extends DevtoolsLauncher {
_dartExecutable, _dartExecutable,
'devtools', 'devtools',
'--no-launch-browser', '--no-launch-browser',
if (printDtdUri) '--print-dtd',
if (vmServiceUri != null) '--vm-uri=$vmServiceUri', if (vmServiceUri != null) '--vm-uri=$vmServiceUri',
...?additionalArguments, ...?additionalArguments,
]); ]);
_processStartCompleter.complete(); _processStartCompleter.complete();
final Completer<Uri> completer = Completer<Uri>();
final Completer<Uri> devToolsCompleter = Completer<Uri>();
_devToolsProcess!.stdout _devToolsProcess!.stdout
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen((String line) { .listen((String line) {
final Match? match = _serveDevToolsPattern.firstMatch(line); final Match? dtdMatch = _serveDtdPattern.firstMatch(line);
if (match != null) { if (dtdMatch != null) {
final String url = match[1]!; final String uri = dtdMatch[1]!;
completer.complete(Uri.parse(url)); 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 _devToolsProcess!.stderr
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .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) { } on Exception catch (e, st) {
_logger.printError('Failed to launch DevTools: $e', stackTrace: st); _logger.printError('Failed to launch DevTools: $e', stackTrace: st);
} }

View file

@ -24,6 +24,19 @@ abstract class ResidentDevtoolsHandler {
/// The current devtools server, or null if one is not running. /// The current devtools server, or null if one is not running.
DevToolsServerAddress? get activeDevToolsServer; 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]. /// Whether it's ok to announce the [activeDevToolsServer].
/// ///
/// This should only return true once all the devices have been notified /// This should only return true once all the devices have been notified
@ -63,6 +76,12 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler {
return _devToolsLauncher?.activeDevToolsServer; return _devToolsLauncher?.activeDevToolsServer;
} }
@override
Uri? get dtdUri => _devToolsLauncher?.dtdUri;
@override
bool get printDtdUri => _devToolsLauncher?.printDtdUri ?? false;
@override @override
bool get readyToAnnounce => _readyToAnnounce; bool get readyToAnnounce => _readyToAnnounce;
bool _readyToAnnounce = false; bool _readyToAnnounce = false;
@ -337,6 +356,12 @@ class NoOpDevtoolsHandler implements ResidentDevtoolsHandler {
wasShutdown = true; wasShutdown = true;
return; return;
} }
@override
Uri? get dtdUri => null;
@override
bool get printDtdUri => false;
} }
/// Convert a [URI] with query parameters into a display format instead /// Convert a [URI] with query parameters into a display format instead

View file

@ -1526,6 +1526,12 @@ abstract class ResidentRunner extends ResidentHandlers {
); );
} }
if (includeDevtools) { 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( final Uri? uri = devToolsServerAddress!.uri?.replace(
queryParameters: <String, dynamic>{'uri': '${device.vmService!.httpAddress}'}, queryParameters: <String, dynamic>{'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. /// The URL of the current DevTools server.
/// ///
/// Returns null if [ready] is not complete. /// Returns null if [ready] is not complete.

View file

@ -17,6 +17,7 @@ import '../base/utils.dart';
import '../cache.dart'; import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../resident_runner.dart';
import '../tester/flutter_tester.dart'; import '../tester/flutter_tester.dart';
import '../version.dart'; import '../version.dart';
import '../web/web_device.dart'; import '../web/web_device.dart';
@ -35,6 +36,7 @@ abstract final class FlutterGlobalOptions {
static const String kMachineFlag = 'machine'; static const String kMachineFlag = 'machine';
static const String kPackagesOption = 'packages'; static const String kPackagesOption = 'packages';
static const String kPrefixedErrorsFlag = 'prefixed-errors'; static const String kPrefixedErrorsFlag = 'prefixed-errors';
static const String kPrintDtd = 'print-dtd';
static const String kQuietFlag = 'quiet'; static const String kQuietFlag = 'quiet';
static const String kShowTestDeviceFlag = 'show-test-device'; static const String kShowTestDeviceFlag = 'show-test-device';
static const String kShowWebServerDeviceFlag = 'show-web-server-device'; static const String kShowWebServerDeviceFlag = 'show-web-server-device';
@ -116,6 +118,12 @@ class FlutterCommandRunner extends CommandRunner<void> {
argParser.addOption(FlutterGlobalOptions.kPackagesOption, argParser.addOption(FlutterGlobalOptions.kPackagesOption,
hide: !verboseHelp, hide: !verboseHelp,
help: 'Path to your "package_config.json" file.'); 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) { if (verboseHelp) {
argParser.addSeparator('Local build selection options (not normally required):'); argParser.addSeparator('Local build selection options (not normally required):');
} }
@ -357,6 +365,10 @@ class FlutterCommandRunner extends CommandRunner<void> {
if (machineFlag && topLevelResults.command?.name != 'analyze') { if (machineFlag && topLevelResults.command?.name != 'analyze') {
throwToolExit('The "--machine" flag is only valid with the "--version" flag or the "analyze --suggestions" command.', exitCode: 2); 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); await super.runCommand(topLevelResults);
}, },
); );

View file

@ -824,7 +824,7 @@ void main() {
expect(result['host'], '127.0.0.1'); expect(result['host'], '127.0.0.1');
expect(result['port'], 1234); expect(result['port'], 1234);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
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 { testUsingContext('devtools.serve command should return null fields if null returned', () async {
@ -840,7 +840,7 @@ void main() {
expect(result['host'], null); expect(result['host'], null);
expect(result['port'], null); expect(result['port'], null);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
DevtoolsLauncher: () => FakeDevtoolsLauncher(null), DevtoolsLauncher: () => FakeDevtoolsLauncher(),
}); });
testUsingContext('proxy.connect tries to connect to an ipv4 address and proxies the connection correctly', () async { 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<DevToolsServerAddress?> serve() async => _serverAddress;
@override
Future<void> close() async {}
}
class FakeApplicationPackageFactory implements ApplicationPackageFactory { class FakeApplicationPackageFactory implements ApplicationPackageFactory {
TargetPlatform? platformRequested; TargetPlatform? platformRequested;
File? applicationBinaryRequested; File? applicationBinaryRequested;

View file

@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
@ -15,52 +16,87 @@ import '../src/fake_process_manager.dart';
import '../src/fakes.dart'; import '../src/fakes.dart';
void main() { void main() {
late BufferLogger logger;
Cache.flutterRoot = ''; Cache.flutterRoot = '';
setUp(() { (BufferLogger, Artifacts) getTestState() => (BufferLogger.test(), Artifacts.test());
logger = BufferLogger.test();
});
testWithoutContext('DevtoolsLauncher launches DevTools from the SDK and saves the URI', () async { testWithoutContext('DevtoolsLauncher launches DevTools from the SDK and saves the URI', () async {
final (BufferLogger logger, Artifacts artifacts) = getTestState();
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
dartExecutable: 'dart', artifacts: artifacts,
logger: logger, logger: logger,
botDetector: const FakeBotDetector(false), botDetector: const FakeBotDetector(false),
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'dart', 'Artifact.engineDartBinary',
'devtools', 'devtools',
'--no-launch-browser', '--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, completer: completer,
), ),
]), ]),
); );
expect(launcher.dtdUri, isNull);
expect(launcher.printDtdUri, false);
final DevToolsServerAddress? address = await launcher.serve(); final DevToolsServerAddress? address = await launcher.serve();
expect(address?.host, '127.0.0.1'); expect(address?.host, '127.0.0.1');
expect(address?.port, 9100); 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<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
dartExecutable: 'dart', artifacts: artifacts,
logger: logger, logger: logger,
botDetector: const FakeBotDetector(false), botDetector: const FakeBotDetector(false),
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'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<void> completer = Completer<void>();
final DevtoolsLauncher launcher = DevtoolsServerLauncher(
artifacts: artifacts,
logger: logger,
botDetector: const FakeBotDetector(false),
processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
'Artifact.engineDartBinary',
'devtools', 'devtools',
'--no-launch-browser', '--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, completer: completer,
), ),
]), ]),
@ -70,27 +106,28 @@ void main() {
expect(address?.host, '127.0.0.1'); expect(address?.host, '127.0.0.1');
expect(address?.port, 9100); 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(); address = await launcher.serve();
expect(address?.host, '127.0.0.1'); expect(address?.host, '127.0.0.1');
expect(address?.port, 9100); expect(address?.port, 9100);
}); });
testWithoutContext('DevtoolsLauncher can launch devtools with a memory profile', () async { testWithoutContext('DevtoolsLauncher can launch devtools with a memory profile', () async {
final (BufferLogger logger, Artifacts artifacts) = getTestState();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
'dart', 'Artifact.engineDartBinary',
'devtools', 'devtools',
'--no-launch-browser', '--no-launch-browser',
'--vm-uri=localhost:8181/abcdefg', '--vm-uri=localhost:8181/abcdefg',
'--profile-memory=foo', '--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( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
dartExecutable: 'dart', artifacts: artifacts,
logger: logger, logger: logger,
botDetector: const FakeBotDetector(false), botDetector: const FakeBotDetector(false),
processManager: processManager, processManager: processManager,
@ -103,14 +140,15 @@ void main() {
}); });
testWithoutContext('DevtoolsLauncher prints error if exception is thrown during launch', () async { testWithoutContext('DevtoolsLauncher prints error if exception is thrown during launch', () async {
final (BufferLogger logger, Artifacts artifacts) = getTestState();
final DevtoolsLauncher launcher = DevtoolsServerLauncher( final DevtoolsLauncher launcher = DevtoolsServerLauncher(
dartExecutable: 'dart', artifacts: artifacts,
logger: logger, logger: logger,
botDetector: const FakeBotDetector(false), botDetector: const FakeBotDetector(false),
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
command: <String>[ command: <String>[
'dart', 'Artifact.engineDartBinary',
'devtools', 'devtools',
'--no-launch-browser', '--no-launch-browser',
'--vm-uri=http://127.0.0.1:1234/abcdefg', '--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 { testWithoutContext('DevtoolsLauncher handles failure of DevTools process on a bot', () async {
final (BufferLogger logger, Artifacts artifacts) = getTestState();
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
final DevtoolsServerLauncher launcher = DevtoolsServerLauncher( final DevtoolsServerLauncher launcher = DevtoolsServerLauncher(
dartExecutable: 'dart', artifacts: artifacts,
logger: logger, logger: logger,
botDetector: const FakeBotDetector(true), botDetector: const FakeBotDetector(true),
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand( FakeCommand(
command: const <String>[ command: const <String>[
'dart', 'Artifact.engineDartBinary',
'devtools', 'devtools',
'--no-launch-browser', '--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, completer: completer,
exitCode: 255, exitCode: 255,
), ),

View file

@ -28,7 +28,6 @@ import '../../src/context.dart';
import '../../src/fake_vm_services.dart'; import '../../src/fake_vm_services.dart';
import '../../src/fakes.dart'; import '../../src/fakes.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1', id: '1',
pauseEvent: vm_service.Event( pauseEvent: vm_service.Event(
@ -565,22 +564,3 @@ class FakeDartDevelopmentService extends Fake implements DartDevelopmentService
disposed = true; disposed = true;
} }
} }
class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher {
bool closed = false;
final Completer<void> _processStarted = Completer<void>();
@override
Future<void> launch(Uri vmServiceUri, {List<String>? additionalArguments}) {
_processStarted.complete();
return Completer<void>().future;
}
@override
Future<void> get processStart => _processStarted.future;
@override
Future<void> close() async {
closed = true;
}
}

View file

@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/dds.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
@ -61,6 +62,8 @@ final FakeVmServiceRequest listViews = FakeVmServiceRequest(
void main() { void main() {
Cache.flutterRoot = ''; Cache.flutterRoot = '';
(BufferLogger, Artifacts) getTestState() => (BufferLogger.test(), Artifacts.test());
testWithoutContext('Does not serve devtools if launcher is null', () async { testWithoutContext('Does not serve devtools if launcher is null', () async {
final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler( final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler(
null, null,
@ -86,10 +89,11 @@ void main() {
}); });
testWithoutContext('Can use devtools with existing devtools URI', () async { testWithoutContext('Can use devtools with existing devtools URI', () async {
final (BufferLogger logger, Artifacts artifacts) = getTestState();
final DevtoolsServerLauncher launcher = DevtoolsServerLauncher( final DevtoolsServerLauncher launcher = DevtoolsServerLauncher(
processManager: FakeProcessManager.empty(), processManager: FakeProcessManager.empty(),
dartExecutable: 'dart', artifacts: artifacts,
logger: BufferLogger.test(), logger: logger,
botDetector: const FakeBotDetector(false), botDetector: const FakeBotDetector(false),
); );
final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler( final ResidentDevtoolsHandler handler = FlutterResidentDevtoolsHandler(
@ -429,22 +433,6 @@ void main() {
}); });
} }
class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher {
@override
DevToolsServerAddress? activeDevToolsServer;
@override
Uri? devToolsUrl;
@override
Future<DevToolsServerAddress?> serve() async => null;
@override
Future<void> get ready => readyCompleter.future;
Completer<void> readyCompleter = Completer<void>()..complete();
}
class FakeResidentRunner extends Fake implements ResidentRunner { class FakeResidentRunner extends Fake implements ResidentRunner {
@override @override
bool supportsServiceProtocol = true; bool supportsServiceProtocol = true;

View file

@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/reporting/reporting.dart'; 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.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/version.dart';
@ -175,116 +176,134 @@ void main() {
Analytics: () => fakeAnalytics, Analytics: () => fakeAnalytics,
}); });
testUsingContext("Doesn't crash on invalid .packages file", () async { group('${FlutterGlobalOptions.kPrintDtd} flag', () {
final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; testUsingContext('sets DevtoolsLauncher.printDtdUri to false when not present', () async {
fileSystem.file('pubspec.yaml').createSync(); final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner;
fileSystem.file('.packages') await runner.run(<String>[]);
..createSync() expect(DevtoolsLauncher.instance!.printDtdUri, false);
..writeAsStringSync('Not a valid package'); }, overrides: <Type, Generator>{
DevtoolsLauncher: () => FakeDevtoolsLauncher()..dtdUri = Uri(),
});
await runner.run(<String>['dummy']); testUsingContext('sets DevtoolsLauncher.printDtdUri to true when present', () async {
final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner;
}, overrides: <Type, Generator>{ await runner.run(<String>['--${FlutterGlobalOptions.kPrintDtd}']);
FileSystem: () => fileSystem, expect(DevtoolsLauncher.instance!.printDtdUri, true);
ProcessManager: () => FakeProcessManager.any(), }, overrides: <Type, Generator>{
Platform: () => platform, DevtoolsLauncher: () => FakeDevtoolsLauncher()..dtdUri = Uri(),
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(() { testUsingContext("Doesn't crash on invalid .packages file", () async {
Cache.flutterRoot = oldFlutterRoot;
});
testUsingContext('', () {
final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner; final FlutterCommandRunner runner = createTestCommandRunner(DummyFlutterCommand()) as FlutterCommandRunner;
final List<String> packagePaths = runner.getRepoPackages() fileSystem.file('pubspec.yaml').createSync();
.map((Directory d) => d.path).toList(); fileSystem.file('.packages')
expect(packagePaths, <String>[ ..createSync()
fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')).path, ..writeAsStringSync('Not a valid package');
fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools')).path,
]); await runner.run(<String>['dummy']);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
Platform: () => platform, Platform: () => platform,
FlutterVersion: () => FakeFlutterVersion(),
OutputPreferences: () => OutputPreferences.test(), 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<String> packagePaths = runner.getRepoPackages()
.map((Directory d) => d.path).toList();
expect(packagePaths, <String>[
fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools', 'aatool')).path,
fileSystem.directory(fileSystem.path.join(_kFlutterRoot, 'dev', 'tools')).path,
]);
}, overrides: <Type, Generator>{
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(<String>['fake']);
expect(fakeCommand.preferences.wrapText, isTrue);
}, overrides: <Type, Generator>{
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(<String>['fake']);
expect(fakeCommand.preferences.wrapText, isFalse);
}, overrides: <Type, Generator>{
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(<String>['--no-wrap', 'fake']);
expect(fakeCommand.preferences.wrapText, isFalse);
}, overrides: <Type, Generator>{
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(<String>['--wrap', 'fake']);
expect(fakeCommand.preferences.wrapText, isTrue);
}, overrides: <Type, Generator>{
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(<String>['fake']);
expect(fakeCommand.preferences.wrapText, isTrue);
}, overrides: <Type, Generator>{
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(<String>['fake']);
expect(fakeCommand.preferences.wrapText, isFalse);
}, overrides: <Type, Generator>{
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(<String>['--no-wrap', 'fake']);
expect(fakeCommand.preferences.wrapText, isFalse);
}, overrides: <Type, Generator>{
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(<String>['--wrap', 'fake']);
expect(fakeCommand.preferences.wrapText, isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Stdio: () => FakeStdio(hasFakeTerminal: false),
OutputPreferences: () => OutputPreferences.test(),
}, initializeFlutterRoot: false);
});
});
}); });
} }

View file

@ -20,6 +20,7 @@ import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart';
import 'package:flutter_tools/src/project.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:flutter_tools/src/version.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
@ -686,3 +687,48 @@ class FakeJava extends Fake implements Java {
return _canRun; return _canRun;
} }
} }
class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher {
FakeDevtoolsLauncher({DevToolsServerAddress? serverAddress})
: _serverAddress = serverAddress;
@override
Future<void> get processStart => _processStarted.future;
final Completer<void> _processStarted = Completer<void>();
@override
Future<void> get ready => readyCompleter.future;
Completer<void> readyCompleter = Completer<void>()..complete();
@override
DevToolsServerAddress? activeDevToolsServer;
@override
Uri? devToolsUrl;
@override
Uri? dtdUri;
@override
bool printDtdUri = false;
final DevToolsServerAddress? _serverAddress;
@override
Future<DevToolsServerAddress?> serve() async => _serverAddress;
@override
Future<void> launch(Uri vmServiceUri, {List<String>? additionalArguments}) {
_processStarted.complete();
return Completer<void>().future;
}
bool closed = false;
@override
Future<void> close() async {
closed = true;
}
}