[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
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index a2579f5..9280f4b 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -615,14 +615,13 @@
     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,
       );
     }
 
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 00a85df..6089ce8 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -144,10 +144,12 @@
         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 @@
         featureFlags: featureFlags,
         platform: globals.platform,
       ),
-      WindowsWorkflow: () => const WindowsWorkflow(),
+      WindowsWorkflow: () => WindowsWorkflow(
+        featureFlags: featureFlags,
+        platform: globals.platform,
+      ),
       Xcode: () => Xcode(
         logger: globals.logger,
         processManager: globals.processManager,
diff --git a/packages/flutter_tools/lib/src/desktop_device.dart b/packages/flutter_tools/lib/src/desktop_device.dart
index 3f6340f..3fa0d1b 100644
--- a/packages/flutter_tools/lib/src/desktop_device.dart
+++ b/packages/flutter_tools/lib/src/desktop_device.dart
@@ -12,11 +12,11 @@
 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 @@
   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 @@
   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 @@
   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 @@
       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 @@
       devicePort: debuggingOptions?.deviceVmServicePort,
       hostPort: debuggingOptions?.hostVmServicePort,
       ipv6: ipv6,
+      logger: _logger,
     );
     try {
       final Uri observatoryUri = await observatoryDiscovery.uri;
@@ -172,7 +178,7 @@
     // 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 @@
   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);
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index cdc2c70..614a026 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -21,6 +21,7 @@
 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 '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 @@
     @required Config config,
     @required Artifacts artifacts,
     @required MacOSWorkflow macOSWorkflow,
+    @required OperatingSystemUtils operatingSystemUtils,
+    @required WindowsWorkflow windowsWorkflow,
   }) : deviceDiscoverers =  <DeviceDiscovery>[
     AndroidDevices(
       logger: logger,
@@ -376,6 +380,7 @@
       logger: logger,
       platform: platform,
       fileSystem: fileSystem,
+      operatingSystemUtils: operatingSystemUtils,
     ),
     LinuxDevices(
       platform: platform,
@@ -383,8 +388,15 @@
       processManager: processManager,
       logger: logger,
       fileSystem: fileSystem,
+      operatingSystemUtils: operatingSystemUtils,
     ),
-    WindowsDevices(),
+    WindowsDevices(
+      processManager: processManager,
+      operatingSystemUtils: operatingSystemUtils,
+      logger: logger,
+      fileSystem: fileSystem,
+      windowsWorkflow: windowsWorkflow,
+    ),
     WebDevices(
       featureFlags: featureFlags,
       fileSystem: fileSystem,
diff --git a/packages/flutter_tools/lib/src/linux/linux_device.dart b/packages/flutter_tools/lib/src/linux/linux_device.dart
index 47f0621..a860d16 100644
--- a/packages/flutter_tools/lib/src/linux/linux_device.dart
+++ b/packages/flutter_tools/lib/src/linux/linux_device.dart
@@ -7,12 +7,12 @@
 
 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 @@
     @required ProcessManager processManager,
     @required Logger logger,
     @required FileSystem fileSystem,
+    @required OperatingSystemUtils operatingSystemUtils,
   }) : super(
       'linux',
       platformType: PlatformType.linux,
@@ -31,6 +32,7 @@
       logger: logger,
       processManager: processManager,
       fileSystem: fileSystem,
+      operatingSystemUtils: operatingSystemUtils,
   );
 
   @override
@@ -70,17 +72,19 @@
   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 @@
   final ProcessManager _processManager;
   final Logger _logger;
   final FileSystem _fileSystem;
+  final OperatingSystemUtils _operatingSystemUtils;
 
   @override
   bool get supportsPlatform => _platform.isLinux;
@@ -105,6 +110,7 @@
         logger: _logger,
         processManager: _processManager,
         fileSystem: _fileSystem,
+        operatingSystemUtils: _operatingSystemUtils,
       ),
     ];
   }
diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart
index 17c7fb0..934011f 100644
--- a/packages/flutter_tools/lib/src/macos/macos_device.dart
+++ b/packages/flutter_tools/lib/src/macos/macos_device.dart
@@ -8,11 +8,11 @@
 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 @@
   MacOSDevice({
     @required ProcessManager processManager,
     @required Logger logger,
-    FileSystem fileSystem,
+    @required FileSystem fileSystem,
+    @required OperatingSystemUtils operatingSystemUtils,
   }) : _processManager = processManager,
        _logger = logger,
        super(
@@ -32,7 +33,8 @@
         ephemeral: false,
         processManager: processManager,
         logger: logger,
-        fileSystem: fileSystem ?? globals.fs,
+        fileSystem: fileSystem,
+        operatingSystemUtils: operatingSystemUtils,
       );
 
   final ProcessManager _processManager;
@@ -93,12 +95,14 @@
     @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 @@
   final ProcessManager _processManager;
   final Logger _logger;
   final FileSystem _fileSystem;
+  final OperatingSystemUtils _operatingSystemUtils;
 
   @override
   bool get supportsPlatform => _platform.isMacOS;
@@ -123,6 +128,7 @@
         processManager: _processManager,
         logger: _logger,
         fileSystem: _fileSystem,
+        operatingSystemUtils: _operatingSystemUtils,
       ),
     ];
   }
diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart
index bdcbb45..96d238a 100644
--- a/packages/flutter_tools/lib/src/protocol_discovery.dart
+++ b/packages/flutter_tools/lib/src/protocol_discovery.dart
@@ -7,6 +7,7 @@
 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 @@
     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 @@
     @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 @@
       hostPort: hostPort,
       devicePort: devicePort,
       ipv6: ipv6,
+      logger: logger ?? globals.logger,
     );
   }
 
@@ -59,6 +63,7 @@
   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 @@
       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);
     }
 
diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart
index 91550a6..70233f0 100644
--- a/packages/flutter_tools/lib/src/windows/windows_device.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_device.dart
@@ -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 @@
 
 /// 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 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 @@
       return const <Device>[];
     }
     return <Device>[
-      WindowsDevice(),
+      WindowsDevice(
+        fileSystem: _fileSystem,
+        logger: _logger,
+        processManager: _processManager,
+        operatingSystemUtils: _operatingSystemUtils,
+      ),
     ];
   }
 
diff --git a/packages/flutter_tools/lib/src/windows/windows_workflow.dart b/packages/flutter_tools/lib/src/windows/windows_workflow.dart
index 72dddea..2144f62 100644
--- a/packages/flutter_tools/lib/src/windows/windows_workflow.dart
+++ b/packages/flutter_tools/lib/src/windows/windows_workflow.dart
@@ -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;
diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart
index 2a2b89e..a7f3cf3 100644
--- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart
@@ -3,31 +3,168 @@
 // 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';
+
+void main() {
+  group('Basic info', () {
+    testWithoutContext('Category is desktop', () async {
+      final FakeDesktopDevice device = setUpDesktopDevice();
+
+      expect(device.category, Category.desktop);
+    });
+
+    testWithoutContext('Not an emulator', () async {
+      final FakeDesktopDevice device = setUpDesktopDevice();
+
+      expect(await device.isLocalEmulator, false);
+      expect(await device.emulatorId, null);
+    });
+
+    testWithoutContext('Uses OS name as SDK name', () async {
+      final FakeDesktopDevice device = setUpDesktopDevice();
+
+      expect(await device.sdkNameAndVersion, 'Example');
+    });
+  });
+
+  group('Install', () {
+    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);
+    });
+
+    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);
+
+      expect(await device.installApp(package), true);
+      expect(await device.isAppInstalled(package), true);
+      expect(await device.isLatestBuildInstalled(package), true);
+      expect(device.category, Category.desktop);
+    });
+  });
+
+  group('Starting and stopping application', () {
+    testWithoutContext('Stop without start is a successful no-op', () async {
+      final FakeDesktopDevice device = setUpDesktopDevice();
+      final FakeAppplicationPackage package = FakeAppplicationPackage();
+
+      expect(await device.stopApp(package), true);
+    });
+
+    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'));
+    });
+
+    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(logger.errorText, contains('Unable to find executable to run'));
+    });
+
+    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);
+    });
+  });
+
+  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() : super(
+  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].
@@ -36,6 +173,8 @@
   /// The [buildInfo] last passed to [buildForDevice].
   BuildInfo lastBuildInfo;
 
+  final bool nullExecutablePathForDevice;
+
   @override
   String get name => 'dummy';
 
@@ -61,130 +200,15 @@
   // 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);
   }
 }
 
-/// A desktop device that returns a null executable path, for failure testing.
-class NullExecutableDesktopDevice extends FakeDesktopDevice {
+class FakeAppplicationPackage extends Fake implements ApplicationPackage {}
+class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
   @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();
-      expect(device.category, Category.desktop);
-    });
-
-    test('Not an emulator', () async {
-      final FakeDesktopDevice device = FakeDesktopDevice();
-      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);
-    });
-  });
-
-  group('Install', () {
-    test('Install checks always return true', () async {
-      final FakeDesktopDevice device = FakeDesktopDevice();
-      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();
-      expect(await device.uninstallApp(package), true);
-      expect(await device.isAppInstalled(package), true);
-      expect(await device.isLatestBuildInstalled(package), true);
-
-      expect(await device.installApp(package), true);
-      expect(await device.isAppInstalled(package), true);
-      expect(await device.isLatestBuildInstalled(package), true);
-      expect(device.category, Category.desktop);
-    });
-  });
-
-  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, '', '');
-      });
-    }
-
-    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);
-      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();
-      final LaunchResult result = await device.startApp(package, prebuiltApplication: true);
-      expect(result.started, false);
-      expect(testLogger.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);
-      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();
-    final DevicePortForwarder portForwarder = device.portForwarder;
-    final int result = await portForwarder.forward(2);
-    expect(result, 2);
-    expect(portForwarder.forwardedPorts.isEmpty, true);
-  });
+  String get name => 'Example';
 }
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 a6c75eb..2767cd9 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
@@ -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 @@
       processManager: FakeProcessManager.any(),
       logger: BufferLogger.test(),
       fileSystem: MemoryFileSystem.test(),
+      operatingSystemUtils: FakeOperatingSystemUtils(),
     );
 
     final PrebuiltLinuxApp linuxApp = PrebuiltLinuxApp(executable: 'foo');
@@ -57,6 +58,7 @@
       featureFlags: TestFeatureFlags(isLinuxEnabled: true),
       logger: BufferLogger.test(),
       processManager: FakeProcessManager.any(),
+      operatingSystemUtils: FakeOperatingSystemUtils(),
     ).devices, <Device>[]);
   });
 
@@ -67,6 +69,7 @@
       featureFlags: TestFeatureFlags(isLinuxEnabled: false),
       logger: BufferLogger.test(),
       processManager: FakeProcessManager.any(),
+      operatingSystemUtils: FakeOperatingSystemUtils(),
     ).devices, <Device>[]);
   });
 
@@ -77,6 +80,7 @@
       featureFlags: TestFeatureFlags(isLinuxEnabled: true),
       logger: BufferLogger.test(),
       processManager: FakeProcessManager.any(),
+      operatingSystemUtils: FakeOperatingSystemUtils(),
     ).devices, hasLength(1));
   });
 
@@ -88,47 +92,47 @@
       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 @@
     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';
+}
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 90bba3e..8099e78 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
@@ -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 @@
       processManager: FakeProcessManager.any(),
       logger: BufferLogger.test(),
       fileSystem: MemoryFileSystem.test(),
+      operatingSystemUtils: FakeOperatingSystemUtils(),
     );
     final MockMacOSApp mockMacOSApp = MockMacOSApp();
 
@@ -54,7 +52,7 @@
     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 @@
         )
       ]),
       logger: BufferLogger.test(),
+      operatingSystemUtils: FakeOperatingSystemUtils(),
     );
     final MockMacOSApp mockMacOSApp = MockMacOSApp();
     when(mockMacOSApp.executable(BuildMode.release)).thenReturn('Example.app');
@@ -83,9 +82,6 @@
 
     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 @@
       processManager: FakeProcessManager.any(),
       logger: BufferLogger.test(),
       platform: linux,
+      operatingSystemUtils: FakeOperatingSystemUtils(),
       macOSWorkflow: MacOSWorkflow(
         featureFlags: TestFeatureFlags(isMacOSEnabled: true),
         platform: linux,
@@ -107,6 +104,7 @@
       processManager: FakeProcessManager.any(),
       logger: BufferLogger.test(),
       platform: macOS,
+      operatingSystemUtils: FakeOperatingSystemUtils(),
       macOSWorkflow: MacOSWorkflow(
         featureFlags: TestFeatureFlags(isMacOSEnabled: false),
         platform: macOS,
@@ -122,6 +120,7 @@
       processManager: FakeProcessManager.any(),
       logger: BufferLogger.test(),
       platform: macOS,
+      operatingSystemUtils: FakeOperatingSystemUtils(),
       macOSWorkflow: MacOSWorkflow(
         featureFlags: TestFeatureFlags(isMacOSEnabled: true),
         platform: macOS,
@@ -137,6 +136,7 @@
       processManager: FakeProcessManager.any(),
       logger: BufferLogger.test(),
       platform: macOS,
+      operatingSystemUtils: FakeOperatingSystemUtils(),
       macOSWorkflow: MacOSWorkflow(
         featureFlags: TestFeatureFlags(isMacOSEnabled: true),
         platform: macOS,
@@ -149,38 +149,36 @@
     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 @@
       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 @@
     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 {}
diff --git a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
index 77c9a06..c46e9a2 100644
--- a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart
@@ -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/testbed.dart';
 
 void main() {
-  group(WindowsDevice, () {
-    final WindowsDevice device = WindowsDevice();
+  testWithoutContext('WindowsDevice defaults', () async {
+    final WindowsDevice windowsDevice = setUpWindowsDevice();
+    final PrebuiltWindowsApp windowsApp = PrebuiltWindowsApp(executable: 'foo');
 
-    final MockPlatform notWindows = MockPlatform();
-    when(notWindows.isWindows).thenReturn(false);
-    when(notWindows.environment).thenReturn(const <String, String>{});
+    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);
 
-    final MockPlatform mockWindowsPlatform = MockPlatform();
-    when(mockWindowsPlatform.isWindows).thenReturn(true);
+    expect(windowsDevice.supportsRuntimeMode(BuildMode.debug), true);
+    expect(windowsDevice.supportsRuntimeMode(BuildMode.profile), true);
+    expect(windowsDevice.supportsRuntimeMode(BuildMode.release), true);
+    expect(windowsDevice.supportsRuntimeMode(BuildMode.jitRelease), false);
+  });
 
-    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);
+  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>[]);
+  });
 
-      expect(device.supportsRuntimeMode(BuildMode.debug), true);
-      expect(device.supportsRuntimeMode(BuildMode.profile), true);
-      expect(device.supportsRuntimeMode(BuildMode.release), true);
-      expect(device.supportsRuntimeMode(BuildMode.jitRelease), false);
-    });
+  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));
+  });
 
-    testUsingContext('No devices listed if platform unsupported', () async {
-      expect(await WindowsDevices().devices, <Device>[]);
-    }, overrides: <Type, Generator>{
-      Platform: () => notWindows,
-    });
+  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));
+  });
 
-    testUsingContext('WindowsDevices: devices', () async {
-      expect(await WindowsDevices().devices, hasLength(1));
-    }, overrides: <Type, Generator>{
-      Platform: () => mockWindowsPlatform,
-      FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
-    });
+  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);
 
-    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),
-    });
+    expect(windowsDevice.isSupportedForProject(flutterProject), 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();
+  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), true);
-    }, overrides: <Type, Generator>{
-      FileSystem: () => MemoryFileSystem.test(),
-      ProcessManager: () => FakeProcessManager.any(),
-    });
+    expect(windowsDevice.isSupportedForProject(flutterProject), false);
+  });
 
-    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();
+  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);
-    }, overrides: <Type, Generator>{
-      FileSystem: () => MemoryFileSystem.test(),
-      ProcessManager: () => FakeProcessManager.any(),
-    });
+    expect(windowsDevice.isSupportedForProject(flutterProject), false);
+  });
 
-    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();
+  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().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(),
-    });
+    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 {}
diff --git a/packages/flutter_tools/test/general.shard/windows/windows_workflow_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_workflow_test.dart
index dd4d485..45f4bd2 100644
--- a/packages/flutter_tools/test/general.shard/windows/windows_workflow_test.dart
+++ b/packages/flutter_tools/test/general.shard/windows/windows_workflow_test.dart
@@ -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;
+  final FakePlatform windows = FakePlatform(operatingSystem: 'windows');
+  final FakePlatform notWindows = FakePlatform(operatingSystem: 'linux');
 
-  setUp(() {
-    windows = FakePlatform(operatingSystem: 'windows');
-    notWindows = FakePlatform(operatingSystem: 'linux');
-    testbed = Testbed(
-      overrides: <Type, Generator>{
-        Platform: () => windows,
-        FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
-      },
+  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);
-  }));
+  });
 
-  test('Windows defaults on non-windows platform', () => testbed.run(() {
+  testWithoutContext('Windows workflow configuration when feature is disabled on Windows host machine', () {
+    final WindowsWorkflow windowsWorkflow = WindowsWorkflow(
+      platform: windows,
+      featureFlags: TestFeatureFlags(isWindowsEnabled: false),
+    );
+
     expect(windowsWorkflow.appliesToHostPlatform, false);
     expect(windowsWorkflow.canListDevices, false);
     expect(windowsWorkflow.canLaunchDevices, false);
     expect(windowsWorkflow.canListEmulators, false);
-  }, overrides: <Type, Generator>{
-    Platform: () => notWindows,
-  }));
+  });
 
-  test('Windows defaults on non-windows platform', () => testbed.run(() {
+  testWithoutContext('Windows workflow configuration when feature is enabled on non-Windows host machine', () {
+    final WindowsWorkflow windowsWorkflow = WindowsWorkflow(
+      platform: notWindows,
+      featureFlags: TestFeatureFlags(isWindowsEnabled: true),
+    );
+
     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);
+  });
 }
diff --git a/packages/flutter_tools/test/src/fake_process_manager.dart b/packages/flutter_tools/test/src/fake_process_manager.dart
index 26a29ce..140249b 100644
--- a/packages/flutter_tools/test/src/fake_process_manager.dart
+++ b/packages/flutter_tools/test/src/fake_process_manager.dart
@@ -123,13 +123,13 @@
     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 @@
         : Stream<List<int>>.value(utf8.encode(_stdout));
 
   final int _exitCode;
+  final Completer<void> _completer;
 
   @override
   final Future<int> exitCode;
@@ -205,6 +206,8 @@
     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 @@
     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 @@
 
   @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;
   }
 }