Unverified Commit ddb81770 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] remove globals from desktop configuration (#67146)

Refactors the desktop devices and workflow to remove unnecessary usage of global variables. This should make it easier to test and continue enhancing the desktop functionality of the tooling

#47161
parent ddb01a0c
......@@ -615,14 +615,13 @@ class AndroidDevice extends Device {
ProtocolDiscovery observatoryDiscovery;
if (debuggingOptions.debuggingEnabled) {
// TODO(devoncarew): Remember the forwarding information (so we can later remove the
// port forwarding or set it up again when adb fails on us).
observatoryDiscovery = ProtocolDiscovery.observatory(
await getLogReader(),
portForwarder: portForwarder,
hostPort: debuggingOptions.hostVmServicePort,
devicePort: debuggingOptions.deviceVmServicePort,
ipv6: ipv6,
logger: _logger,
);
}
......
......@@ -144,10 +144,12 @@ Future<T> runInContext<T>(
config: globals.config,
fuchsiaWorkflow: fuchsiaWorkflow,
xcDevice: globals.xcdevice,
windowsWorkflow: windowsWorkflow,
macOSWorkflow: MacOSWorkflow(
platform: globals.platform,
featureFlags: featureFlags,
),
operatingSystemUtils: globals.os,
),
Doctor: () => Doctor(logger: globals.logger),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
......@@ -249,7 +251,10 @@ Future<T> runInContext<T>(
featureFlags: featureFlags,
platform: globals.platform,
),
WindowsWorkflow: () => const WindowsWorkflow(),
WindowsWorkflow: () => WindowsWorkflow(
featureFlags: featureFlags,
platform: globals.platform,
),
Xcode: () => Xcode(
logger: globals.logger,
processManager: globals.processManager,
......
......@@ -12,11 +12,11 @@ import 'base/common.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/os.dart';
import 'build_info.dart';
import 'convert.dart';
import 'devfs.dart';
import 'device.dart';
import 'globals.dart' as globals;
import 'protocol_discovery.dart';
/// A partial implementation of Device for desktop-class devices to inherit
......@@ -25,12 +25,14 @@ abstract class DesktopDevice extends Device {
DesktopDevice(String identifier, {
@required PlatformType platformType,
@required bool ephemeral,
Logger logger,
ProcessManager processManager,
FileSystem fileSystem,
}) : _logger = logger ?? globals.logger, // TODO(jonahwilliams): remove after updating google3
_processManager = processManager ?? globals.processManager,
_fileSystem = fileSystem ?? globals.fs,
@required Logger logger,
@required ProcessManager processManager,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
}) : _logger = logger,
_processManager = processManager,
_fileSystem = fileSystem,
_operatingSystemUtils = operatingSystemUtils,
super(
identifier,
category: Category.desktop,
......@@ -41,6 +43,7 @@ abstract class DesktopDevice extends Device {
final Logger _logger;
final ProcessManager _processManager;
final FileSystem _fileSystem;
final OperatingSystemUtils _operatingSystemUtils;
final Set<Process> _runningProcesses = <Process>{};
final DesktopLogReader _deviceLogReader = DesktopLogReader();
......@@ -86,7 +89,7 @@ abstract class DesktopDevice extends Device {
DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
@override
Future<String> get sdkNameAndVersion async => globals.os.name;
Future<String> get sdkNameAndVersion async => _operatingSystemUtils.name;
@override
bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;
......@@ -130,9 +133,11 @@ abstract class DesktopDevice extends Device {
return LaunchResult.failed();
}
final Process process = await _processManager.start(<String>[
executable,
]);
final Process process = await _processManager.start(
<String>[
executable,
],
);
_runningProcesses.add(process);
unawaited(process.exitCode.then((_) => _runningProcesses.remove(process)));
......@@ -144,6 +149,7 @@ abstract class DesktopDevice extends Device {
devicePort: debuggingOptions?.deviceVmServicePort,
hostPort: debuggingOptions?.hostVmServicePort,
ipv6: ipv6,
logger: _logger,
);
try {
final Uri observatoryUri = await observatoryDiscovery.uri;
......@@ -172,7 +178,7 @@ abstract class DesktopDevice extends Device {
// Walk a copy of _runningProcesses, since the exit handler removes from the
// set.
for (final Process process in Set<Process>.of(_runningProcesses)) {
succeeded &= process.kill();
succeeded &= _processManager.killPid(process.pid);
}
return succeeded;
}
......@@ -198,9 +204,12 @@ abstract class DesktopDevice extends Device {
void onAttached(ApplicationPackage package, BuildMode buildMode, Process process) {}
}
/// A log reader for desktop applications that delegates to a [Process] stdout
/// and stderr streams.
class DesktopLogReader extends DeviceLogReader {
final StreamController<List<int>> _inputController = StreamController<List<int>>.broadcast();
/// Begin listening to the stdout and stderr streams of the provided [process].
void initializeProcess(Process process) {
process.stdout.listen(_inputController.add);
process.stderr.listen(_inputController.add);
......
......@@ -21,6 +21,7 @@ import 'base/dds.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/os.dart';
import 'base/platform.dart';
import 'base/user_messages.dart';
import 'base/utils.dart';
......@@ -42,6 +43,7 @@ import 'tester/flutter_tester.dart';
import 'version.dart';
import 'web/web_device.dart';
import 'windows/windows_device.dart';
import 'windows/windows_workflow.dart';
DeviceManager get deviceManager => context.get<DeviceManager>();
......@@ -340,6 +342,8 @@ class FlutterDeviceManager extends DeviceManager {
@required Config config,
@required Artifacts artifacts,
@required MacOSWorkflow macOSWorkflow,
@required OperatingSystemUtils operatingSystemUtils,
@required WindowsWorkflow windowsWorkflow,
}) : deviceDiscoverers = <DeviceDiscovery>[
AndroidDevices(
logger: logger,
......@@ -376,6 +380,7 @@ class FlutterDeviceManager extends DeviceManager {
logger: logger,
platform: platform,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
),
LinuxDevices(
platform: platform,
......@@ -383,8 +388,15 @@ class FlutterDeviceManager extends DeviceManager {
processManager: processManager,
logger: logger,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
),
WindowsDevices(
processManager: processManager,
operatingSystemUtils: operatingSystemUtils,
logger: logger,
fileSystem: fileSystem,
windowsWorkflow: windowsWorkflow,
),
WindowsDevices(),
WebDevices(
featureFlags: featureFlags,
fileSystem: fileSystem,
......
......@@ -7,12 +7,12 @@ import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/os.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';
......@@ -24,6 +24,7 @@ class LinuxDevice extends DesktopDevice {
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
}) : super(
'linux',
platformType: PlatformType.linux,
......@@ -31,6 +32,7 @@ class LinuxDevice extends DesktopDevice {
logger: logger,
processManager: processManager,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
);
@override
......@@ -70,17 +72,19 @@ class LinuxDevices extends PollingDeviceDiscovery {
LinuxDevices({
@required Platform platform,
@required FeatureFlags featureFlags,
FileSystem fileSystem,
ProcessManager processManager,
Logger logger,
}) : _platform = platform ?? globals.platform, // TODO(jonahwilliams): remove after google3 roll
@required OperatingSystemUtils operatingSystemUtils,
@required FileSystem fileSystem,
@required ProcessManager processManager,
@required Logger logger,
}) : _platform = platform,
_linuxWorkflow = LinuxWorkflow(
platform: platform,
featureFlags: featureFlags,
),
_fileSystem = fileSystem ?? globals.fs,
_fileSystem = fileSystem,
_logger = logger,
_processManager = processManager ?? globals.processManager,
_processManager = processManager,
_operatingSystemUtils = operatingSystemUtils,
super('linux devices');
final Platform _platform;
......@@ -88,6 +92,7 @@ class LinuxDevices extends PollingDeviceDiscovery {
final ProcessManager _processManager;
final Logger _logger;
final FileSystem _fileSystem;
final OperatingSystemUtils _operatingSystemUtils;
@override
bool get supportsPlatform => _platform.isLinux;
......@@ -105,6 +110,7 @@ class LinuxDevices extends PollingDeviceDiscovery {
logger: _logger,
processManager: _processManager,
fileSystem: _fileSystem,
operatingSystemUtils: _operatingSystemUtils,
),
];
}
......
......@@ -8,11 +8,11 @@ import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.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';
......@@ -23,7 +23,8 @@ class MacOSDevice extends DesktopDevice {
MacOSDevice({
@required ProcessManager processManager,
@required Logger logger,
FileSystem fileSystem,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
}) : _processManager = processManager,
_logger = logger,
super(
......@@ -32,7 +33,8 @@ class MacOSDevice extends DesktopDevice {
ephemeral: false,
processManager: processManager,
logger: logger,
fileSystem: fileSystem ?? globals.fs,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
);
final ProcessManager _processManager;
......@@ -93,12 +95,14 @@ class MacOSDevices extends PollingDeviceDiscovery {
@required MacOSWorkflow macOSWorkflow,
@required ProcessManager processManager,
@required Logger logger,
FileSystem fileSystem,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
}) : _logger = logger,
_platform = platform,
_macOSWorkflow = macOSWorkflow,
_processManager = processManager,
_fileSystem = fileSystem ?? globals.fs,
_fileSystem = fileSystem,
_operatingSystemUtils = operatingSystemUtils,
super('macOS devices');
final MacOSWorkflow _macOSWorkflow;
......@@ -106,6 +110,7 @@ class MacOSDevices extends PollingDeviceDiscovery {
final ProcessManager _processManager;
final Logger _logger;
final FileSystem _fileSystem;
final OperatingSystemUtils _operatingSystemUtils;
@override
bool get supportsPlatform => _platform.isMacOS;
......@@ -123,6 +128,7 @@ class MacOSDevices extends PollingDeviceDiscovery {
processManager: _processManager,
logger: _logger,
fileSystem: _fileSystem,
operatingSystemUtils: _operatingSystemUtils,
),
];
}
......
......@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:meta/meta.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'device.dart';
import 'globals.dart' as globals;
......@@ -22,8 +23,9 @@ class ProtocolDiscovery {
this.hostPort,
this.devicePort,
this.ipv6,
}) : assert(logReader != null)
{
Logger logger,
}) : _logger = logger,
assert(logReader != null) {
_deviceLogSubscription = logReader.logLines.listen(
_handleLine,
onDone: _stopScrapingLogs,
......@@ -39,6 +41,7 @@ class ProtocolDiscovery {
@required int hostPort,
@required int devicePort,
@required bool ipv6,
Logger logger, // TODO(jonahwilliams): make required.
}) {
const String kObservatoryService = 'Observatory';
return ProtocolDiscovery._(
......@@ -50,6 +53,7 @@ class ProtocolDiscovery {
hostPort: hostPort,
devicePort: devicePort,
ipv6: ipv6,
logger: logger ?? globals.logger,
);
}
......@@ -59,6 +63,7 @@ class ProtocolDiscovery {
final int hostPort;
final int devicePort;
final bool ipv6;
final Logger _logger;
/// The time to wait before forwarding a new observatory URIs from [logReader].
final Duration throttleDuration;
......@@ -138,20 +143,20 @@ class ProtocolDiscovery {
return;
}
if (devicePort != null && uri.port != devicePort) {
globals.printTrace('skipping potential observatory $uri due to device port mismatch');
_logger.printTrace('skipping potential observatory $uri due to device port mismatch');
return;
}
_uriStreamController.add(uri);
}
Future<Uri> _forwardPort(Uri deviceUri) async {
globals.printTrace('$serviceName URL on device: $deviceUri');
_logger.printTrace('$serviceName URL on device: $deviceUri');
Uri hostUri = deviceUri;
if (portForwarder != null) {
final int actualDevicePort = deviceUri.port;
final int actualHostPort = await portForwarder.forward(actualDevicePort, hostPort: hostPort);
globals.printTrace('Forwarded host port $actualHostPort to device port $actualDevicePort for $serviceName');
_logger.printTrace('Forwarded host port $actualHostPort to device port $actualDevicePort for $serviceName');
hostUri = deviceUri.replace(port: actualHostPort);
}
......
......@@ -3,13 +3,16 @@
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../desktop_device.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../project.dart';
import 'application_package.dart';
import 'build_windows.dart';
......@@ -17,10 +20,19 @@ import 'windows_workflow.dart';
/// A device that represents a desktop Windows target.
class WindowsDevice extends DesktopDevice {
WindowsDevice() : super(
WindowsDevice({
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
}) : super(
'windows',
platformType: PlatformType.windows,
ephemeral: false,
processManager: processManager,
logger: logger,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
);
@override
......@@ -57,13 +69,30 @@ class WindowsDevice extends DesktopDevice {
}
class WindowsDevices extends PollingDeviceDiscovery {
WindowsDevices() : super('windows devices');
WindowsDevices({
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
@required WindowsWorkflow windowsWorkflow,
}) : _fileSystem = fileSystem,
_logger = logger,
_processManager = processManager,
_operatingSystemUtils = operatingSystemUtils,
_windowsWorkflow = windowsWorkflow,
super('windows devices');
final FileSystem _fileSystem;
final Logger _logger;
final ProcessManager _processManager;
final OperatingSystemUtils _operatingSystemUtils;
final WindowsWorkflow _windowsWorkflow;
@override
bool get supportsPlatform => globals.platform.isWindows;
bool get supportsPlatform => _windowsWorkflow.appliesToHostPlatform;
@override
bool get canListAnything => windowsWorkflow.canListDevices;
bool get canListAnything => _windowsWorkflow.canListDevices;
@override
Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
......@@ -71,7 +100,12 @@ class WindowsDevices extends PollingDeviceDiscovery {
return const <Device>[];
}
return <Device>[
WindowsDevice(),
WindowsDevice(
fileSystem: _fileSystem,
logger: _logger,
processManager: _processManager,
operatingSystemUtils: _operatingSystemUtils,
),
];
}
......
......@@ -2,29 +2,38 @@
// 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 '../base/context.dart';
import '../base/platform.dart';
import '../doctor.dart';
import '../features.dart';
import '../globals.dart' as globals;
/// The [WindowsWorkflow] instance.
WindowsWorkflow get windowsWorkflow => context.get<WindowsWorkflow>();
/// The windows-specific implementation of a [Workflow].
/// The Windows-specific implementation of a [Workflow].
///
/// This workflow requires the flutter-desktop-embedding as a sibling
/// repository to the flutter repo.
/// This workflow requires the host machine to be Windows, and the Windows
/// desktop configuration setting to be enabled.
class WindowsWorkflow implements Workflow {
const WindowsWorkflow();
const WindowsWorkflow({
@required Platform platform,
@required FeatureFlags featureFlags,
}) : _platform = platform,
_featureFlags = featureFlags;
final Platform _platform;
final FeatureFlags _featureFlags;
@override
bool get appliesToHostPlatform => globals.platform.isWindows && featureFlags.isWindowsEnabled;
bool get appliesToHostPlatform => _platform.isWindows && _featureFlags.isWindowsEnabled;
@override
bool get canLaunchDevices => globals.platform.isWindows && featureFlags.isWindowsEnabled;
bool get canLaunchDevices => _platform.isWindows && _featureFlags.isWindowsEnabled;
@override
bool get canListDevices => globals.platform.isWindows && featureFlags.isWindowsEnabled;
bool get canListDevices => _platform.isWindows && _featureFlags.isWindowsEnabled;
@override
bool get canListEmulators => false;
......
......@@ -3,110 +3,59 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.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/os.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/desktop_device.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
/// A trivial subclass of DesktopDevice for testing the shared functionality.
class FakeDesktopDevice extends DesktopDevice {
FakeDesktopDevice() : super(
'dummy',
platformType: PlatformType.linux,
ephemeral: false,
);
/// The [mainPath] last passed to [buildForDevice].
String lastBuiltMainPath;
/// The [buildInfo] last passed to [buildForDevice].
BuildInfo lastBuildInfo;
@override
String get name => 'dummy';
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;
@override
bool isSupported() => true;
@override
bool isSupportedForProject(FlutterProject flutterProject) => true;
@override
Future<void> buildForDevice(
ApplicationPackage package, {
String mainPath,
BuildInfo buildInfo,
}) async {
lastBuiltMainPath = mainPath;
lastBuildInfo = buildInfo;
}
// Dummy implementation that just returns the build mode name.
@override
String executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
return buildMode == null ? 'null' : getNameForBuildMode(buildMode);
}
}
/// A desktop device that returns a null executable path, for failure testing.
class NullExecutableDesktopDevice extends FakeDesktopDevice {
@override
String executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
return null;
}
}
class MockAppplicationPackage extends Mock implements ApplicationPackage {}
class MockProcessManager extends Mock implements ProcessManager {}
void main() {
group('Basic info', () {
test('Category is desktop', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
testWithoutContext('Category is desktop', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(device.category, Category.desktop);
});
test('Not an emulator', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
testWithoutContext('Not an emulator', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(await device.isLocalEmulator, false);
expect(await device.emulatorId, null);
});
testUsingContext('Uses OS name as SDK name', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
expect(await device.sdkNameAndVersion, globals.os.name);
testWithoutContext('Uses OS name as SDK name', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(await device.sdkNameAndVersion, 'Example');
});
});
group('Install', () {
test('Install checks always return true', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
testWithoutContext('Install checks always return true', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
expect(await device.isAppInstalled(null), true);
expect(await device.isLatestBuildInstalled(null), true);
expect(device.category, Category.desktop);
});
test('Install and uninstall are no-ops that report success', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
final MockAppplicationPackage package = MockAppplicationPackage();
testWithoutContext('Install and uninstall are no-ops that report success', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
final FakeAppplicationPackage package = FakeAppplicationPackage();
expect(await device.uninstallApp(package), true);
expect(await device.isAppInstalled(package), true);
expect(await device.isLatestBuildInstalled(package), true);
......@@ -119,72 +68,147 @@ void main() {
});
group('Starting and stopping application', () {
final FileSystem memoryFileSystem = MemoryFileSystem.test();
final MockProcessManager mockProcessManager = MockProcessManager();
// Configures mock environment so that startApp will be able to find and
// run an FakeDesktopDevice exectuable with for the given mode.
void setUpMockExecutable(FakeDesktopDevice device, BuildMode mode, {Future<int> exitFuture}) {
final String executableName = device.executablePathForDevice(null, mode);
memoryFileSystem.file(executableName).writeAsStringSync('\n');
when(mockProcessManager.start(<String>[executableName])).thenAnswer((Invocation invocation) async {
return FakeProcess(
exitCode: Completer<int>().future,
stdout: Stream<List<int>>.fromIterable(<List<int>>[
utf8.encode('Observatory listening on http://127.0.0.1/0\n'),
]),
stderr: const Stream<List<int>>.empty(),
);
});
when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
return ProcessResult(0, 1, '', '');
});
}
testWithoutContext('Stop without start is a successful no-op', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
final FakeAppplicationPackage package = FakeAppplicationPackage();
test('Stop without start is a successful no-op', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
final MockAppplicationPackage package = MockAppplicationPackage();
expect(await device.stopApp(package), true);
});
testUsingContext('Can run from prebuilt application', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
final MockAppplicationPackage package = MockAppplicationPackage();
setUpMockExecutable(device, null);
testWithoutContext('Can run from prebuilt application', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final Completer<void> completer = Completer<void>();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['null'],
stdout: 'Observatory listening on http://127.0.0.1/0\n',
completer: completer,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem);
final String executableName = device.executablePathForDevice(null, BuildMode.debug);
fileSystem.file(executableName).writeAsStringSync('\n');
final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp(package, prebuiltApplication: true);
expect(result.started, true);
expect(result.observatoryUri, Uri.parse('http://127.0.0.1/0'));
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
ProcessManager: () => mockProcessManager,
});
testUsingContext('Null executable path fails gracefully', () async {
final NullExecutableDesktopDevice device = NullExecutableDesktopDevice();
final MockAppplicationPackage package = MockAppplicationPackage();
testWithoutContext('Null executable path fails gracefully', () async {
final BufferLogger logger = BufferLogger.test();
final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger);
final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp(package, prebuiltApplication: true);
expect(result.started, false);
expect(testLogger.errorText, contains('Unable to find executable to run'));
expect(logger.errorText, contains('Unable to find executable to run'));
});
testUsingContext('stopApp kills process started by startApp', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
final MockAppplicationPackage package = MockAppplicationPackage();
setUpMockExecutable(device, null);
testWithoutContext('stopApp kills process started by startApp', () async {
final Completer<void> completer = Completer<void>();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['null'],
stdout: 'Observatory listening on http://127.0.0.1/0\n',
completer: completer,
),
]);
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager);
final FakeAppplicationPackage package = FakeAppplicationPackage();
final LaunchResult result = await device.startApp(package, prebuiltApplication: true);
expect(result.started, true);
expect(await device.stopApp(package), true);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
ProcessManager: () => mockProcessManager,
});
});
test('Port forwarder is a no-op', () async {
final FakeDesktopDevice device = FakeDesktopDevice();
testWithoutContext('Port forwarder is a no-op', () async {
final FakeDesktopDevice device = setUpDesktopDevice();
final DevicePortForwarder portForwarder = device.portForwarder;
final int result = await portForwarder.forward(2);
expect(result, 2);
expect(portForwarder.forwardedPorts.isEmpty, true);
});
}
FakeDesktopDevice setUpDesktopDevice({
FileSystem fileSystem,
Logger logger,
ProcessManager processManager,
OperatingSystemUtils operatingSystemUtils,
bool nullExecutablePathForDevice = false,
}) {
return FakeDesktopDevice(
fileSystem: fileSystem ?? MemoryFileSystem.test(),
logger: logger ?? BufferLogger.test(),
processManager: processManager ?? FakeProcessManager.any(),
operatingSystemUtils: operatingSystemUtils ?? FakeOperatingSystemUtils(),
nullExecutablePathForDevice: nullExecutablePathForDevice,
);
}
/// A trivial subclass of DesktopDevice for testing the shared functionality.
class FakeDesktopDevice extends DesktopDevice {
FakeDesktopDevice({
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required OperatingSystemUtils operatingSystemUtils,
this.nullExecutablePathForDevice,
}) : super(
'dummy',
platformType: PlatformType.linux,
ephemeral: false,
processManager: processManager,
logger: logger,
fileSystem: fileSystem,
operatingSystemUtils: operatingSystemUtils,
);
/// The [mainPath] last passed to [buildForDevice].
String lastBuiltMainPath;
/// The [buildInfo] last passed to [buildForDevice].
BuildInfo lastBuildInfo;
final bool nullExecutablePathForDevice;
@override
String get name => 'dummy';
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.tester;
@override
bool isSupported() => true;
@override
bool isSupportedForProject(FlutterProject flutterProject) => true;
@override
Future<void> buildForDevice(
ApplicationPackage package, {
String mainPath,
BuildInfo buildInfo,
}) async {
lastBuiltMainPath = mainPath;
lastBuildInfo = buildInfo;
}
// Dummy implementation that just returns the build mode name.
@override
String executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
if (nullExecutablePathForDevice) {
return null;
}
return buildMode == null ? 'null' : getNameForBuildMode(buildMode);
}
}
class FakeAppplicationPackage extends Fake implements ApplicationPackage {}
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
@override
String get name => 'Example';
}
......@@ -5,10 +5,10 @@
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/os.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/globals.dart' as globals;
import 'package:flutter_tools/src/linux/application_package.dart';
import 'package:flutter_tools/src/linux/linux_device.dart';
import 'package:flutter_tools/src/project.dart';
......@@ -32,6 +32,7 @@ void main() {
processManager: FakeProcessManager.any(),
logger: BufferLogger.test(),
fileSystem: MemoryFileSystem.test(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
final PrebuiltLinuxApp linuxApp = PrebuiltLinuxApp(executable: 'foo');
......@@ -57,6 +58,7 @@ void main() {
featureFlags: TestFeatureFlags(isLinuxEnabled: true),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
).devices, <Device>[]);
});
......@@ -67,6 +69,7 @@ void main() {
featureFlags: TestFeatureFlags(isLinuxEnabled: false),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
).devices, <Device>[]);
});
......@@ -77,6 +80,7 @@ void main() {
featureFlags: TestFeatureFlags(isLinuxEnabled: true),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
).devices, hasLength(1));
});
......@@ -88,47 +92,47 @@ void main() {
featureFlags: TestFeatureFlags(isLinuxEnabled: true),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
).discoverDevices(timeout: const Duration(seconds: 10));
expect(devices, hasLength(1));
});
testUsingContext('LinuxDevice.isSupportedForProject is true with editable host app', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
globals.fs.directory('linux').createSync();
final FlutterProject flutterProject = FlutterProject.current();
testWithoutContext('LinuxDevice.isSupportedForProject is true with editable host app', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
fileSystem.directory('linux').createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(LinuxDevice(
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
fileSystem: fileSystem,
operatingSystemUtils: FakeOperatingSystemUtils(),
).isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('LinuxDevice.isSupportedForProject is false with no host app', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
testWithoutContext('LinuxDevice.isSupportedForProject is false with no host app', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(LinuxDevice(
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
fileSystem: fileSystem,
operatingSystemUtils: FakeOperatingSystemUtils(),
).isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('LinuxDevice.executablePathForDevice uses the correct package executable', () async {
testWithoutContext('LinuxDevice.executablePathForDevice uses the correct package executable', () async {
final MockLinuxApp mockApp = MockLinuxApp();
final LinuxDevice device = LinuxDevice(
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
const String debugPath = 'debug/executable';
const String profilePath = 'profile/executable';
......@@ -140,10 +144,19 @@ void main() {
expect(device.executablePathForDevice(mockApp, BuildMode.debug), debugPath);
expect(device.executablePathForDevice(mockApp, BuildMode.profile), profilePath);
expect(device.executablePathForDevice(mockApp, BuildMode.release), releasePath);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
}
FlutterProject setUpFlutterProject(Directory directory) {
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(
fileSystem: directory.fileSystem,
logger: BufferLogger.test(),
);
return flutterProjectFactory.fromDirectory(directory);
}
class MockLinuxApp extends Mock implements LinuxApp {}
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
@override
String get name => 'Linux';
}
......@@ -5,19 +5,16 @@
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/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/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';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -37,6 +34,7 @@ void main() {
processManager: FakeProcessManager.any(),
logger: BufferLogger.test(),
fileSystem: MemoryFileSystem.test(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
final MockMacOSApp mockMacOSApp = MockMacOSApp();
......@@ -54,7 +52,7 @@ void main() {
expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testUsingContext('Attaches to log reader when running in release mode', () async {
testWithoutContext('Attaches to log reader when running in release mode', () async {
final Completer<void> completer = Completer<void>();
final MacOSDevice device = MacOSDevice(
fileSystem: MemoryFileSystem.test(),
......@@ -67,6 +65,7 @@ void main() {
)
]),
logger: BufferLogger.test(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
final MockMacOSApp mockMacOSApp = MockMacOSApp();
when(mockMacOSApp.executable(BuildMode.release)).thenReturn('Example.app');
......@@ -83,9 +82,6 @@ void main() {
expect(logReader.logLines, emits('Hello WorldGoodnight, Moon'));
completer.complete();
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testWithoutContext('No devices listed if platform is unsupported', () async {
......@@ -94,6 +90,7 @@ void main() {
processManager: FakeProcessManager.any(),
logger: BufferLogger.test(),
platform: linux,
operatingSystemUtils: FakeOperatingSystemUtils(),
macOSWorkflow: MacOSWorkflow(
featureFlags: TestFeatureFlags(isMacOSEnabled: true),
platform: linux,
......@@ -107,6 +104,7 @@ void main() {
processManager: FakeProcessManager.any(),
logger: BufferLogger.test(),
platform: macOS,
operatingSystemUtils: FakeOperatingSystemUtils(),
macOSWorkflow: MacOSWorkflow(
featureFlags: TestFeatureFlags(isMacOSEnabled: false),
platform: macOS,
......@@ -122,6 +120,7 @@ void main() {
processManager: FakeProcessManager.any(),
logger: BufferLogger.test(),
platform: macOS,
operatingSystemUtils: FakeOperatingSystemUtils(),
macOSWorkflow: MacOSWorkflow(
featureFlags: TestFeatureFlags(isMacOSEnabled: true),
platform: macOS,
......@@ -137,6 +136,7 @@ void main() {
processManager: FakeProcessManager.any(),
logger: BufferLogger.test(),
platform: macOS,
operatingSystemUtils: FakeOperatingSystemUtils(),
macOSWorkflow: MacOSWorkflow(
featureFlags: TestFeatureFlags(isMacOSEnabled: true),
platform: macOS,
......@@ -149,38 +149,36 @@ void main() {
expect(devices, hasLength(1));
});
testUsingContext('isSupportedForProject is true with editable host app', () async {
testWithoutContext('isSupportedForProject is true with editable host app', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final MacOSDevice device = MacOSDevice(
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
globals.fs.directory('macos').createSync();
final FlutterProject flutterProject = FlutterProject.current();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
fileSystem.directory('macos').createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(device.isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('isSupportedForProject is false with no host app', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final MacOSDevice device = MacOSDevice(
fileSystem: MemoryFileSystem.test(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(device.isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('executablePathForDevice uses the correct package executable', () async {
......@@ -189,6 +187,7 @@ void main() {
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
const String debugPath = 'debug/executable';
const String profilePath = 'profile/executable';
......@@ -200,10 +199,15 @@ void main() {
expect(device.executablePathForDevice(mockApp, BuildMode.debug), debugPath);
expect(device.executablePathForDevice(mockApp, BuildMode.profile), profilePath);
expect(device.executablePathForDevice(mockApp, BuildMode.release), releasePath);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
}
FlutterProject setUpFlutterProject(Directory directory) {
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(
fileSystem: directory.fileSystem,
logger: BufferLogger.test(),
);
return flutterProjectFactory.fromDirectory(directory);
}
class MockMacOSApp extends Mock implements MacOSApp {}
......@@ -4,14 +4,14 @@
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';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/windows/application_package.dart';
import 'package:flutter_tools/src/windows/windows_device.dart';
import 'package:flutter_tools/src/windows/windows_workflow.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
......@@ -19,109 +19,134 @@ import '../../src/context.dart';
import '../../src/testbed.dart';
void main() {
group(WindowsDevice, () {
final WindowsDevice device = WindowsDevice();
final MockPlatform notWindows = MockPlatform();
when(notWindows.isWindows).thenReturn(false);
when(notWindows.environment).thenReturn(const <String, String>{});
final MockPlatform mockWindowsPlatform = MockPlatform();
when(mockWindowsPlatform.isWindows).thenReturn(true);
testUsingContext('defaults', () async {
final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');
expect(await device.targetPlatform, TargetPlatform.windows_x64);
expect(device.name, 'Windows');
expect(await device.installApp(windowsApp), true);
expect(await device.uninstallApp(windowsApp), true);
expect(await device.isLatestBuildInstalled(windowsApp), true);
expect(await device.isAppInstalled(windowsApp), 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 WindowsDevices().devices, <Device>[]);
}, overrides: <Type, Generator>{
Platform: () => notWindows,
});
testUsingContext('WindowsDevices: devices', () async {
expect(await WindowsDevices().devices, hasLength(1));
}, overrides: <Type, Generator>{
Platform: () => mockWindowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
testUsingContext('WindowsDevices: discoverDevices', () async {
// Timeout ignored.
final List<Device> devices = await WindowsDevices().discoverDevices(timeout: const Duration(seconds: 10));
expect(devices, hasLength(1));
}, overrides: <Type, Generator>{
Platform: () => mockWindowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
testUsingContext('isSupportedForProject is true with editable host app', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
globals.fs.directory('windows').createSync();
globals.fs.file(globals.fs.path.join('windows', 'CMakeLists.txt')).createSync();
final FlutterProject flutterProject = FlutterProject.current();
expect(WindowsDevice().isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
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(WindowsDevice().isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('isSupportedForProject is false with no build file', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
globals.fs.directory('windows').createSync();
final FlutterProject flutterProject = FlutterProject.current();
expect(WindowsDevice().isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('executablePathForDevice uses the correct package executable', () async {
final MockWindowsApp mockApp = MockWindowsApp();
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(WindowsDevice().executablePathForDevice(mockApp, BuildMode.debug), debugPath);
expect(WindowsDevice().executablePathForDevice(mockApp, BuildMode.profile), profilePath);
expect(WindowsDevice().executablePathForDevice(mockApp, BuildMode.release), releasePath);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testWithoutContext('WindowsDevice defaults', () async {
final WindowsDevice windowsDevice = setUpWindowsDevice();
final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');
expect(await windowsDevice.targetPlatform, TargetPlatform.windows_x64);
expect(windowsDevice.name, 'Windows');
expect(await windowsDevice.installApp(windowsApp), true);
expect(await windowsDevice.uninstallApp(windowsApp), true);
expect(await windowsDevice.isLatestBuildInstalled(windowsApp), true);
expect(await windowsDevice.isAppInstalled(windowsApp), true);
expect(windowsDevice.category, Category.desktop);
expect(windowsDevice.supportsRuntimeMode(BuildMode.debug), true);
expect(windowsDevice.supportsRuntimeMode(BuildMode.profile), true);
expect(windowsDevice.supportsRuntimeMode(BuildMode.release), true);
expect(windowsDevice.supportsRuntimeMode(BuildMode.jitRelease), false);
});
testWithoutContext('WindowsDevices does not list devices if the workflow is unsupported', () async {
expect(await WindowsDevices(
windowsWorkflow: WindowsWorkflow(
featureFlags: TestFeatureFlags(isWindowsEnabled: false),
platform: FakePlatform(operatingSystem: 'windows')
),
operatingSystemUtils: FakeOperatingSystemUtils(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
).devices, <Device>[]);
});
testWithoutContext('WindowsDevices lists a devices if the workflow is supported', () async {
expect(await WindowsDevices(
windowsWorkflow: WindowsWorkflow(
featureFlags: TestFeatureFlags(isWindowsEnabled: true),
platform: FakePlatform(operatingSystem: 'windows')
),
operatingSystemUtils: FakeOperatingSystemUtils(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
).devices, hasLength(1));
});
testWithoutContext('WindowsDevices ignores the timeout provided to discoverDevices', () async {
final WindowsDevices windowsDevices = WindowsDevices(
windowsWorkflow: WindowsWorkflow(
featureFlags: TestFeatureFlags(isWindowsEnabled: true),
platform: FakePlatform(operatingSystem: 'windows')
),
operatingSystemUtils: FakeOperatingSystemUtils(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(),
);
// Timeout ignored.
final List<Device> devices = await windowsDevices.discoverDevices(timeout: const Duration(seconds: 10));
expect(devices, hasLength(1));
});
testWithoutContext('isSupportedForProject is true with editable host app', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final WindowsDevice windowsDevice = setUpWindowsDevice(fileSystem: fileSystem);
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
fileSystem.directory('windows').createSync();
fileSystem.file(fileSystem.path.join('windows', 'CMakeLists.txt')).createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(windowsDevice.isSupportedForProject(flutterProject), true);
});
testWithoutContext('isSupportedForProject is false with no host app', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final WindowsDevice windowsDevice = setUpWindowsDevice(fileSystem: fileSystem);
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(windowsDevice.isSupportedForProject(flutterProject), false);
});
testWithoutContext('isSupportedForProject is false with no build file', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final WindowsDevice windowsDevice = setUpWindowsDevice(fileSystem: fileSystem);
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
fileSystem.directory('windows').createSync();
final FlutterProject flutterProject = setUpFlutterProject(fileSystem.currentDirectory);
expect(windowsDevice.isSupportedForProject(flutterProject), false);
});
testWithoutContext('executablePathForDevice uses the correct package executable', () async {
final WindowsDevice windowsDevice = setUpWindowsDevice();
final MockWindowsApp mockApp = MockWindowsApp();
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(windowsDevice.executablePathForDevice(mockApp, BuildMode.debug), debugPath);
expect(windowsDevice.executablePathForDevice(mockApp, BuildMode.profile), profilePath);
expect(windowsDevice.executablePathForDevice(mockApp, BuildMode.release), releasePath);
});
}
class MockPlatform extends Mock implements Platform {}
FlutterProject setUpFlutterProject(Directory directory) {
final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(
fileSystem: directory.fileSystem,
logger: BufferLogger.test(),
);
return flutterProjectFactory.fromDirectory(directory);
}
WindowsDevice setUpWindowsDevice({
FileSystem fileSystem,
Logger logger,
ProcessManager processManager,
}) {
return WindowsDevice(
fileSystem: fileSystem ?? MemoryFileSystem.test(),
logger: logger ?? BufferLogger.test(),
processManager: processManager ?? FakeProcessManager.any(),
operatingSystemUtils: FakeOperatingSystemUtils(),
);
}
class MockWindowsApp extends Mock implements WindowsApp {}
......@@ -3,51 +3,60 @@
// 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/windows/windows_workflow.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/testbed.dart';
void main() {
Testbed testbed;
FakePlatform windows;
FakePlatform notWindows;
setUp(() {
windows = FakePlatform(operatingSystem: 'windows');
notWindows = FakePlatform(operatingSystem: 'linux');
testbed = Testbed(
overrides: <Type, Generator>{
Platform: () => windows,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
},
final FakePlatform windows = FakePlatform(operatingSystem: 'windows');
final FakePlatform notWindows = FakePlatform(operatingSystem: 'linux');
testWithoutContext('Windows workflow configuration when feature is enabled on Windows host machine', () {
final WindowsWorkflow windowsWorkflow = WindowsWorkflow(
platform: windows,
featureFlags: TestFeatureFlags(isWindowsEnabled: true),
);
});
test('Windows default workflow values', () => testbed.run(() {
expect(windowsWorkflow.appliesToHostPlatform, true);
expect(windowsWorkflow.canListDevices, true);
expect(windowsWorkflow.canLaunchDevices, true);
expect(windowsWorkflow.canListEmulators, false);
}));
});
testWithoutContext('Windows workflow configuration when feature is disabled on Windows host machine', () {
final WindowsWorkflow windowsWorkflow = WindowsWorkflow(
platform: windows,
featureFlags: TestFeatureFlags(isWindowsEnabled: false),
);
test('Windows defaults on non-windows platform', () => testbed.run(() {
expect(windowsWorkflow.appliesToHostPlatform, false);
expect(windowsWorkflow.canListDevices, false);
expect(windowsWorkflow.canLaunchDevices, false);
expect(windowsWorkflow.canListEmulators, false);
}, overrides: <Type, Generator>{
Platform: () => notWindows,
}));
});
testWithoutContext('Windows workflow configuration when feature is enabled on non-Windows host machine', () {
final WindowsWorkflow windowsWorkflow = WindowsWorkflow(
platform: notWindows,
featureFlags: TestFeatureFlags(isWindowsEnabled: true),
);
test('Windows defaults on non-windows platform', () => testbed.run(() {
expect(windowsWorkflow.appliesToHostPlatform, false);
expect(windowsWorkflow.canListDevices, false);
expect(windowsWorkflow.canLaunchDevices, false);
expect(windowsWorkflow.canListEmulators, false);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
}));
});
testWithoutContext('Windows workflow configuration when feature is disabled on non-Windows host machine', () {
final WindowsWorkflow windowsWorkflow = WindowsWorkflow(
platform: notWindows,
featureFlags: TestFeatureFlags(isWindowsEnabled: false),
);
expect(windowsWorkflow.appliesToHostPlatform, false);
expect(windowsWorkflow.canListDevices, false);
expect(windowsWorkflow.canLaunchDevices, false);
expect(windowsWorkflow.canListEmulators, false);
});
}
......@@ -123,13 +123,13 @@ class _FakeProcess implements Process {
this._stderr,
this.stdin,
this._stdout,
Completer<void> completer,
this._completer,
) : exitCode = Future<void>.delayed(duration).then((void value) {
if (onRun != null) {
onRun();
}
if (completer != null) {
return completer.future.then((void _) => _exitCode);
if (_completer != null) {
return _completer.future.then((void _) => _exitCode);
}
return _exitCode;
}),
......@@ -141,6 +141,7 @@ class _FakeProcess implements Process {
: Stream<List<int>>.value(utf8.encode(_stdout));
final int _exitCode;
final Completer<void> _completer;
@override
final Future<int> exitCode;
......@@ -205,6 +206,8 @@ abstract class FakeProcessManager implements ProcessManager {
commands.forEach(addCommand);
}
final Map<int, _FakeProcess> _fakeRunningProcesses = <int, _FakeProcess>{};
/// Whether this fake has more [FakeCommand]s that are expected to run.
///
/// This is always `true` for [FakeProcessManager.any].
......@@ -248,7 +251,16 @@ abstract class FakeProcessManager implements ProcessManager {
bool includeParentEnvironment = true, // ignored
bool runInShell = false, // ignored
ProcessStartMode mode = ProcessStartMode.normal, // ignored
}) async => _runCommand(command.cast<String>(), workingDirectory, environment, systemEncoding);
}) {
final _FakeProcess process = _runCommand(command.cast<String>(), workingDirectory, environment, systemEncoding);
if (process._completer != null) {
_fakeRunningProcesses[process.pid] = process;
process.exitCode.whenComplete(() {
_fakeRunningProcesses.remove(process.pid);
});
}
return Future<Process>.value(process);
}
@override
Future<ProcessResult> run(
......@@ -294,8 +306,13 @@ abstract class FakeProcessManager implements ProcessManager {
@override
bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
// Killing a fake process has no effect.
return false;
// Killing a fake process has no effect unless it has an attached completer.
final _FakeProcess fakeProcess = _fakeRunningProcesses[pid];
if (fakeProcess == null) {
return false;
}
fakeProcess._completer.complete();
return true;
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment