diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 2c6499c1469da..1cb415cd7fed2 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -142,6 +142,10 @@ Future runInContext( config: globals.config, fuchsiaWorkflow: fuchsiaWorkflow, xcDevice: globals.xcdevice, + macOSWorkflow: MacOSWorkflow( + platform: globals.platform, + featureFlags: featureFlags, + ), ), Doctor: () => Doctor(logger: globals.logger), DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, @@ -193,7 +197,10 @@ Future runInContext( outputPreferences: globals.outputPreferences, timeoutConfiguration: timeoutConfiguration, ), - MacOSWorkflow: () => const MacOSWorkflow(), + MacOSWorkflow: () => MacOSWorkflow( + featureFlags: featureFlags, + platform: globals.platform, + ), MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(), OperatingSystemUtils: () => OperatingSystemUtils( fileSystem: globals.fs, diff --git a/packages/flutter_tools/lib/src/desktop_device.dart b/packages/flutter_tools/lib/src/desktop_device.dart index 937bfcd61efed..26561e5c5393d 100644 --- a/packages/flutter_tools/lib/src/desktop_device.dart +++ b/packages/flutter_tools/lib/src/desktop_device.dart @@ -5,10 +5,12 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:process/process.dart'; import 'application_package.dart'; import 'base/common.dart'; import 'base/io.dart'; +import 'base/logger.dart'; import 'build_info.dart'; import 'convert.dart'; import 'device.dart'; @@ -18,15 +20,23 @@ import 'protocol_discovery.dart'; /// A partial implementation of Device for desktop-class devices to inherit /// from, containing implementations that are common to all desktop devices. abstract class DesktopDevice extends Device { - DesktopDevice(String identifier, {@required PlatformType platformType, @required bool ephemeral}) : super( - identifier, - category: Category.desktop, - platformType: platformType, - ephemeral: ephemeral, - ); - + DesktopDevice(String identifier, { + @required PlatformType platformType, + @required bool ephemeral, + Logger logger, + ProcessManager processManager, + }) : _logger = logger ?? globals.logger, // TODO(jonahwilliams): remove after updating google3 + _processManager = processManager ?? globals.processManager, + super( + identifier, + category: Category.desktop, + platformType: platformType, + ephemeral: ephemeral, + ); + + final Logger _logger; + final ProcessManager _processManager; final Set _runningProcesses = {}; - final DesktopLogReader _deviceLogReader = DesktopLogReader(); // Since the host and target devices are the same, no work needs to be done @@ -108,20 +118,20 @@ abstract class DesktopDevice extends Device { final BuildMode buildMode = debuggingOptions?.buildInfo?.mode; final String executable = executablePathForDevice(package, buildMode); if (executable == null) { - globals.printError('Unable to find executable to run'); + _logger.printError('Unable to find executable to run'); return LaunchResult.failed(); } - final Process process = await globals.processManager.start([ + final Process process = await _processManager.start([ executable, ]); _runningProcesses.add(process); unawaited(process.exitCode.then((_) => _runningProcesses.remove(process))); + _deviceLogReader.initializeProcess(process); if (debuggingOptions?.buildInfo?.isRelease == true) { return LaunchResult.succeeded(); } - _deviceLogReader.initializeProcess(process); final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(_deviceLogReader, devicePort: debuggingOptions?.deviceVmServicePort, hostPort: debuggingOptions?.hostVmServicePort, @@ -133,12 +143,12 @@ abstract class DesktopDevice extends Device { onAttached(package, buildMode, process); return LaunchResult.succeeded(observatoryUri: observatoryUri); } - globals.printError( + _logger.printError( 'Error waiting for a debug connection: ' 'The log reader stopped unexpectedly.', ); } on Exception catch (error) { - globals.printError('Error waiting for a debug connection: $error'); + _logger.printError('Error waiting for a debug connection: $error'); } finally { await observatoryDiscovery.cancel(); } @@ -186,9 +196,7 @@ class DesktopLogReader extends DeviceLogReader { void initializeProcess(Process process) { process.stdout.listen(_inputController.add); process.stderr.listen(_inputController.add); - process.exitCode.then((int result) { - _inputController.close(); - }); + process.exitCode.whenComplete(_inputController.close); } @override diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 2c7259910d698..1130db61f305a 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -33,6 +33,7 @@ import 'ios/ios_workflow.dart'; import 'ios/simulators.dart'; import 'linux/linux_device.dart'; import 'macos/macos_device.dart'; +import 'macos/macos_workflow.dart'; import 'macos/xcode.dart'; import 'project.dart'; import 'tester/flutter_tester.dart'; @@ -292,6 +293,7 @@ class FlutterDeviceManager extends DeviceManager { @required FlutterVersion flutterVersion, @required Config config, @required Artifacts artifacts, + @required MacOSWorkflow macOSWorkflow, }) : deviceDiscoverers = [ AndroidDevices( logger: logger, @@ -322,10 +324,17 @@ class FlutterDeviceManager extends DeviceManager { logger: logger, artifacts: artifacts, ), - MacOSDevices(), + MacOSDevices( + processManager: processManager, + macOSWorkflow: macOSWorkflow, + logger: logger, + platform: platform, + ), LinuxDevices( platform: platform, featureFlags: featureFlags, + processManager: processManager, + logger: logger, ), WindowsDevices(), WebDevices( diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 389068e1eb9ff..8c404205c077a 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -65,6 +65,11 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { featureFlags: featureFlags, ); + final MacOSWorkflow macOSWorkflow = MacOSWorkflow( + platform: globals.platform, + featureFlags: featureFlags, + ); + @override List get validators { if (_validators != null) { diff --git a/packages/flutter_tools/lib/src/linux/linux_device.dart b/packages/flutter_tools/lib/src/linux/linux_device.dart index 2cc4295c53c8e..8916f46018d67 100644 --- a/packages/flutter_tools/lib/src/linux/linux_device.dart +++ b/packages/flutter_tools/lib/src/linux/linux_device.dart @@ -3,12 +3,15 @@ // found in the LICENSE file. import 'package:meta/meta.dart'; +import 'package:process/process.dart'; +import '../base/logger.dart'; import '../base/platform.dart'; import '../build_info.dart'; import '../desktop_device.dart'; import '../device.dart'; import '../features.dart'; +import '../globals.dart' as globals; import '../project.dart'; import 'application_package.dart'; import 'build_linux.dart'; @@ -16,10 +19,15 @@ import 'linux_workflow.dart'; /// A device that represents a desktop Linux target. class LinuxDevice extends DesktopDevice { - LinuxDevice() : super( + LinuxDevice({ + @required ProcessManager processManager, + @required Logger logger, + }) : super( 'linux', platformType: PlatformType.linux, ephemeral: false, + logger: logger, + processManager: processManager, ); @override @@ -59,15 +67,21 @@ class LinuxDevices extends PollingDeviceDiscovery { LinuxDevices({ @required Platform platform, @required FeatureFlags featureFlags, - }) : _platform = platform, + ProcessManager processManager, + Logger logger, + }) : _platform = platform ?? globals.platform, // TODO(jonahwilliams): remove after google3 roll _linuxWorkflow = LinuxWorkflow( platform: platform, featureFlags: featureFlags, ), + _logger = logger, + _processManager = processManager ?? globals.processManager, super('linux devices'); final Platform _platform; final LinuxWorkflow _linuxWorkflow; + final ProcessManager _processManager; + final Logger _logger; @override bool get supportsPlatform => _platform.isLinux; @@ -81,7 +95,10 @@ class LinuxDevices extends PollingDeviceDiscovery { return const []; } return [ - LinuxDevice(), + LinuxDevice( + logger: _logger, + processManager: _processManager, + ), ]; } diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index 1d7ef01502b55..f92b7f36248a3 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/platform.dart'; import '../build_info.dart'; import '../desktop_device.dart'; import '../device.dart'; -import '../globals.dart' as globals; import '../macos/application_package.dart'; import '../project.dart'; import 'build_macos.dart'; @@ -14,11 +18,21 @@ import 'macos_workflow.dart'; /// A device that represents a desktop MacOS target. class MacOSDevice extends DesktopDevice { - MacOSDevice() : super( - 'macos', - platformType: PlatformType.macos, - ephemeral: false, - ); + MacOSDevice({ + @required ProcessManager processManager, + @required Logger logger, + }) : _processManager = processManager, + _logger = logger, + super( + 'macos', + platformType: PlatformType.macos, + ephemeral: false, + processManager: processManager, + logger: logger, + ); + + final ProcessManager _processManager; + final Logger _logger; @override bool isSupported() => true; @@ -44,7 +58,7 @@ class MacOSDevice extends DesktopDevice { flutterProject: FlutterProject.current(), buildInfo: buildInfo, targetOverride: mainPath, - verboseLogging: globals.logger.isVerbose, + verboseLogging: _logger.isVerbose, ); } @@ -59,7 +73,7 @@ class MacOSDevice extends DesktopDevice { // than post-attach, since this won't run for release builds, but there's // no general-purpose way of knowing when a process is far enoug along in // the launch process for 'open' to foreground it. - globals.processManager.run([ + _processManager.run([ 'open', package.applicationBundle(buildMode), ]).then((ProcessResult result) { if (result.exitCode != 0) { @@ -70,13 +84,27 @@ class MacOSDevice extends DesktopDevice { } class MacOSDevices extends PollingDeviceDiscovery { - MacOSDevices() : super('macOS devices'); + MacOSDevices({ + @required Platform platform, + @required MacOSWorkflow macOSWorkflow, + @required ProcessManager processManager, + @required Logger logger, + }) : _logger = logger, + _platform = platform, + _macOSWorkflow = macOSWorkflow, + _processManager = processManager, + super('macOS devices'); + + final MacOSWorkflow _macOSWorkflow; + final Platform _platform; + final ProcessManager _processManager; + final Logger _logger; @override - bool get supportsPlatform => globals.platform.isMacOS; + bool get supportsPlatform => _platform.isMacOS; @override - bool get canListAnything => macOSWorkflow.canListDevices; + bool get canListAnything => _macOSWorkflow.canListDevices; @override Future> pollingGetDevices({ Duration timeout }) async { @@ -84,7 +112,7 @@ class MacOSDevices extends PollingDeviceDiscovery { return const []; } return [ - MacOSDevice(), + MacOSDevice(processManager: _processManager, logger: _logger), ]; } diff --git a/packages/flutter_tools/lib/src/macos/macos_workflow.dart b/packages/flutter_tools/lib/src/macos/macos_workflow.dart index 0ae1ae4c22672..b91c68c5ff862 100644 --- a/packages/flutter_tools/lib/src/macos/macos_workflow.dart +++ b/packages/flutter_tools/lib/src/macos/macos_workflow.dart @@ -2,29 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../base/context.dart'; +import 'package:meta/meta.dart'; + +import '../base/platform.dart'; import '../doctor.dart'; import '../features.dart'; -import '../globals.dart' as globals; -/// The [MacOSWorkflow] instance. -MacOSWorkflow get macOSWorkflow => context.get(); /// The macOS-specific implementation of a [Workflow]. /// /// This workflow requires the flutter-desktop-embedding as a sibling /// repository to the flutter repo. class MacOSWorkflow implements Workflow { - const MacOSWorkflow(); + const MacOSWorkflow({ + @required Platform platform, + @required FeatureFlags featureFlags, + }) : _platform = platform, + _featureFlags = featureFlags; + + final Platform _platform; + final FeatureFlags _featureFlags; @override - bool get appliesToHostPlatform => globals.platform.isMacOS && featureFlags.isMacOSEnabled; + bool get appliesToHostPlatform => _platform.isMacOS && _featureFlags.isMacOSEnabled; @override - bool get canLaunchDevices => globals.platform.isMacOS && featureFlags.isMacOSEnabled; + bool get canLaunchDevices => _platform.isMacOS && _featureFlags.isMacOSEnabled; @override - bool get canListDevices => globals.platform.isMacOS && featureFlags.isMacOSEnabled; + bool get canListDevices => _platform.isMacOS && _featureFlags.isMacOSEnabled; @override bool get canListEmulators => false; diff --git a/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart b/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart index 72ec9f7985e6e..5a8e458c96da8 100644 --- a/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart +++ b/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart @@ -4,6 +4,7 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; @@ -17,15 +18,21 @@ import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/testbed.dart'; -void main() { - final LinuxDevice device = LinuxDevice(); - final MockPlatform notLinux = MockPlatform(); - when(notLinux.isLinux).thenReturn(false); +final FakePlatform linux = FakePlatform( + operatingSystem: 'linux', +); +final FakePlatform windows = FakePlatform( + operatingSystem: 'windows', +); - final MockPlatform mockLinuxPlatform = MockPlatform(); - when(mockLinuxPlatform.isLinux).thenReturn(true); +void main() { testWithoutContext('LinuxDevice defaults', () async { + final LinuxDevice device = LinuxDevice( + processManager: FakeProcessManager.any(), + logger: BufferLogger.test(), + ); + final PrebuiltLinuxApp linuxApp = PrebuiltLinuxApp(executable: 'foo'); expect(await device.targetPlatform, TargetPlatform.linux_x64); expect(device.name, 'Linux'); @@ -44,30 +51,38 @@ void main() { testWithoutContext('LinuxDevice: no devices listed if platform unsupported', () async { expect(await LinuxDevices( - platform: notLinux, + platform: windows, featureFlags: TestFeatureFlags(isLinuxEnabled: true), + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), ).devices, []); }); testWithoutContext('LinuxDevice: no devices listed if Linux feature flag disabled', () async { expect(await LinuxDevices( - platform: mockLinuxPlatform, + platform: linux, featureFlags: TestFeatureFlags(isLinuxEnabled: false), + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), ).devices, []); }); testWithoutContext('LinuxDevice: devices', () async { expect(await LinuxDevices( - platform: mockLinuxPlatform, + platform: linux, featureFlags: TestFeatureFlags(isLinuxEnabled: true), + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), ).devices, hasLength(1)); }); testWithoutContext('LinuxDevice: discoverDevices', () async { // Timeout ignored. final List devices = await LinuxDevices( - platform: mockLinuxPlatform, + platform: linux, featureFlags: TestFeatureFlags(isLinuxEnabled: true), + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), ).discoverDevices(timeout: const Duration(seconds: 10)); expect(devices, hasLength(1)); }); @@ -78,7 +93,10 @@ void main() { globals.fs.directory('linux').createSync(); final FlutterProject flutterProject = FlutterProject.current(); - expect(LinuxDevice().isSupportedForProject(flutterProject), true); + expect(LinuxDevice( + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ).isSupportedForProject(flutterProject), true); }, overrides: { FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), @@ -89,7 +107,10 @@ void main() { globals.fs.file('.packages').createSync(); final FlutterProject flutterProject = FlutterProject.current(); - expect(LinuxDevice().isSupportedForProject(flutterProject), false); + expect(LinuxDevice( + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ).isSupportedForProject(flutterProject), false); }, overrides: { FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), @@ -97,6 +118,10 @@ void main() { testUsingContext('LinuxDevice.executablePathForDevice uses the correct package executable', () async { final MockLinuxApp mockApp = MockLinuxApp(); + final LinuxDevice device = LinuxDevice( + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ); const String debugPath = 'debug/executable'; const String profilePath = 'profile/executable'; const String releasePath = 'release/executable'; @@ -104,15 +129,13 @@ void main() { when(mockApp.executable(BuildMode.profile)).thenReturn(profilePath); when(mockApp.executable(BuildMode.release)).thenReturn(releasePath); - expect(LinuxDevice().executablePathForDevice(mockApp, BuildMode.debug), debugPath); - expect(LinuxDevice().executablePathForDevice(mockApp, BuildMode.profile), profilePath); - expect(LinuxDevice().executablePathForDevice(mockApp, BuildMode.release), releasePath); + expect(device.executablePathForDevice(mockApp, BuildMode.debug), debugPath); + expect(device.executablePathForDevice(mockApp, BuildMode.profile), profilePath); + expect(device.executablePathForDevice(mockApp, BuildMode.release), releasePath); }, overrides: { FileSystem: () => MemoryFileSystem(), ProcessManager: () => FakeProcessManager.any(), }); } -class MockPlatform extends Mock implements Platform {} - class MockLinuxApp extends Mock implements LinuxApp {} diff --git a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart index 728a596d5e74c..0b0b66eccc7dc 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart @@ -2,17 +2,19 @@ // 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:file/memory.dart'; import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; -import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/macos/application_package.dart'; import 'package:flutter_tools/src/macos/macos_device.dart'; +import 'package:flutter_tools/src/macos/macos_workflow.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; @@ -21,104 +23,178 @@ import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/testbed.dart'; +final FakePlatform macOS = FakePlatform( + operatingSystem: 'macos', +); + +final FakePlatform linux = FakePlatform( + operatingSystem: 'linux', +); + void main() { - group(MacOSDevice, () { - final MacOSDevice device = MacOSDevice(); - final MockProcessManager mockProcessManager = MockProcessManager(); - - final MockPlatform notMac = MockPlatform(); - when(notMac.isMacOS).thenReturn(false); - when(notMac.environment).thenReturn(const {}); - - final MockPlatform mockMacPlatform = MockPlatform(); - when(mockMacPlatform.isMacOS).thenReturn(true); - - when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { - return ProcessResult(0, 1, '', ''); - }); - - testUsingContext('defaults', () async { - final MockMacOSApp mockMacOSApp = MockMacOSApp(); - expect(await device.targetPlatform, TargetPlatform.darwin_x64); - expect(device.name, 'macOS'); - expect(await device.installApp(mockMacOSApp), true); - expect(await device.uninstallApp(mockMacOSApp), true); - expect(await device.isLatestBuildInstalled(mockMacOSApp), true); - expect(await device.isAppInstalled(mockMacOSApp), true); - expect(device.category, Category.desktop); - - expect(device.supportsRuntimeMode(BuildMode.debug), true); - expect(device.supportsRuntimeMode(BuildMode.profile), true); - expect(device.supportsRuntimeMode(BuildMode.release), true); - expect(device.supportsRuntimeMode(BuildMode.jitRelease), false); - }); - - testUsingContext('No devices listed if platform unsupported', () async { - expect(await MacOSDevices().devices, []); - }, overrides: { - Platform: () => notMac, - }); - - testUsingContext('devices', () async { - expect(await MacOSDevices().devices, hasLength(1)); - }, overrides: { - Platform: () => mockMacPlatform, - FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), - }); - - testUsingContext('discoverDevices', () async { - // Timeout ignored. - final List devices = await MacOSDevices().discoverDevices(timeout: const Duration(seconds: 10)); - expect(devices, hasLength(1)); - }, overrides: { - Platform: () => mockMacPlatform, - FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), - }); - - testUsingContext('isSupportedForProject is true with editable host app', () async { - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - globals.fs.directory('macos').createSync(); - final FlutterProject flutterProject = FlutterProject.current(); - - expect(MacOSDevice().isSupportedForProject(flutterProject), true); - }, overrides: { - FileSystem: () => MemoryFileSystem(), - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('isSupportedForProject is false with no host app', () async { - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - final FlutterProject flutterProject = FlutterProject.current(); - - expect(MacOSDevice().isSupportedForProject(flutterProject), false); - }, overrides: { - FileSystem: () => MemoryFileSystem(), - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('executablePathForDevice uses the correct package executable', () async { - final MockMacOSApp mockApp = MockMacOSApp(); - const String debugPath = 'debug/executable'; - const String profilePath = 'profile/executable'; - const String releasePath = 'release/executable'; - when(mockApp.executable(BuildMode.debug)).thenReturn(debugPath); - when(mockApp.executable(BuildMode.profile)).thenReturn(profilePath); - when(mockApp.executable(BuildMode.release)).thenReturn(releasePath); - - expect(MacOSDevice().executablePathForDevice(mockApp, BuildMode.debug), debugPath); - expect(MacOSDevice().executablePathForDevice(mockApp, BuildMode.profile), profilePath); - expect(MacOSDevice().executablePathForDevice(mockApp, BuildMode.release), releasePath); - }, overrides: { - FileSystem: () => MemoryFileSystem(), - ProcessManager: () => FakeProcessManager.any(), - }); + testWithoutContext('default configuration', () async { + final MacOSDevice device = MacOSDevice( + processManager: FakeProcessManager.any(), + logger: BufferLogger.test(), + ); + final MockMacOSApp mockMacOSApp = MockMacOSApp(); + + expect(await device.targetPlatform, TargetPlatform.darwin_x64); + expect(device.name, 'macOS'); + expect(await device.installApp(mockMacOSApp), true); + expect(await device.uninstallApp(mockMacOSApp), true); + expect(await device.isLatestBuildInstalled(mockMacOSApp), true); + expect(await device.isAppInstalled(mockMacOSApp), true); + expect(device.category, Category.desktop); + + expect(device.supportsRuntimeMode(BuildMode.debug), true); + expect(device.supportsRuntimeMode(BuildMode.profile), true); + expect(device.supportsRuntimeMode(BuildMode.release), true); + expect(device.supportsRuntimeMode(BuildMode.jitRelease), false); }); -} -class MockPlatform extends Mock implements Platform {} + testUsingContext('Attaches to log reader when running in release mode', () async { + final Completer completer = Completer(); + final MacOSDevice device = MacOSDevice( + processManager: FakeProcessManager.list([ + FakeCommand( + command: const ['Example.app'], + stdout: 'Hello World', + stderr: 'Goodnight, Moon', + completer: completer, + ) + ]), + logger: BufferLogger.test(), + ); + final MockMacOSApp mockMacOSApp = MockMacOSApp(); + when(mockMacOSApp.executable(BuildMode.release)).thenReturn('Example.app'); + + final LaunchResult result = await device.startApp( + mockMacOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + prebuiltApplication: true, + ); + + expect(result.started, true); + + final DeviceLogReader logReader = device.getLogReader(app: mockMacOSApp); + + expect(logReader.logLines, emits('Hello WorldGoodnight, Moon')); + completer.complete(); + }, overrides: { + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); -class MockMacOSApp extends Mock implements MacOSApp {} + testWithoutContext('No devices listed if platform is unsupported', () async { + expect(await MacOSDevices( + processManager: FakeProcessManager.any(), + logger: BufferLogger.test(), + platform: linux, + macOSWorkflow: MacOSWorkflow( + featureFlags: TestFeatureFlags(isMacOSEnabled: true), + platform: linux, + ), + ).devices, isEmpty); + }); + + testWithoutContext('No devices listed if platform is supported and feature is disabled', () async { + final MacOSDevices macOSDevices = MacOSDevices( + processManager: FakeProcessManager.any(), + logger: BufferLogger.test(), + platform: macOS, + macOSWorkflow: MacOSWorkflow( + featureFlags: TestFeatureFlags(isMacOSEnabled: false), + platform: macOS, + ), + ); + + expect(await macOSDevices.devices, isEmpty); + }); + + testWithoutContext('devices listed if platform is supported and feature is enabled', () async { + final MacOSDevices macOSDevices = MacOSDevices( + processManager: FakeProcessManager.any(), + logger: BufferLogger.test(), + platform: macOS, + macOSWorkflow: MacOSWorkflow( + featureFlags: TestFeatureFlags(isMacOSEnabled: true), + platform: macOS, + ), + ); + + expect(await macOSDevices.devices, hasLength(1)); + }); -class MockProcessManager extends Mock implements ProcessManager {} + testWithoutContext('can discover devices with a provided timeout', () async { + final MacOSDevices macOSDevices = MacOSDevices( + processManager: FakeProcessManager.any(), + logger: BufferLogger.test(), + platform: macOS, + macOSWorkflow: MacOSWorkflow( + featureFlags: TestFeatureFlags(isMacOSEnabled: true), + platform: macOS, + ), + ); + + // Timeout ignored. + final List devices = await macOSDevices.discoverDevices(timeout: const Duration(seconds: 10)); + + expect(devices, hasLength(1)); + }); + + testUsingContext('isSupportedForProject is true with editable host app', () async { + final MacOSDevice device = MacOSDevice( + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ); + + globals.fs.file('pubspec.yaml').createSync(); + globals.fs.file('.packages').createSync(); + globals.fs.directory('macos').createSync(); + final FlutterProject flutterProject = FlutterProject.current(); + + expect(device.isSupportedForProject(flutterProject), true); + }, overrides: { + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('isSupportedForProject is false with no host app', () async { + final MacOSDevice device = MacOSDevice( + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ); + globals.fs.file('pubspec.yaml').createSync(); + globals.fs.file('.packages').createSync(); + final FlutterProject flutterProject = FlutterProject.current(); + + expect(device.isSupportedForProject(flutterProject), false); + }, overrides: { + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('executablePathForDevice uses the correct package executable', () async { + final MockMacOSApp mockApp = MockMacOSApp(); + final MacOSDevice device = MacOSDevice( + logger: BufferLogger.test(), + processManager: FakeProcessManager.any(), + ); + const String debugPath = 'debug/executable'; + const String profilePath = 'profile/executable'; + const String releasePath = 'release/executable'; + when(mockApp.executable(BuildMode.debug)).thenReturn(debugPath); + when(mockApp.executable(BuildMode.profile)).thenReturn(profilePath); + when(mockApp.executable(BuildMode.release)).thenReturn(releasePath); + + expect(device.executablePathForDevice(mockApp, BuildMode.debug), debugPath); + expect(device.executablePathForDevice(mockApp, BuildMode.profile), profilePath); + expect(device.executablePathForDevice(mockApp, BuildMode.release), releasePath); + }, overrides: { + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + }); +} + +class MockMacOSApp extends Mock implements MacOSApp {} diff --git a/packages/flutter_tools/test/general.shard/macos/macos_workflow_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_workflow_test.dart index 0c598de7fe298..37e4d3080bd94 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_workflow_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_workflow_test.dart @@ -3,60 +3,53 @@ // found in the LICENSE file. import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/macos/macos_workflow.dart'; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; import '../../src/common.dart'; -import '../../src/context.dart'; import '../../src/testbed.dart'; +final FakePlatform macOS = FakePlatform( + operatingSystem: 'macos', +); + +final FakePlatform linux = FakePlatform( + operatingSystem: 'linux', +); + void main() { - MockPlatform mac; - MockPlatform notMac; - Testbed testbed; - - setUp(() { - mac = MockPlatform(); - notMac = MockPlatform(); - when(mac.isMacOS).thenReturn(true); - when(notMac.isMacOS).thenReturn(false); - testbed = Testbed(overrides: { - Platform: () => mac, - FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), - }); - }); + testWithoutContext('Applies to macOS platform', () { + final MacOSWorkflow macOSWorkflow = MacOSWorkflow( + platform: macOS, + featureFlags: TestFeatureFlags(isMacOSEnabled: true), + ); - test('Applies to macOS platform', () => testbed.run(() { expect(macOSWorkflow.appliesToHostPlatform, true); expect(macOSWorkflow.canListDevices, true); expect(macOSWorkflow.canLaunchDevices, true); expect(macOSWorkflow.canListEmulators, false); - })); + }); + + testWithoutContext('Does not apply to non-macOS platform', () { + final MacOSWorkflow macOSWorkflow = MacOSWorkflow( + platform: linux, + featureFlags: TestFeatureFlags(isMacOSEnabled: true), + ); - test('Does not apply to non-macOS platform', () => testbed.run(() { expect(macOSWorkflow.appliesToHostPlatform, false); expect(macOSWorkflow.canListDevices, false); expect(macOSWorkflow.canLaunchDevices, false); expect(macOSWorkflow.canListEmulators, false); - }, overrides: { - Platform: () => notMac, - })); + }); + + testWithoutContext('Does not apply when feature is disabled', () { + final MacOSWorkflow macOSWorkflow = MacOSWorkflow( + platform: macOS, + featureFlags: TestFeatureFlags(isMacOSEnabled: false), + ); - test('Does not apply when feature is disabled', () => testbed.run(() { expect(macOSWorkflow.appliesToHostPlatform, false); expect(macOSWorkflow.canListDevices, false); expect(macOSWorkflow.canLaunchDevices, false); expect(macOSWorkflow.canListEmulators, false); - }, overrides: { - FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: false), - })); -} - -class MockPlatform extends Mock implements Platform { - @override - Map environment = {}; + }); } - -class MockProcessManager extends Mock implements ProcessManager {}