[flutter_tools] remove some globals from flutter_tester device (#60787)

Remove globals from flutter_tester device and cleanup test case. Not completely gone since the Kernel Builder will still use them, but a good incremental improvement.
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index cc269c3..6cc8df0 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -51,7 +51,7 @@
             ? await IOSApp.fromIosProject(FlutterProject.current().ios, buildInfo)
             : IOSApp.fromPrebuiltApp(applicationBinary);
       case TargetPlatform.tester:
-        return FlutterTesterApp.fromCurrentDirectory();
+        return FlutterTesterApp.fromCurrentDirectory(globals.fs);
       case TargetPlatform.darwin_x64:
         return applicationBinary == null
             ? MacOSApp.fromMacOSProject(FlutterProject.current().macos)
diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
index f786ccb..2057c25 100644
--- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart
+++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
@@ -5,12 +5,13 @@
 import 'dart:async';
 
 import 'package:meta/meta.dart';
+import 'package:process/process.dart';
 
 import '../application_package.dart';
 import '../artifacts.dart';
-import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/io.dart';
+import '../base/logger.dart';
 import '../build_info.dart';
 import '../bundle.dart';
 import '../convert.dart';
@@ -21,8 +22,8 @@
 import '../version.dart';
 
 class FlutterTesterApp extends ApplicationPackage {
-  factory FlutterTesterApp.fromCurrentDirectory() {
-    return FlutterTesterApp._(globals.fs.currentDirectory);
+  factory FlutterTesterApp.fromCurrentDirectory(FileSystem fileSystem) {
+    return FlutterTesterApp._(fileSystem.currentDirectory);
   }
 
   FlutterTesterApp._(Directory directory)
@@ -38,17 +39,42 @@
   File get packagesFile => _directory.childFile('.packages');
 }
 
+/// The device interface for running on the flutter_tester shell.
+///
+/// Normally this is only used as the runner for `flutter test`, but it can
+/// also be used as a regular device when `--show-test-device` is provided
+/// to the flutter command.
 // TODO(scheglov): This device does not currently work with full restarts.
 class FlutterTesterDevice extends Device {
-  FlutterTesterDevice(String deviceId) : super(
-      deviceId,
-      platformType: null,
-      category: null,
-      ephemeral: false,
-  );
+  FlutterTesterDevice(String deviceId, {
+    @required ProcessManager processManager,
+    @required FlutterVersion flutterVersion,
+    @required Logger logger,
+    @required String buildDirectory,
+    @required FileSystem fileSystem,
+    @required Artifacts artifacts,
+  }) : _processManager = processManager,
+       _flutterVersion = flutterVersion,
+       _logger = logger,
+       _buildDirectory = buildDirectory,
+       _fileSystem = fileSystem,
+       _artifacts = artifacts,
+       super(
+        deviceId,
+        platformType: null,
+        category: null,
+        ephemeral: false,
+      );
+
+  final ProcessManager _processManager;
+  final FlutterVersion _flutterVersion;
+  final Logger _logger;
+  final String _buildDirectory;
+  final FileSystem _fileSystem;
+  final Artifacts _artifacts;
 
   Process _process;
-  final DevicePortForwarder _portForwarder = _NoopPortForwarder();
+  final DevicePortForwarder _portForwarder = const NoOpDevicePortForwarder();
 
   @override
   Future<bool> get isLocalEmulator async => false;
@@ -64,7 +90,7 @@
 
   @override
   Future<String> get sdkNameAndVersion async {
-    final FlutterVersion flutterVersion = globals.flutterVersion;
+    final FlutterVersion flutterVersion = _flutterVersion;
     return 'Flutter ${flutterVersion.frameworkRevisionShort}';
   }
 
@@ -106,9 +132,6 @@
   @override
   bool isSupported() => true;
 
-  bool _isRunning = false;
-  bool get isRunning => _isRunning;
-
   @override
   Future<LaunchResult> startApp(
     ApplicationPackage package, {
@@ -121,42 +144,17 @@
     String userIdentifier,
   }) async {
     final BuildInfo buildInfo = debuggingOptions.buildInfo;
-
     if (!buildInfo.isDebug) {
-      globals.printError('This device only supports debug mode.');
+      _logger.printError('This device only supports debug mode.');
       return LaunchResult.failed();
     }
 
-    final String shellPath = globals.artifacts.getArtifactPath(Artifact.flutterTester);
-    if (!globals.fs.isFileSync(shellPath)) {
-      throwToolExit('Cannot find Flutter shell at $shellPath');
-    }
-
-    final List<String> command = <String>[
-      shellPath,
-      '--run-forever',
-      '--non-interactive',
-      '--enable-dart-profiling',
-      '--packages=${debuggingOptions.buildInfo.packagesPath}',
-    ];
-    if (debuggingOptions.debuggingEnabled) {
-      if (debuggingOptions.startPaused) {
-        command.add('--start-paused');
-      }
-      if (debuggingOptions.disableServiceAuthCodes) {
-        command.add('--disable-service-auth-codes');
-      }
-      if (debuggingOptions.hasObservatoryPort) {
-        command.add('--observatory-port=${debuggingOptions.hostVmServicePort}');
-      }
-    }
-
-    // Build assets and perform initial compilation.
-    final String assetDirPath = getAssetBuildDirectory();
+    final String assetDirPath = _fileSystem.path.join(_buildDirectory, 'flutter_assets');
     final String applicationKernelFilePath = getKernelPathForTransformerOptions(
-      globals.fs.path.join(getBuildDirectory(), 'flutter-tester-app.dill'),
+      _fileSystem.path.join(_buildDirectory, 'flutter-tester-app.dill'),
       trackWidgetCreation: buildInfo.trackWidgetCreation,
     );
+    // Build assets and perform initial compilation.
     await BundleBuilder().build(
       buildInfo: buildInfo,
       mainPath: mainPath,
@@ -167,34 +165,39 @@
       platform: getTargetPlatformForName(getNameForHostPlatform(getCurrentHostPlatform())),
       treeShakeIcons: buildInfo.treeShakeIcons,
     );
-    command.add('--flutter-assets-dir=$assetDirPath');
 
-    command.add(applicationKernelFilePath);
+    final List<String> command = <String>[
+      _artifacts.getArtifactPath(Artifact.flutterTester),
+      '--run-forever',
+      '--non-interactive',
+      '--enable-dart-profiling',
+      '--packages=${debuggingOptions.buildInfo.packagesPath}',
+      '--flutter-assets-dir=$assetDirPath',
+      if (debuggingOptions.startPaused)
+        '--start-paused',
+      if (debuggingOptions.disableServiceAuthCodes)
+        '--disable-service-auth-codes',
+      if (debuggingOptions.hasObservatoryPort)
+        '--observatory-port=${debuggingOptions.hostVmServicePort}',
+      applicationKernelFilePath
+    ];
 
     ProtocolDiscovery observatoryDiscovery;
     try {
-      globals.printTrace(command.join(' '));
-
-      _isRunning = true;
-      _process = await globals.processManager.start(command,
+      _logger.printTrace(command.join(' '));
+      _process = await _processManager.start(command,
         environment: <String, String>{
           'FLUTTER_TEST': 'true',
         },
       );
-      // Setting a bool can't fail in the callback.
-      unawaited(_process.exitCode.then<void>((_) => _isRunning = false));
       _process.stdout
         .transform<String>(utf8.decoder)
         .transform<String>(const LineSplitter())
-        .listen((String line) {
-          _logReader.addLine(line);
-        });
+        .listen(_logReader.addLine);
       _process.stderr
         .transform<String>(utf8.decoder)
         .transform<String>(const LineSplitter())
-        .listen((String line) {
-          _logReader.addLine(line);
-        });
+        .listen(_logReader.addLine);
 
       if (!debuggingOptions.debuggingEnabled) {
         return LaunchResult.succeeded();
@@ -211,12 +214,12 @@
       if (observatoryUri != null) {
         return LaunchResult.succeeded(observatoryUri: observatoryUri);
       }
-      globals.printError(
+      _logger.printError(
         'Failed to launch $package: '
         'The log reader failed unexpectedly.',
       );
     } on Exception catch (error) {
-      globals.printError('Failed to launch $package: $error');
+      _logger.printError('Failed to launch $package: $error');
     } finally {
       await observatoryDiscovery?.cancel();
     }
@@ -256,8 +259,15 @@
 
   static bool showFlutterTesterDevice = false;
 
-  final FlutterTesterDevice _testerDevice =
-      FlutterTesterDevice(kTesterDeviceId);
+  final FlutterTesterDevice _testerDevice = FlutterTesterDevice(
+    kTesterDeviceId,
+    fileSystem: globals.fs,
+    artifacts: globals.artifacts,
+    processManager: globals.processManager,
+    buildDirectory: getBuildDirectory(),
+    logger: globals.logger,
+    flutterVersion: globals.flutterVersion,
+  );
 
   @override
   bool get canListAnything => true;
@@ -289,24 +299,3 @@
   @override
   void dispose() {}
 }
-
-/// A fake port forwarder that doesn't do anything. Used by flutter tester
-/// where the VM is running on the same machine and does not need ports forwarding.
-class _NoopPortForwarder extends DevicePortForwarder {
-  @override
-  Future<int> forward(int devicePort, { int hostPort }) {
-    if (hostPort != null && hostPort != devicePort) {
-      throw 'Forwarding to a different port is not supported by flutter tester';
-    }
-    return Future<int>.value(devicePort);
-  }
-
-  @override
-  List<ForwardedPort> get forwardedPorts => <ForwardedPort>[];
-
-  @override
-  Future<void> unforward(ForwardedPort forwardedPort) async { }
-
-  @override
-  Future<void> dispose() async { }
-}
diff --git a/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart b/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart
index a3545a1..61d522e 100644
--- a/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart
+++ b/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart
@@ -7,6 +7,7 @@
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/artifacts.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/build_system/build_system.dart';
@@ -23,22 +24,18 @@
   MemoryFileSystem fileSystem;
 
   setUp(() {
-    fileSystem = MemoryFileSystem();
+    fileSystem = MemoryFileSystem.test();
   });
 
-  group('FlutterTesterApp', () {
-    testUsingContext('fromCurrentDirectory', () async {
-      const String projectPath = '/home/my/projects/my_project';
-      await fileSystem.directory(projectPath).create(recursive: true);
-      fileSystem.currentDirectory = projectPath;
+  testWithoutContext('FlutterTesterApp can be created from the current directory', () async {
+    const String projectPath = '/home/my/projects/my_project';
+    await fileSystem.directory(projectPath).create(recursive: true);
+    fileSystem.currentDirectory = projectPath;
 
-      final FlutterTesterApp app = FlutterTesterApp.fromCurrentDirectory();
-      expect(app.name, 'my_project');
-      expect(app.packagesFile.path, fileSystem.path.join(projectPath, '.packages'));
-    }, overrides: <Type, Generator>{
-      FileSystem: () => fileSystem,
-      ProcessManager: () => FakeProcessManager.any(),
-    });
+    final FlutterTesterApp app = FlutterTesterApp.fromCurrentDirectory(fileSystem);
+
+    expect(app.name, 'my_project');
+    expect(app.packagesFile.path, fileSystem.path.join(projectPath, '.packages'));
   });
 
   group('FlutterTesterDevices', () {
@@ -74,19 +71,48 @@
       expect(devices, hasLength(1));
     });
   });
-
-  group('FlutterTesterDevice', () {
+  group('startApp', () {
     FlutterTesterDevice device;
     List<String> logLines;
+    String mainPath;
+
+    MockProcessManager mockProcessManager;
+    MockProcess mockProcess;
+    MockBuildSystem mockBuildSystem;
+
+    final Map<Type, Generator> startOverrides = <Type, Generator>{
+      Platform: () => FakePlatform(operatingSystem: 'linux'),
+      FileSystem: () => fileSystem,
+      ProcessManager: () => mockProcessManager,
+      Artifacts: () => Artifacts.test(),
+      BuildSystem: () => mockBuildSystem,
+    };
 
     setUp(() {
-      device = FlutterTesterDevice('flutter-tester');
+      mockBuildSystem = MockBuildSystem();
+      mockProcessManager = MockProcessManager();
+      mockProcessManager.processFactory =
+          (List<String> commands) => mockProcess;
 
+      when(mockBuildSystem.build(
+        any,
+        any,
+      )).thenAnswer((_) async {
+        return BuildResult(success: true);
+      });
+      device = FlutterTesterDevice('flutter-tester',
+        fileSystem: fileSystem,
+        processManager: mockProcessManager,
+        artifacts: Artifacts.test(),
+        buildDirectory: 'build',
+        logger: BufferLogger.test(),
+        flutterVersion: MockFlutterVersion(),
+      );
       logLines = <String>[];
       device.getLogReader().logLines.listen(logLines.add);
     });
 
-    testUsingContext('getters', () async {
+    testWithoutContext('default settings', () async {
       expect(device.id, 'flutter-tester');
       expect(await device.isLocalEmulator, isFalse);
       expect(device.name, 'Flutter test device');
@@ -101,90 +127,47 @@
       expect(device.isSupported(), isTrue);
     });
 
-    group('startApp', () {
-      String flutterRoot;
-      String flutterTesterPath;
+    testWithoutContext('does not accept profile, release, or jit-release builds', () async {
+      final LaunchResult releaseResult = await device.startApp(null,
+        mainPath: mainPath,
+        debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
+      );
+      final LaunchResult profileResult = await device.startApp(null,
+        mainPath: mainPath,
+        debuggingOptions: DebuggingOptions.disabled(BuildInfo.profile),
+      );
+      final LaunchResult jitReleaseResult = await device.startApp(null,
+        mainPath: mainPath,
+        debuggingOptions: DebuggingOptions.disabled(BuildInfo.jitRelease),
+      );
 
-      String projectPath;
-      String mainPath;
-
-      MockArtifacts mockArtifacts;
-      MockProcessManager mockProcessManager;
-      MockProcess mockProcess;
-      MockBuildSystem mockBuildSystem;
-
-      final Map<Type, Generator> startOverrides = <Type, Generator>{
-        Platform: () => FakePlatform(operatingSystem: 'linux'),
-        FileSystem: () => fileSystem,
-        ProcessManager: () => mockProcessManager,
-        Artifacts: () => mockArtifacts,
-        BuildSystem: () => mockBuildSystem,
-      };
-
-      setUp(() {
-        mockBuildSystem = MockBuildSystem();
-        flutterRoot = fileSystem.path.join('home', 'me', 'flutter');
-        flutterTesterPath = fileSystem.path.join(flutterRoot, 'bin', 'cache',
-            'artifacts', 'engine', 'linux-x64', 'flutter_tester');
-        final File flutterTesterFile = fileSystem.file(flutterTesterPath);
-        flutterTesterFile.parent.createSync(recursive: true);
-        flutterTesterFile.writeAsBytesSync(const <int>[]);
-
-        projectPath = fileSystem.path.join('home', 'me', 'hello');
-        mainPath = fileSystem.path.join(projectPath, 'lin', 'main.dart');
-
-        mockProcessManager = MockProcessManager();
-        mockProcessManager.processFactory =
-            (List<String> commands) => mockProcess;
-
-        mockArtifacts = MockArtifacts();
-        final String artifactPath = fileSystem.path.join(flutterRoot, 'artifact');
-        fileSystem.file(artifactPath).createSync(recursive: true);
-        when(mockArtifacts.getArtifactPath(
-          any,
-          mode: anyNamed('mode')
-        )).thenReturn(artifactPath);
-        when(mockArtifacts.isLocalEngine)
-          .thenReturn(false);
-
-        when(mockBuildSystem.build(
-          any,
-          any,
-        )).thenAnswer((_) async {
-          fileSystem.file('$mainPath.dill').createSync(recursive: true);
-          return BuildResult(success: true);
-        });
-      });
-
-      testUsingContext('not debug', () async {
-        final LaunchResult result = await device.startApp(null,
-            mainPath: mainPath,
-            debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)));
-
-        expect(result.started, isFalse);
-      }, overrides: startOverrides);
+      expect(releaseResult.started, isFalse);
+      expect(profileResult.started, isFalse);
+      expect(jitReleaseResult.started, isFalse);
+    });
 
 
-      testUsingContext('start', () async {
-        final Uri observatoryUri = Uri.parse('http://127.0.0.1:6666/');
-        mockProcess = MockProcess(stdout: Stream<List<int>>.fromIterable(<List<int>>[
-          '''
+    testUsingContext('performs a build and starts in debug mode', () async {
+      final FlutterTesterApp app = FlutterTesterApp.fromCurrentDirectory(fileSystem);
+      final Uri observatoryUri = Uri.parse('http://127.0.0.1:6666/');
+      mockProcess = MockProcess(stdout: Stream<List<int>>.fromIterable(<List<int>>[
+        '''
 Observatory listening on $observatoryUri
 Hello!
 '''
-              .codeUnits,
-        ]));
+            .codeUnits,
+      ]));
 
-        final LaunchResult result = await device.startApp(null,
-            mainPath: mainPath,
-            debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)));
-        expect(result.started, isTrue);
-        expect(result.observatoryUri, observatoryUri);
-        expect(logLines.last, 'Hello!');
-      }, overrides: startOverrides);
-    });
+      final LaunchResult result = await device.startApp(app,
+        mainPath: mainPath,
+        debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug)
+      );
+
+      expect(result.started, isTrue);
+      expect(result.observatoryUri, observatoryUri);
+      expect(logLines.last, 'Hello!');
+    }, overrides: startOverrides);
   });
 }
 
 class MockBuildSystem extends Mock implements BuildSystem {}
-class MockArtifacts extends Mock implements Artifacts {}