blob: 54471d9124652b671aca4e196edb4cdb09cdc7cb [file] [log] [blame]
// Copyright 2018 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 'package:process/process.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
/// A mock that can be used to fake a process manager that runs commands
/// and returns results.
/// Call [setResults] to provide a list of results that will return from
/// each command line (with arguments).
/// Call [verifyCalls] to verify that each desired call occurred.
class FakeProcessManager extends Mock implements ProcessManager {
FakeProcessManager({this.stdinResults}) {
/// The callback that will be called each time stdin input is supplied to
/// a call.
final StringReceivedCallback stdinResults;
/// The list of results that will be sent back, organized by the command line
/// that will produce them. Each command line has a list of returned stdout
/// output that will be returned on each successive call.
Map<String, List<ProcessResult>> _fakeResults = <String, List<ProcessResult>>{};
Map<String, List<ProcessResult>> get fakeResults => _fakeResults;
set fakeResults(Map<String, List<ProcessResult>> value) {
_fakeResults = <String, List<ProcessResult>>{};
for (String key in value.keys) {
_fakeResults[key] = <ProcessResult>[]
..addAll(value[key] ?? <ProcessResult>[new ProcessResult(0, 0, '', '')]);
/// The list of invocations that occurred, in the order they occurred.
List<Invocation> invocations = <Invocation>[];
/// Verify that the given command lines were called, in the given order, and that the
/// parameters were in the same order.
void verifyCalls(List<String> calls) {
int index = 0;
for (String call in calls) {
expect(call.split(' '), orderedEquals(invocations[index].positionalArguments[0]));
expect(invocations.length, equals(calls.length));
ProcessResult _popResult(List<String> command) {
final String key = command.join(' ');
expect(fakeResults, isNotEmpty);
expect(fakeResults, contains(key));
expect(fakeResults[key], isNotEmpty);
return fakeResults[key].removeAt(0);
FakeProcess _popProcess(List<String> command) =>
new FakeProcess(_popResult(command), stdinResults: stdinResults);
Future<Process> _nextProcess(Invocation invocation) async {
return new Future<Process>.value(_popProcess(invocation.positionalArguments[0]));
ProcessResult _nextResultSync(Invocation invocation) {
return _popResult(invocation.positionalArguments[0]);
Future<ProcessResult> _nextResult(Invocation invocation) async {
return new Future<ProcessResult>.value(_popResult(invocation.positionalArguments[0]));
void _setupMock() {
// Note that not all possible types of invocations are covered here, just the ones
// expected to be called.
// TODO(gspencer): make this more general so that any call will be captured.
environment: typed(captureAny, named: 'environment'),
workingDirectory: typed(captureAny, named: 'workingDirectory'),
environment: typed(captureAny, named: 'environment'),
workingDirectory: typed(captureAny, named: 'workingDirectory'),
environment: typed(captureAny, named: 'environment'),
workingDirectory: typed(captureAny, named: 'workingDirectory')
when(killPid(typed(captureAny), typed(captureAny))).thenReturn(true);
when(canRun(captureAny, workingDirectory: typed(captureAny, named: 'workingDirectory')))
/// A fake process that can be used to interact with a process "started" by the FakeProcessManager.
class FakeProcess extends Mock implements Process {
FakeProcess(ProcessResult result, {void stdinResults(String input)})
: stdoutStream = new Stream<List<int>>.fromIterable(<List<int>>[result.stdout.codeUnits]),
stderrStream = new Stream<List<int>>.fromIterable(<List<int>>[result.stderr.codeUnits]),
desiredExitCode = result.exitCode,
stdinSink = new IOSink(new StringStreamConsumer(stdinResults)) {
final IOSink stdinSink;
final Stream<List<int>> stdoutStream;
final Stream<List<int>> stderrStream;
final int desiredExitCode;
void _setupMock() {
Future<int> get exitCode => new Future<int>.value(desiredExitCode);
int get pid => 0;
IOSink get stdin => stdinSink;
Stream<List<int>> get stderr => stderrStream;
Stream<List<int>> get stdout => stdoutStream;
/// Callback used to receive stdin input when it occurs.
typedef void StringReceivedCallback(String received);
/// A stream consumer class that consumes UTF8 strings as lists of ints.
class StringStreamConsumer implements StreamConsumer<List<int>> {
List<Stream<List<int>>> streams = <Stream<List<int>>>[];
List<StreamSubscription<List<int>>> subscriptions = <StreamSubscription<List<int>>>[];
List<Completer<dynamic>> completers = <Completer<dynamic>>[];
/// The callback called when this consumer receives input.
StringReceivedCallback sendString;
Future<dynamic> addStream(Stream<List<int>> value) {
completers.add(new Completer<dynamic>());
value.listen((List<int> data) {
subscriptions.last.onDone(() => completers.last.complete(null));
return new Future<dynamic>.value(null);
Future<dynamic> close() async {
for (Completer<dynamic> completer in completers) {
await completer.future;
return new Future<dynamic>.value(null);