[flutter_tools] reduce globals in web validator and chrome launcher (#51443)

diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 922f45c..b9a3297 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -101,7 +101,13 @@
         logger: globals.logger,
         platform: globals.platform,
       ),
-      ChromeLauncher: () => const ChromeLauncher(),
+      ChromeLauncher: () => ChromeLauncher(
+        fileSystem: globals.fs,
+        processManager: globals.processManager,
+        logger: globals.logger,
+        operatingSystemUtils: globals.os,
+        platform: globals.platform,
+      ),
       CocoaPods: () => CocoaPods(),
       CocoaPodsValidator: () => const CocoaPodsValidator(),
       Config: () => Config(
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 9078488..da35d54 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -73,7 +73,11 @@
       if (iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform)
         GroupedValidator(<DoctorValidator>[xcodeValidator, cocoapodsValidator]),
       if (webWorkflow.appliesToHostPlatform)
-        const WebValidator(),
+        WebValidator(
+          chromeLauncher: globals.chromeLauncher,
+          platform: globals.platform,
+          fileSystem: globals.fs,
+        ),
       if (linuxWorkflow.appliesToHostPlatform)
         LinuxDoctorValidator(),
       if (windowsWorkflow.appliesToHostPlatform)
diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart
index 68cda51..4342c73 100644
--- a/packages/flutter_tools/lib/src/globals.dart
+++ b/packages/flutter_tools/lib/src/globals.dart
@@ -25,6 +25,7 @@
 import 'macos/xcode.dart';
 import 'persistent_tool_state.dart';
 import 'version.dart';
+import 'web/chrome.dart';
 
 Artifacts get artifacts => context.get<Artifacts>();
 Cache get cache => context.get<Cache>();
@@ -148,3 +149,6 @@
 
 /// The global Stdio wrapper.
 Stdio get stdio => context.get<Stdio>() ?? const Stdio();
+
+/// The [ChromeLauncher] instance.
+ChromeLauncher get chromeLauncher => context.get<ChromeLauncher>();
diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
index 9ee248a..30885f9 100644
--- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart
@@ -642,7 +642,7 @@
     bool headless = true,
   }) async {
     final Chrome chrome =
-        await chromeLauncher.launch(url.toString(), headless: headless);
+        await globals.chromeLauncher.launch(url.toString(), headless: headless);
 
     final Completer<BrowserManager> completer = Completer<BrowserManager>();
 
diff --git a/packages/flutter_tools/lib/src/web/chrome.dart b/packages/flutter_tools/lib/src/web/chrome.dart
index ee54401..f285496 100644
--- a/packages/flutter_tools/lib/src/web/chrome.dart
+++ b/packages/flutter_tools/lib/src/web/chrome.dart
@@ -5,17 +5,16 @@
 import 'dart:async';
 
 import 'package:meta/meta.dart';
+import 'package:platform/platform.dart';
+import 'package:process/process.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
 import '../base/common.dart';
-import '../base/context.dart';
 import '../base/file_system.dart';
 import '../base/io.dart';
+import '../base/logger.dart';
+import '../base/os.dart';
 import '../convert.dart';
-import '../globals.dart' as globals;
-
-/// The [ChromeLauncher] instance.
-ChromeLauncher get chromeLauncher => context.get<ChromeLauncher>();
 
 /// An environment variable used to override the location of chrome.
 const String kChromeEnvironment = 'CHROME_EXECUTABLE';
@@ -30,37 +29,36 @@
 /// The expected executable name on Windows.
 const String kWindowsExecutable = r'Google\Chrome\Application\chrome.exe';
 
-/// The possible locations where the chrome executable can be located on windows.
-final List<String> kWindowsPrefixes = <String>[
-  globals.platform.environment['LOCALAPPDATA'],
-  globals.platform.environment['PROGRAMFILES'],
-  globals.platform.environment['PROGRAMFILES(X86)'],
-];
-
 /// Find the chrome executable on the current platform.
 ///
 /// Does not verify whether the executable exists.
-String findChromeExecutable() {
-  if (globals.platform.environment.containsKey(kChromeEnvironment)) {
-    return globals.platform.environment[kChromeEnvironment];
+String findChromeExecutable(Platform platform, FileSystem fileSystem) {
+  if (platform.environment.containsKey(kChromeEnvironment)) {
+    return platform.environment[kChromeEnvironment];
   }
-  if (globals.platform.isLinux) {
+  if (platform.isLinux) {
     return kLinuxExecutable;
   }
-  if (globals.platform.isMacOS) {
+  if (platform.isMacOS) {
     return kMacOSExecutable;
   }
-  if (globals.platform.isWindows) {
+  if (platform.isWindows) {
+    /// The possible locations where the chrome executable can be located on windows.
+    final List<String> kWindowsPrefixes = <String>[
+      platform.environment['LOCALAPPDATA'],
+      platform.environment['PROGRAMFILES'],
+      platform.environment['PROGRAMFILES(X86)'],
+    ];
     final String windowsPrefix = kWindowsPrefixes.firstWhere((String prefix) {
       if (prefix == null) {
         return false;
       }
-      final String path = globals.fs.path.join(prefix, kWindowsExecutable);
-      return globals.fs.file(path).existsSync();
+      final String path = fileSystem.path.join(prefix, kWindowsExecutable);
+      return fileSystem.file(path).existsSync();
     }, orElse: () => '.');
-    return globals.fs.path.join(windowsPrefix, kWindowsExecutable);
+    return fileSystem.path.join(windowsPrefix, kWindowsExecutable);
   }
-  throwToolExit('Platform ${globals.platform.operatingSystem} is not supported.');
+  throwToolExit('Platform ${platform.operatingSystem} is not supported.');
   return null;
 }
 
@@ -76,7 +74,23 @@
 
 /// Responsible for launching chrome with devtools configured.
 class ChromeLauncher {
-  const ChromeLauncher();
+  const ChromeLauncher({
+    @required FileSystem fileSystem,
+    @required Platform platform,
+    @required ProcessManager processManager,
+    @required OperatingSystemUtils operatingSystemUtils,
+    @required Logger logger,
+  }) : _fileSystem = fileSystem,
+       _platform = platform,
+       _processManager = processManager,
+       _operatingSystemUtils = operatingSystemUtils,
+       _logger = logger;
+
+  final FileSystem _fileSystem;
+  final Platform _platform;
+  final ProcessManager _processManager;
+  final OperatingSystemUtils _operatingSystemUtils;
+  final Logger _logger;
 
   static bool get hasChromeInstance => _currentCompleter.isCompleted;
 
@@ -84,9 +98,9 @@
 
   /// Whether we can locate the chrome executable.
   bool canFindChrome() {
-    final String chrome = findChromeExecutable();
+    final String chrome = findChromeExecutable(_platform, _fileSystem);
     try {
-      return globals.processManager.canRun(chrome);
+      return _processManager.canRun(chrome);
     } on ArgumentError {
       return false;
     }
@@ -105,14 +119,14 @@
     // This is a JSON file which contains configuration from the
     // browser session, such as window position. It is located
     // under the Chrome data-dir folder.
-    final String preferencesPath = globals.fs.path.join('Default', 'preferences');
+    final String preferencesPath = _fileSystem.path.join('Default', 'preferences');
 
-    final String chromeExecutable = findChromeExecutable();
-    final Directory activeDataDir = globals.fs.systemTempDirectory.createTempSync('flutter_tool.');
+    final String chromeExecutable = findChromeExecutable(_platform, _fileSystem);
+    final Directory activeDataDir = _fileSystem.systemTempDirectory.createTempSync('flutter_tool.');
     // Seed data dir with previous state.
 
-    final File savedPreferencesFile = globals.fs.file(globals.fs.path.join(dataDir?.path ?? '', preferencesPath));
-    final File destinationFile = globals.fs.file(globals.fs.path.join(activeDataDir.path, preferencesPath));
+    final File savedPreferencesFile = _fileSystem.file(_fileSystem.path.join(dataDir?.path ?? '', preferencesPath));
+    final File destinationFile = _fileSystem.file(_fileSystem.path.join(activeDataDir.path, preferencesPath));
     if (dataDir != null) {
       if (savedPreferencesFile.existsSync()) {
         destinationFile.parent.createSync(recursive: true);
@@ -120,7 +134,7 @@
       }
     }
 
-    final int port = debugPort ?? await globals.os.findFreePort();
+    final int port = debugPort ?? await _operatingSystemUtils.findFreePort();
     final List<String> args = <String>[
       chromeExecutable,
       // Using a tmp directory ensures that a new instance of chrome launches
@@ -143,7 +157,7 @@
       url,
     ];
 
-    final Process process = await globals.processManager.start(args);
+    final Process process = await _processManager.start(args);
 
     // When the process exits, copy the user settings back to the provided
     // data-dir.
@@ -164,7 +178,7 @@
       .transform(utf8.decoder)
       .transform(const LineSplitter())
       .listen((String line) {
-        globals.printTrace('[CHROME]: $line');
+        _logger.printTrace('[CHROME]: $line');
       });
 
     // Wait until the DevTools are listening before trying to connect.
@@ -172,7 +186,7 @@
       .transform(utf8.decoder)
       .transform(const LineSplitter())
       .map((String line) {
-        globals.printTrace('[CHROME]:$line');
+        _logger.printTrace('[CHROME]:$line');
         return line;
       })
       .firstWhere((String line) => line.startsWith('DevTools listening'), orElse: () {
diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart
index eb14642..d38e772 100644
--- a/packages/flutter_tools/lib/src/web/web_device.dart
+++ b/packages/flutter_tools/lib/src/web/web_device.dart
@@ -80,7 +80,7 @@
   Future<String> get emulatorId async => null;
 
   @override
-  bool isSupported() =>  featureFlags.isWebEnabled && chromeLauncher.canFindChrome();
+  bool isSupported() =>  featureFlags.isWebEnabled && globals.chromeLauncher.canFindChrome();
 
   @override
   String get name => 'Chrome';
@@ -109,7 +109,7 @@
         }
       }
     } else {
-      final String chrome = findChromeExecutable();
+      final String chrome = findChromeExecutable(globals.platform, globals.fs);
       final ProcessResult result = await globals.processManager.run(<String>[
         chrome,
         '--version',
@@ -136,7 +136,7 @@
     final String url = platformArgs['uri'] as String;
     final bool launchChrome = platformArgs['no-launch-chrome'] != true;
     if (launchChrome) {
-      _chrome = await chromeLauncher.launch(
+      _chrome = await globals.chromeLauncher.launch(
         url,
         dataDir: globals.fs.currentDirectory
             .childDirectory('.dart_tool')
@@ -177,7 +177,7 @@
 class WebDevices extends PollingDeviceDiscovery {
   WebDevices() : super('chrome');
 
-  final bool _chromeIsAvailable = chromeLauncher.canFindChrome();
+  final bool _chromeIsAvailable = globals.chromeLauncher.canFindChrome();
   final ChromeDevice _webDevice = ChromeDevice();
   final WebServerDevice _webServerDevice = WebServerDevice();
 
diff --git a/packages/flutter_tools/lib/src/web/web_validator.dart b/packages/flutter_tools/lib/src/web/web_validator.dart
index b96ae3e..d18450d 100644
--- a/packages/flutter_tools/lib/src/web/web_validator.dart
+++ b/packages/flutter_tools/lib/src/web/web_validator.dart
@@ -2,20 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:meta/meta.dart';
+import 'package:platform/platform.dart';
+
+import '../base/file_system.dart';
 import '../doctor.dart';
-import '../globals.dart' as globals;
 import 'chrome.dart';
 
 /// A validator that checks whether chrome is installed and can run.
 class WebValidator extends DoctorValidator {
-  const WebValidator() : super('Chrome - develop for the web');
+  const WebValidator({
+    @required Platform platform,
+    @required ChromeLauncher chromeLauncher,
+    @required FileSystem fileSystem,
+  }) : _platform = platform,
+       _chromeLauncher = chromeLauncher,
+       _fileSystem = fileSystem,
+       super('Chrome - develop for the web');
+
+  final Platform _platform;
+  final ChromeLauncher _chromeLauncher;
+  final FileSystem _fileSystem;
 
   @override
   Future<ValidationResult> validate() async {
-    final String chrome = findChromeExecutable();
-    final bool canRunChrome = chromeLauncher.canFindChrome();
+    final String chrome = findChromeExecutable(_platform, _fileSystem);
+    final bool canRunChrome = _chromeLauncher.canFindChrome();
     final List<ValidationMessage> messages = <ValidationMessage>[
-      if (globals.platform.environment.containsKey(kChromeEnvironment))
+      if (_platform.environment.containsKey(kChromeEnvironment))
         if (!canRunChrome)
           ValidationMessage.hint('$chrome is not executable.')
         else
diff --git a/packages/flutter_tools/test/general.shard/web/chrome_test.dart b/packages/flutter_tools/test/general.shard/web/chrome_test.dart
index 9ccea6e..56c6ac8 100644
--- a/packages/flutter_tools/test/general.shard/web/chrome_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/chrome_test.dart
@@ -71,7 +71,7 @@
   }
 
   test('can launch chrome and connect to the devtools', () => testbed.run(() async {
-    await chromeLauncher.launch('example_url', skipCheck: true);
+    await globals.chromeLauncher.launch('example_url', skipCheck: true);
 
     final VerificationResult result = verify(globals.processManager.start(captureAny));
     expect(result.captured.single, containsAll(expectChromeArgs()));
@@ -79,7 +79,7 @@
   }));
 
   test('can launch chrome with a custom debug port', () => testbed.run(() async {
-    await chromeLauncher.launch('example_url', skipCheck: true, debugPort: 10000);
+    await globals.chromeLauncher.launch('example_url', skipCheck: true, debugPort: 10000);
     final VerificationResult result = verify(globals.processManager.start(captureAny));
 
     expect(result.captured.single, containsAll(expectChromeArgs(debugPort: 10000)));
@@ -87,7 +87,7 @@
   }));
 
   test('can launch chrome headless', () => testbed.run(() async {
-    await chromeLauncher.launch('example_url', skipCheck: true, headless: true);
+    await globals.chromeLauncher.launch('example_url', skipCheck: true, headless: true);
     final VerificationResult result = verify(globals.processManager.start(captureAny));
 
     expect(result.captured.single, containsAll(expectChromeArgs()));
@@ -104,7 +104,7 @@
       ..createSync(recursive: true)
       ..writeAsStringSync('example');
 
-    await chromeLauncher.launch('example_url', skipCheck: true, dataDir: dataDir);
+    await globals.chromeLauncher.launch('example_url', skipCheck: true, dataDir: dataDir);
     final VerificationResult result = verify(globals.processManager.start(captureAny));
     final String arg = (result.captured.single as List<String>)
       .firstWhere((String arg) => arg.startsWith('--user-data-dir='));
diff --git a/packages/flutter_tools/test/general.shard/web/web_validator_test.dart b/packages/flutter_tools/test/general.shard/web/web_validator_test.dart
index d9ed734..b2bb2ba 100644
--- a/packages/flutter_tools/test/general.shard/web/web_validator_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/web_validator_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/doctor.dart';
 import 'package:flutter_tools/src/web/chrome.dart';
 import 'package:flutter_tools/src/web/web_validator.dart';
@@ -10,56 +12,63 @@
 import 'package:platform/platform.dart';
 
 import '../../src/common.dart';
-import '../../src/testbed.dart';
+import '../../src/fake_process_manager.dart';
 
 void main() {
-  group('WebValidator', () {
-    Testbed testbed;
-    WebValidator webValidator;
-    MockPlatform mockPlatform;
-    MockProcessManager mockProcessManager;
+  Platform platform;
+  ProcessManager processManager;
+  ChromeLauncher chromeLauncher;
+  FileSystem fileSystem;
+  WebValidator webValidator;
 
-    setUp(() {
-      mockProcessManager = MockProcessManager();
-      testbed = Testbed(setup: () {
-        when(mockProcessManager.canRun(kMacOSExecutable)).thenReturn(true);
-        return null;
-      }, overrides: <Type, Generator>{
-        Platform: () => mockPlatform,
-        ProcessManager: () => mockProcessManager,
-      });
-      webValidator = const WebValidator();
-      mockPlatform = MockPlatform();
-      when(mockPlatform.isMacOS).thenReturn(true);
-      when(mockPlatform.isWindows).thenReturn(false);
-      when(mockPlatform.isLinux).thenReturn(false);
-    });
-
-    test('Can find macOS executable ', () => testbed.run(() async {
-      final ValidationResult result = await webValidator.validate();
-      expect(result.type, ValidationType.installed);
-    }));
-
-    test('Can notice missing macOS executable ', () => testbed.run(() async {
-      when(mockProcessManager.canRun(kMacOSExecutable)).thenReturn(false);
-      final ValidationResult result = await webValidator.validate();
-      expect(result.type, ValidationType.missing);
-    }));
-
-    test("Doesn't warn about CHROME_EXECUTABLE unless it cant find chrome ", () => testbed.run(() async {
-      when(mockProcessManager.canRun(kMacOSExecutable)).thenReturn(false);
-      final ValidationResult result = await webValidator.validate();
-      expect(result.messages, <ValidationMessage>[
-        ValidationMessage.hint('Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.'),
-      ]);
-      expect(result.type, ValidationType.missing);
-    }));
+  setUp(() {
+    fileSystem = MemoryFileSystem.test();
+    processManager = MockProcessManager();
+    platform = FakePlatform(
+      operatingSystem: 'macos',
+      environment: <String, String>{},
+    );
+    chromeLauncher = ChromeLauncher(
+      fileSystem: fileSystem,
+      platform: platform,
+      processManager: processManager,
+      operatingSystemUtils: null,
+      logger: null,
+    );
+    webValidator = webValidator = WebValidator(
+      platform: platform,
+      chromeLauncher: chromeLauncher,
+      fileSystem: fileSystem,
+    );
   });
-}
 
-class MockPlatform extends Mock implements Platform  {
-  @override
-  Map<String, String> get environment => const <String, String>{};
+  testWithoutContext('WebValidator can find executable on macOS', () async {
+    when(processManager.canRun(kMacOSExecutable)).thenReturn(true);
+
+    final ValidationResult result = await webValidator.validate();
+
+    expect(result.type, ValidationType.installed);
+  });
+
+  testWithoutContext('WebValidator Can notice missing macOS executable ', () async {
+    when(processManager.canRun(kMacOSExecutable)).thenReturn(false);
+
+    final ValidationResult result = await webValidator.validate();
+
+    expect(result.type, ValidationType.missing);
+  });
+
+  testWithoutContext('WebValidator does not warn about CHROME_EXECUTABLE unless it cant find chrome ', () async {
+    when(processManager.canRun(kMacOSExecutable)).thenReturn(false);
+
+    final ValidationResult result = await webValidator.validate();
+
+    expect(result.messages, <ValidationMessage>[
+      ValidationMessage.hint(
+          'Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.'),
+    ]);
+    expect(result.type, ValidationType.missing);
+  });
 }
 
 class MockProcessManager extends Mock implements ProcessManager {}