From 538ba522eeeffb8a754ecb12b77eddac3452ed74 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 24 Jan 2018 17:16:30 -0800 Subject: [PATCH] Move common process/IO-related mocks to mocks.dart (#14255) Moves MockProcess, MockStdio and a few other useful mocks from packages_test.dart to common/mocks.dart. These are useful for testing code with interactive IO. This adds a new constructor to MockProcess to provide additional flexibility. --- .../test/commands/packages_test.dart | 180 +---------------- packages/flutter_tools/test/src/mocks.dart | 188 ++++++++++++++++++ 2 files changed, 189 insertions(+), 179 deletions(-) diff --git a/packages/flutter_tools/test/commands/packages_test.dart b/packages/flutter_tools/test/commands/packages_test.dart index a3f9be3dde6..86bbc286032 100644 --- a/packages/flutter_tools/test/commands/packages_test.dart +++ b/packages/flutter_tools/test/commands/packages_test.dart @@ -3,8 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; -import 'dart:io' show IOSink; import 'package:args/command_runner.dart'; import 'package:flutter_tools/src/base/file_system.dart' hide IOSink; @@ -16,6 +14,7 @@ import 'package:test/test.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/mocks.dart' show MockProcessManager, MockStdio, PromptingProcess; void main() { Cache.disableLocking(); @@ -130,180 +129,3 @@ void main() { }); }); } - -/// A strategy for creating Process objects from a list of commands. -typedef Process ProcessFactory(List command); - -/// A ProcessManager that starts Processes by delegating to a ProcessFactory. -class MockProcessManager implements ProcessManager { - ProcessFactory processFactory = (List commands) => new MockProcess(); - List commands; - - @override - Future start( - List command, { - String workingDirectory, - Map environment, - bool includeParentEnvironment: true, - bool runInShell: false, - ProcessStartMode mode: ProcessStartMode.NORMAL, - }) { - commands = command; - return new Future.value(processFactory(command)); - } - - @override - dynamic noSuchMethod(Invocation invocation) => null; -} - -/// A process that prompts the user to proceed, then asynchronously writes -/// some lines to stdout before it exits. -class PromptingProcess implements Process { - Future showPrompt(String prompt, List outputLines) async { - _stdoutController.add(UTF8.encode(prompt)); - final List bytesOnStdin = await _stdin.future; - // Echo stdin to stdout. - _stdoutController.add(bytesOnStdin); - if (bytesOnStdin[0] == UTF8.encode('y')[0]) { - for (final String line in outputLines) - _stdoutController.add(UTF8.encode('$line\n')); - } - await _stdoutController.close(); - } - - final StreamController> _stdoutController = new StreamController>(); - final CompleterIOSink _stdin = new CompleterIOSink(); - - @override - Stream> get stdout => _stdoutController.stream; - - @override - Stream> get stderr => const Stream>.empty(); - - @override - IOSink get stdin => _stdin; - - @override - Future get exitCode async { - await _stdoutController.done; - return 0; - } - - @override - dynamic noSuchMethod(Invocation invocation) => null; -} - -/// An inactive process that collects stdin and produces no output. -class MockProcess implements Process { - final IOSink _stdin = new MemoryIOSink(); - - @override - Stream> get stdout => const Stream>.empty(); - - @override - Stream> get stderr => const Stream>.empty(); - - @override - IOSink get stdin => _stdin; - - @override - Future get exitCode => new Future.value(0); - - @override - dynamic noSuchMethod(Invocation invocation) => null; -} - -/// An IOSink that completes a future with the first line written to it. -class CompleterIOSink extends MemoryIOSink { - final Completer> _completer = new Completer>(); - - Future> get future => _completer.future; - - @override - void add(List data) { - if (!_completer.isCompleted) - _completer.complete(data); - super.add(data); - } -} - -/// A Stdio that collects stdout and supports simulated stdin. -class MockStdio extends Stdio { - final MemoryIOSink _stdout = new MemoryIOSink(); - final StreamController> _stdin = new StreamController>(); - - @override - IOSink get stdout => _stdout; - - @override - Stream> get stdin => _stdin.stream; - - void simulateStdin(String line) { - _stdin.add(UTF8.encode('$line\n')); - } - - List get writtenToStdout => _stdout.writes.map(_stdout.encoding.decode).toList(); -} - -/// An IOSink that collects whatever is written to it. -class MemoryIOSink implements IOSink { - @override - Encoding encoding = UTF8; - - final List> writes = >[]; - - @override - void add(List data) { - writes.add(data); - } - - @override - Future addStream(Stream> stream) { - final Completer completer = new Completer(); - stream.listen((List data) { - add(data); - }).onDone(() => completer.complete(null)); - return completer.future; - } - - @override - void writeCharCode(int charCode) { - add([charCode]); - } - - @override - void write(Object obj) { - add(encoding.encode('$obj')); - } - - @override - void writeln([Object obj = '']) { - add(encoding.encode('$obj\n')); - } - - @override - void writeAll(Iterable objects, [String separator = '']) { - bool addSeparator = false; - for (dynamic object in objects) { - if (addSeparator) { - write(separator); - } - write(object); - addSeparator = true; - } - } - - @override - void addError(dynamic error, [StackTrace stackTrace]) { - throw new UnimplementedError(); - } - - @override - Future get done => close(); - - @override - Future close() async => null; - - @override - Future flush() async => null; -} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index acf5352d492..657a8d0acf3 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -3,9 +3,12 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; +import 'dart:io' as io show IOSink; import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/application_package.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; @@ -13,6 +16,7 @@ import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; import 'package:test/test.dart'; class MockApplicationPackageStore extends ApplicationPackageStore { @@ -29,6 +33,190 @@ class MockApplicationPackageStore extends ApplicationPackageStore { ); } +/// A strategy for creating Process objects from a list of commands. +typedef Process ProcessFactory(List command); + +/// A ProcessManager that starts Processes by delegating to a ProcessFactory. +class MockProcessManager implements ProcessManager { + ProcessFactory processFactory = (List commands) => new MockProcess(); + List commands; + + @override + Future start( + List command, { + String workingDirectory, + Map environment, + bool includeParentEnvironment: true, + bool runInShell: false, + ProcessStartMode mode: ProcessStartMode.NORMAL, + }) { + commands = command; + return new Future.value(processFactory(command)); + } + + @override + dynamic noSuchMethod(Invocation invocation) => null; +} + +/// A process that exits successfully with no output and ignores all input. +class MockProcess extends Mock implements Process { + MockProcess({ + this.pid: 1, + Future exitCode, + Stream> stdin, + this.stdout: const Stream>.empty(), + this.stderr: const Stream>.empty(), + }) : exitCode = exitCode ?? new Future.value(0), + stdin = stdin ?? new MemoryIOSink(); + + @override + final int pid; + + @override + final Future exitCode; + + @override + final io.IOSink stdin; + + @override + final Stream> stdout; + + @override + final Stream> stderr; +} + +/// A process that prompts the user to proceed, then asynchronously writes +/// some lines to stdout before it exits. +class PromptingProcess implements Process { + Future showPrompt(String prompt, List outputLines) async { + _stdoutController.add(UTF8.encode(prompt)); + final List bytesOnStdin = await _stdin.future; + // Echo stdin to stdout. + _stdoutController.add(bytesOnStdin); + if (bytesOnStdin[0] == UTF8.encode('y')[0]) { + for (final String line in outputLines) + _stdoutController.add(UTF8.encode('$line\n')); + } + await _stdoutController.close(); + } + + final StreamController> _stdoutController = new StreamController>(); + final CompleterIOSink _stdin = new CompleterIOSink(); + + @override + Stream> get stdout => _stdoutController.stream; + + @override + Stream> get stderr => const Stream>.empty(); + + @override + IOSink get stdin => _stdin; + + @override + Future get exitCode async { + await _stdoutController.done; + return 0; + } + + @override + dynamic noSuchMethod(Invocation invocation) => null; +} + +/// An IOSink that completes a future with the first line written to it. +class CompleterIOSink extends MemoryIOSink { + final Completer> _completer = new Completer>(); + + Future> get future => _completer.future; + + @override + void add(List data) { + if (!_completer.isCompleted) + _completer.complete(data); + super.add(data); + } +} + +/// An IOSink that collects whatever is written to it. +class MemoryIOSink implements IOSink { + @override + Encoding encoding = UTF8; + + final List> writes = >[]; + + @override + void add(List data) { + writes.add(data); + } + + @override + Future addStream(Stream> stream) { + final Completer completer = new Completer(); + stream.listen((List data) { + add(data); + }).onDone(() => completer.complete(null)); + return completer.future; + } + + @override + void writeCharCode(int charCode) { + add([charCode]); + } + + @override + void write(Object obj) { + add(encoding.encode('$obj')); + } + + @override + void writeln([Object obj = '']) { + add(encoding.encode('$obj\n')); + } + + @override + void writeAll(Iterable objects, [String separator = '']) { + bool addSeparator = false; + for (dynamic object in objects) { + if (addSeparator) { + write(separator); + } + write(object); + addSeparator = true; + } + } + + @override + void addError(dynamic error, [StackTrace stackTrace]) { + throw new UnimplementedError(); + } + + @override + Future get done => close(); + + @override + Future close() async => null; + + @override + Future flush() async => null; +} + +/// A Stdio that collects stdout and supports simulated stdin. +class MockStdio extends Stdio { + final MemoryIOSink _stdout = new MemoryIOSink(); + final StreamController> _stdin = new StreamController>(); + + @override + IOSink get stdout => _stdout; + + @override + Stream> get stdin => _stdin.stream; + + void simulateStdin(String line) { + _stdin.add(UTF8.encode('$line\n')); + } + + List get writtenToStdout => _stdout.writes.map(_stdout.encoding.decode).toList(); +} + class MockPollingDeviceDiscovery extends PollingDeviceDiscovery { final List _devices = []; final StreamController _onAddedController = new StreamController.broadcast();