Create abstraction layer for dart:io's Process commands (#7100)

With this change, they're run via instance methods on an object
obtained through the context. This will allow us to substitute
that object in tests with replay/record versions to allow us to
mock out the os-layer in tests.
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index e8f9452..bf86f76 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -11,6 +11,7 @@
 import '../base/os.dart';
 import '../base/logger.dart';
 import '../base/process.dart';
+import '../base/process_manager.dart';
 import '../build_info.dart';
 import '../commands/build_apk.dart';
 import '../device.dart';
@@ -59,7 +60,7 @@
       try {
         // We pass an encoding of LATIN1 so that we don't try and interpret the
         // `adb shell getprop` result as UTF8.
-        ProcessResult result = Process.runSync(
+        ProcessResult result = processManager.runSync(
           propCommand.first,
           propCommand.sublist(1),
           stdoutEncoding: LATIN1
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index 05bc39a..58f77db 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import '../base/os.dart';
+import '../base/process_manager.dart';
 import '../doctor.dart';
 import '../globals.dart';
 import 'android_sdk.dart';
@@ -70,7 +71,7 @@
         try {
           printTrace('java -version');
 
-          ProcessResult result = Process.runSync('java', <String>['-version']);
+          ProcessResult result = processManager.runSync('java', <String>['-version']);
           if (result.exitCode == 0) {
             javaVersion = result.stderr;
             List<String> versionLines = javaVersion.split('\n');
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index f709d4d..01c46d3 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -10,6 +10,7 @@
 
 import 'context.dart';
 import 'process.dart';
+import 'process_manager.dart';
 
 /// Returns [OperatingSystemUtils] active in the current app context (i.e. zone).
 OperatingSystemUtils get os => context[OperatingSystemUtils];
@@ -49,14 +50,14 @@
 
   @override
   ProcessResult makeExecutable(File file) {
-    return Process.runSync('chmod', <String>['a+x', file.path]);
+    return processManager.runSync('chmod', <String>['a+x', file.path]);
   }
 
   /// Return the path to the given executable, or `null` if `which` was not able
   /// to locate the binary.
   @override
   File which(String execName) {
-    ProcessResult result = Process.runSync('which', <String>[execName]);
+    ProcessResult result = processManager.runSync('which', <String>[execName]);
     if (result.exitCode != 0)
       return null;
     String path = result.stdout.trim().split('\n').first.trim();
@@ -87,7 +88,7 @@
 
   @override
   File which(String execName) {
-    ProcessResult result = Process.runSync('where', <String>[execName]);
+    ProcessResult result = processManager.runSync('where', <String>[execName]);
     if (result.exitCode != 0)
       return null;
     return new File(result.stdout.trim().split('\n').first.trim());
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 104db6c..8b20f90 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -6,6 +6,7 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'process_manager.dart';
 import '../globals.dart';
 
 typedef String StringConverter(String string);
@@ -44,7 +45,7 @@
   _traceCommand(cmd, workingDirectory: workingDirectory);
   String executable = cmd[0];
   List<String> arguments = cmd.length > 1 ? cmd.sublist(1) : <String>[];
-  Process process = await Process.start(
+  Process process = await processManager.start(
     executable,
     arguments,
     workingDirectory: workingDirectory,
@@ -108,13 +109,13 @@
   Future<Process> proc = runDetached(cmd);
   return new Future<Null>.delayed(timeout, () async {
     printTrace('Intentionally killing ${cmd[0]}');
-    Process.killPid((await proc).pid);
+    processManager.killPid((await proc).pid);
   });
 }
 
 Future<Process> runDetached(List<String> cmd) {
   _traceCommand(cmd);
-  Future<Process> proc = Process.start(
+  Future<Process> proc = processManager.start(
     cmd[0], cmd.getRange(1, cmd.length).toList(),
     mode: ProcessStartMode.DETACHED
   );
@@ -126,7 +127,7 @@
   bool allowReentrantFlutter: false
 }) async {
   _traceCommand(cmd, workingDirectory: workingDirectory);
-  ProcessResult results = await Process.run(
+  ProcessResult results = await processManager.run(
     cmd[0],
     cmd.getRange(1, cmd.length).toList(),
     workingDirectory: workingDirectory,
@@ -140,7 +141,7 @@
 bool exitsHappy(List<String> cli) {
   _traceCommand(cli);
   try {
-    return Process.runSync(cli.first, cli.sublist(1)).exitCode == 0;
+    return processManager.runSync(cli.first, cli.sublist(1)).exitCode == 0;
   } catch (error) {
     return false;
   }
@@ -203,7 +204,7 @@
   bool hideStdout: false,
 }) {
   _traceCommand(cmd, workingDirectory: workingDirectory);
-  ProcessResult results = Process.runSync(
+  ProcessResult results = processManager.runSync(
     cmd[0],
     cmd.getRange(1, cmd.length).toList(),
     workingDirectory: workingDirectory,
diff --git a/packages/flutter_tools/lib/src/base/process_manager.dart b/packages/flutter_tools/lib/src/base/process_manager.dart
new file mode 100644
index 0000000..0cfc1f5
--- /dev/null
+++ b/packages/flutter_tools/lib/src/base/process_manager.dart
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'context.dart';
+
+ProcessManager get processManager => context[ProcessManager];
+
+class ProcessManager {
+  Future<Process> start(
+      String executable,
+      List<String> arguments,
+      {String workingDirectory,
+       Map<String, String> environment,
+       ProcessStartMode mode: ProcessStartMode.NORMAL}) {
+    return Process.start(
+      executable,
+      arguments,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      mode: mode,
+    );
+  }
+
+  Future<ProcessResult> run(
+      String executable,
+      List<String> arguments,
+      {String workingDirectory,
+       Map<String, String> environment,
+       Encoding stdoutEncoding: SYSTEM_ENCODING,
+       Encoding stderrEncoding: SYSTEM_ENCODING}) {
+    return Process.run(
+      executable,
+      arguments,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      stdoutEncoding: stdoutEncoding,
+      stderrEncoding: stderrEncoding,
+    );
+  }
+
+  ProcessResult runSync(
+      String executable,
+      List<String> arguments,
+      {String workingDirectory,
+       Map<String, String> environment,
+       Encoding stdoutEncoding: SYSTEM_ENCODING,
+       Encoding stderrEncoding: SYSTEM_ENCODING}) {
+    return Process.runSync(
+      executable,
+      arguments,
+      workingDirectory: workingDirectory,
+      environment: environment,
+      stdoutEncoding: stdoutEncoding,
+      stderrEncoding: stderrEncoding,
+    );
+  }
+
+  bool killPid(int pid, [ProcessSignal signal = ProcessSignal.SIGTERM]) {
+    return Process.killPid(pid, signal);
+  }
+}
diff --git a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart
index b04d6f4..b8e5534 100644
--- a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart
@@ -11,6 +11,7 @@
 
 import '../base/common.dart';
 import '../base/logger.dart';
+import '../base/process_manager.dart';
 import '../base/utils.dart';
 import '../cache.dart';
 import '../dart/sdk.dart';
@@ -159,7 +160,7 @@
     List<String> args = <String>[snapshot, '--sdk', sdk];
 
     printTrace('dart ${args.join(' ')}');
-    _process = await Process.start(path.join(dartSdkPath, 'bin', 'dart'), args);
+    _process = await processManager.start(path.join(dartSdkPath, 'bin', 'dart'), args);
     _process.exitCode.whenComplete(() => _process = null);
 
     Stream<String> errorStream = _process.stderr.transform(UTF8.decoder).transform(const LineSplitter());
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index cf421cd..f83f7a0 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -10,6 +10,7 @@
 
 import '../base/common.dart';
 import '../base/logger.dart';
+import '../base/process_manager.dart';
 import '../base/os.dart';
 import '../cache.dart';
 import '../dart/package_map.dart';
@@ -126,7 +127,7 @@
       Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
       try {
         File sourceFile = coverageFile.copySync(path.join(tempDir.path, 'lcov.source.info'));
-        ProcessResult result = Process.runSync('lcov', <String>[
+        ProcessResult result = processManager.runSync('lcov', <String>[
           '--add-tracefile', baseCoverageData,
           '--add-tracefile', sourceFile.path,
           '--output-file', coverageFile.path,
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index d2aa790..9e05e19 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -9,6 +9,7 @@
 import '../application_package.dart';
 import '../base/os.dart';
 import '../base/process.dart';
+import '../base/process_manager.dart';
 import '../build_info.dart';
 import '../device.dart';
 import '../doctor.dart';
@@ -514,7 +515,7 @@
     Process process = forwardedPort.context;
 
     if (process != null) {
-      Process.killPid(process.pid);
+      processManager.killPid(process.pid);
     } else {
       printError("Forwarded port did not have a valid process");
     }
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index f727f38..b7eead4 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -11,6 +11,7 @@
 import '../application_package.dart';
 import '../base/context.dart';
 import '../base/process.dart';
+import '../base/process_manager.dart';
 import '../build_info.dart';
 import '../flx.dart' as flx;
 import '../globals.dart';
@@ -39,7 +40,7 @@
       } else {
         try {
           printTrace('xcrun clang');
-          ProcessResult result = Process.runSync('/usr/bin/xcrun', <String>['clang']);
+          ProcessResult result = processManager.runSync('/usr/bin/xcrun', <String>['clang']);
 
           if (result.stdout != null && result.stdout.contains('license'))
             _eulaSigned = false;
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index d0bae71..3c30dca 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -13,6 +13,7 @@
 import '../base/common.dart';
 import '../base/context.dart';
 import '../base/process.dart';
+import '../base/process_manager.dart';
 import '../build_info.dart';
 import '../device.dart';
 import '../flx.dart' as flx;
@@ -190,7 +191,7 @@
 
     List<String> args = <String>['simctl', 'list', '--json', section.name];
     printTrace('$_xcrunPath ${args.join(' ')}');
-    ProcessResult results = Process.runSync(_xcrunPath, args);
+    ProcessResult results = processManager.runSync(_xcrunPath, args);
     if (results.exitCode != 0) {
       printError('Error executing simctl: ${results.exitCode}\n${results.stderr}');
       return <String, Map<String, dynamic>>{};
diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart
index 680df4c..6e85424 100644
--- a/packages/flutter_tools/lib/src/test/flutter_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart
@@ -15,6 +15,7 @@
 import 'package:test/src/runner/plugin/platform.dart'; // ignore: implementation_imports
 import 'package:test/src/runner/plugin/hack_register_platform.dart' as hack; // ignore: implementation_imports
 
+import '../base/process_manager.dart';
 import '../dart/package_map.dart';
 import '../globals.dart';
 import 'coverage_collector.dart';
@@ -74,7 +75,7 @@
     'FLUTTER_TEST': 'true',
     'FONTCONFIG_FILE': _fontConfigFile.path,
   };
-  return Process.start(executable, arguments, environment: environment);
+  return processManager.start(executable, arguments, environment: environment);
 }
 
 void _attachStandardStreams(Process process) {
diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart
index 4ff3859..953d41e 100644
--- a/packages/flutter_tools/lib/src/version.dart
+++ b/packages/flutter_tools/lib/src/version.dart
@@ -5,6 +5,7 @@
 import 'dart:io';
 
 import 'base/process.dart';
+import 'base/process_manager.dart';
 import 'cache.dart';
 
 final Set<String> kKnownBranchNames = new Set<String>.from(<String>[
@@ -102,7 +103,7 @@
 }
 
 String _runSync(String executable, List<String> arguments, String cwd) {
-  ProcessResult results = Process.runSync(executable, arguments, workingDirectory: cwd);
+  ProcessResult results = processManager.runSync(executable, arguments, workingDirectory: cwd);
   return results.exitCode == 0 ? results.stdout.trim() : '';
 }