Commit 60b19b20 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

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.
parent 0924b020
......@@ -14,6 +14,7 @@ import 'src/base/context.dart';
import 'src/base/logger.dart';
import 'src/base/os.dart';
import 'src/base/process.dart';
import 'src/base/process_manager.dart';
import 'src/base/utils.dart';
import 'src/cache.dart';
import 'src/commands/analyze.dart';
......@@ -95,7 +96,11 @@ Future<Null> main(List<String> args) async {
// Make the context current.
_executableContext.runInZone(() {
// Initialize the context with some defaults.
// Seed these context entries first since others depend on them
context.putIfAbsent(ProcessManager, () => new ProcessManager());
context.putIfAbsent(Logger, () => new StdoutLogger());
// Order-independent context entries
context.putIfAbsent(DeviceManager, () => new DeviceManager());
context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
context.putIfAbsent(Doctor, () => new Doctor());
......
......@@ -11,6 +11,7 @@ import '../application_package.dart';
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 @@ class AndroidDevice extends Device {
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
......
......@@ -6,6 +6,7 @@ import 'dart:async';
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 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
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');
......
......@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
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 @@ class _PosixUtils extends OperatingSystemUtils {
@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 @@ class _WindowsUtils extends OperatingSystemUtils {
@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());
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'process_manager.dart';
import '../globals.dart';
typedef String StringConverter(String string);
......@@ -44,7 +45,7 @@ Future<Process> runCommand(List<String> cmd, {
_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<Null> runAndKill(List<String> cmd, Duration timeout) {
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 @@ Future<RunResult> runAsync(List<String> cmd, {
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 @@ Future<RunResult> runAsync(List<String> cmd, {
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 @@ String _runWithLoggingSync(List<String> cmd, {
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,
......
// 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);
}
}
......@@ -11,6 +11,7 @@ import 'package:path/path.dart' as path;
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 @@ class AnalysisServer {
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());
......
......@@ -10,6 +10,7 @@ import 'package:test/src/executable.dart' as executable; // ignore: implementati
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 @@ class TestCommand extends FlutterCommand {
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,
......
......@@ -9,6 +9,7 @@ import 'dart:io';
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 @@ class _IOSDevicePortForwarder extends DevicePortForwarder {
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");
}
......
......@@ -11,6 +11,7 @@ import 'package:path/path.dart' as path;
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 @@ class XCode {
} 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;
......
......@@ -13,6 +13,7 @@ import '../application_package.dart';
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 @@ class SimControl {
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>>{};
......
......@@ -15,6 +15,7 @@ import 'package:test/src/backend/test_platform.dart'; // ignore: implementation_
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 @@ Future<Process> _startProcess(String mainPath, { String packages, int observator
'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) {
......
......@@ -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 @@ class FlutterVersion {
}
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() : '';
}
......
......@@ -49,8 +49,8 @@ void main() {
runner = createTestCommandRunner(doctorCommand);
await runner.run(<String>['doctor']);
expect(count, 0);
}, overrides: <Type, dynamic>{
Usage: new Usage()
}, overrides: <Type, Generator>{
Usage: () => new Usage(),
});
// Ensure we con't send for the 'flutter config' command.
......@@ -67,8 +67,8 @@ void main() {
flutterUsage.enabled = true;
await runner.run(<String>['config']);
expect(count, 0);
}, overrides: <Type, dynamic>{
Usage: new Usage()
}, overrides: <Type, Generator>{
Usage: () => new Usage(),
});
});
......@@ -79,8 +79,11 @@ void main() {
await createTestCommandRunner().run(<String>['--version']);
expect(count, 0);
}, overrides: <Type, dynamic>{
Usage: new Usage(settingsName: 'flutter_bot_test', versionOverride: 'dev/unknown')
}, overrides: <Type, Generator>{
Usage: () => new Usage(
settingsName: 'flutter_bot_test',
versionOverride: 'dev/unknown',
),
});
});
}
......@@ -45,8 +45,8 @@ void main() {
await onDone;
expect(errorCount, 0);
}, overrides: <Type, dynamic>{
OperatingSystemUtils: os
}, overrides: <Type, Generator>{
OperatingSystemUtils: () => os
});
});
......@@ -65,8 +65,8 @@ void main() {
await onDone;
expect(errorCount, 2);
}, overrides: <Type, dynamic>{
OperatingSystemUtils: os
}, overrides: <Type, Generator>{
OperatingSystemUtils: () => os
});
}
......
......@@ -21,9 +21,9 @@ void main() {
DevicesCommand command = new DevicesCommand();
await createTestCommandRunner(command).run(<String>['devices']);
expect(testLogger.statusText, contains('No devices detected'));
}, overrides: <Type, dynamic>{
AndroidSdk: null,
DeviceManager: new DeviceManager()
}, overrides: <Type, Generator>{
AndroidSdk: () => null,
DeviceManager: () => new DeviceManager(),
});
});
}
......@@ -33,8 +33,8 @@ void main() {
// rwxr--r--
expect(mode.substring(0, 3), endsWith('x'));
}
}, overrides: <Type, dynamic> {
OperatingSystemUtils: new OperatingSystemUtils(),
}, overrides: <Type, Generator> {
OperatingSystemUtils: () => new OperatingSystemUtils(),
});
});
}
......@@ -8,6 +8,7 @@ import 'package:flutter_tools/src/base/config.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/process_manager.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/devfs.dart';
......@@ -27,20 +28,21 @@ BufferLogger get testLogger => context[Logger];
MockDeviceManager get testDeviceManager => context[DeviceManager];
MockDoctor get testDoctor => context[Doctor];
typedef dynamic Generator();
void testUsingContext(String description, dynamic testMethod(), {
Timeout timeout,
Map<Type, dynamic> overrides: const <Type, dynamic>{}
Map<Type, Generator> overrides: const <Type, Generator>{}
}) {
test(description, () async {
AppContext testContext = new AppContext();
// Apply all overrides to the test context.
overrides.forEach((Type type, dynamic value) {
testContext.setVariable(type, value);
});
// Initialize the test context with some default mocks.
// Seed these context entries first since others depend on them
testContext.putIfAbsent(ProcessManager, () => new ProcessManager());
testContext.putIfAbsent(Logger, () => new BufferLogger());
// Order-independent context entries
testContext.putIfAbsent(DeviceManager, () => new MockDeviceManager());
testContext.putIfAbsent(DevFSConfig, () => new DevFSConfig());
testContext.putIfAbsent(Doctor, () => new MockDoctor());
......@@ -63,7 +65,15 @@ void testUsingContext(String description, dynamic testMethod(), {
testContext.putIfAbsent(Usage, () => new MockUsage());
try {
return await testContext.runInZone(testMethod);
return await testContext.runInZone(() {
// Apply the overrides to the test context in the zone since their
// instantiation may reference items already stored on the context.
overrides.forEach((Type type, dynamic value()) {
context.setVariable(type, value());
});
return testMethod();
});
} catch (error) {
if (testContext[Logger] is BufferLogger) {
BufferLogger bufferLogger = testContext[Logger];
......
......@@ -29,8 +29,8 @@ void main() {
);
expect(tempDir, isNotNull);
tempDir.deleteSync(recursive: true);
}, overrides: <Type, dynamic> {
Cache: new Cache(rootOverride: tempDir)
}, overrides: <Type, Generator> {
Cache: () => new Cache(rootOverride: tempDir),
});
testUsingContext('using enginePath', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment