• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:convert';
7import 'dart:io' as io show IOSink, ProcessSignal, Stdout, StdoutException;
8
9import 'package:flutter_tools/src/android/android_device.dart';
10import 'package:flutter_tools/src/android/android_sdk.dart' show AndroidSdk;
11import 'package:flutter_tools/src/application_package.dart';
12import 'package:flutter_tools/src/base/context.dart';
13import 'package:flutter_tools/src/base/file_system.dart' hide IOSink;
14import 'package:flutter_tools/src/base/io.dart';
15import 'package:flutter_tools/src/base/platform.dart';
16import 'package:flutter_tools/src/build_info.dart';
17import 'package:flutter_tools/src/compile.dart';
18import 'package:flutter_tools/src/devfs.dart';
19import 'package:flutter_tools/src/device.dart';
20import 'package:flutter_tools/src/ios/devices.dart';
21import 'package:flutter_tools/src/ios/simulators.dart';
22import 'package:flutter_tools/src/project.dart';
23import 'package:flutter_tools/src/runner/flutter_command.dart';
24import 'package:mockito/mockito.dart';
25import 'package:process/process.dart';
26
27import 'common.dart';
28
29final Generator kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
30
31class MockApplicationPackageStore extends ApplicationPackageStore {
32  MockApplicationPackageStore() : super(
33    android: AndroidApk(
34      id: 'io.flutter.android.mock',
35      file: fs.file('/mock/path/to/android/SkyShell.apk'),
36      versionCode: 1,
37      launchActivity: 'io.flutter.android.mock.MockActivity',
38    ),
39    iOS: BuildableIOSApp(MockIosProject())
40  );
41}
42
43class MockApplicationPackageFactory extends Mock implements ApplicationPackageFactory {
44  final MockApplicationPackageStore _store = MockApplicationPackageStore();
45
46  @override
47  Future<ApplicationPackage> getPackageForPlatform(
48    TargetPlatform platform, {
49    File applicationBinary,
50  }) async {
51    return _store.getPackageForPlatform(platform);
52  }
53}
54
55/// An SDK installation with several SDK levels (19, 22, 23).
56class MockAndroidSdk extends Mock implements AndroidSdk {
57  static Directory createSdkDirectory({
58    bool withAndroidN = false,
59    String withNdkDir,
60    int ndkVersion = 16,
61    bool withNdkSysroot = false,
62    bool withSdkManager = true,
63    bool withPlatformTools = true,
64    bool withBuildTools = true,
65  }) {
66    final Directory dir = fs.systemTempDirectory.createTempSync('flutter_mock_android_sdk.');
67    final String exe = platform.isWindows ? '.exe' : '';
68    final String bat = platform.isWindows ? '.bat' : '';
69
70    _createDir(dir, 'licenses');
71
72    if (withPlatformTools) {
73      _createSdkFile(dir, 'platform-tools/adb$exe');
74    }
75
76    if (withBuildTools) {
77      _createSdkFile(dir, 'build-tools/19.1.0/aapt$exe');
78      _createSdkFile(dir, 'build-tools/22.0.1/aapt$exe');
79      _createSdkFile(dir, 'build-tools/23.0.2/aapt$exe');
80      if (withAndroidN)
81        _createSdkFile(dir, 'build-tools/24.0.0-preview/aapt$exe');
82    }
83
84    _createSdkFile(dir, 'platforms/android-22/android.jar');
85    _createSdkFile(dir, 'platforms/android-23/android.jar');
86    if (withAndroidN) {
87      _createSdkFile(dir, 'platforms/android-N/android.jar');
88      _createSdkFile(dir, 'platforms/android-N/build.prop', contents: _buildProp);
89    }
90
91    if (withSdkManager)
92      _createSdkFile(dir, 'tools/bin/sdkmanager$bat');
93
94    if (withNdkDir != null) {
95      final String ndkToolchainBin = fs.path.join(
96        'ndk-bundle',
97        'toolchains',
98        'arm-linux-androideabi-4.9',
99        'prebuilt',
100        withNdkDir,
101        'bin',
102      );
103      final String ndkCompiler = fs.path.join(
104        ndkToolchainBin,
105        'arm-linux-androideabi-gcc',
106      );
107      final String ndkLinker = fs.path.join(
108        ndkToolchainBin,
109        'arm-linux-androideabi-ld',
110      );
111      _createSdkFile(dir, ndkCompiler);
112      _createSdkFile(dir, ndkLinker);
113      _createSdkFile(dir, fs.path.join('ndk-bundle', 'source.properties'), contents: '''
114Pkg.Desc = Android NDK[]
115Pkg.Revision = $ndkVersion.1.5063045
116
117''');
118    }
119    if (withNdkSysroot) {
120      final String armPlatform = fs.path.join(
121        'ndk-bundle',
122        'platforms',
123        'android-9',
124        'arch-arm',
125      );
126      _createDir(dir, armPlatform);
127    }
128
129    return dir;
130  }
131
132  static void _createSdkFile(Directory dir, String filePath, { String contents }) {
133    final File file = dir.childFile(filePath);
134    file.createSync(recursive: true);
135    if (contents != null) {
136      file.writeAsStringSync(contents, flush: true);
137    }
138  }
139
140  static void _createDir(Directory dir, String path) {
141    final Directory directory = fs.directory(fs.path.join(dir.path, path));
142    directory.createSync(recursive: true);
143  }
144
145  static const String _buildProp = r'''
146ro.build.version.incremental=1624448
147ro.build.version.sdk=24
148ro.build.version.codename=REL
149''';
150}
151
152/// A strategy for creating Process objects from a list of commands.
153typedef ProcessFactory = Process Function(List<String> command);
154
155/// A ProcessManager that starts Processes by delegating to a ProcessFactory.
156class MockProcessManager extends Mock implements ProcessManager {
157  ProcessFactory processFactory = (List<String> commands) => MockProcess();
158  bool canRunSucceeds = true;
159  bool runSucceeds = true;
160  List<String> commands;
161
162  @override
163  bool canRun(dynamic command, { String workingDirectory }) => canRunSucceeds;
164
165  @override
166  Future<Process> start(
167    List<dynamic> command, {
168    String workingDirectory,
169    Map<String, String> environment,
170    bool includeParentEnvironment = true,
171    bool runInShell = false,
172    ProcessStartMode mode = ProcessStartMode.normal,
173  }) {
174    if (!runSucceeds) {
175      final String executable = command[0];
176      final List<String> arguments = command.length > 1 ? command.sublist(1) : <String>[];
177      throw ProcessException(executable, arguments);
178    }
179
180    commands = command;
181    return Future<Process>.value(processFactory(command));
182  }
183}
184
185/// A process that exits successfully with no output and ignores all input.
186class MockProcess extends Mock implements Process {
187  MockProcess({
188    this.pid = 1,
189    Future<int> exitCode,
190    Stream<List<int>> stdin,
191    this.stdout = const Stream<List<int>>.empty(),
192    this.stderr = const Stream<List<int>>.empty(),
193  }) : exitCode = exitCode ?? Future<int>.value(0),
194       stdin = stdin ?? MemoryIOSink();
195
196  @override
197  final int pid;
198
199  @override
200  final Future<int> exitCode;
201
202  @override
203  final io.IOSink stdin;
204
205  @override
206  final Stream<List<int>> stdout;
207
208  @override
209  final Stream<List<int>> stderr;
210}
211
212/// A fake process implemenation which can be provided all necessary values.
213class FakeProcess implements Process {
214  FakeProcess({
215    this.pid = 1,
216    Future<int> exitCode,
217    Stream<List<int>> stdin,
218    this.stdout = const Stream<List<int>>.empty(),
219    this.stderr = const Stream<List<int>>.empty(),
220  }) : exitCode = exitCode ?? Future<int>.value(0),
221       stdin = stdin ?? MemoryIOSink();
222
223  @override
224  final int pid;
225
226  @override
227  final Future<int> exitCode;
228
229  @override
230  final io.IOSink stdin;
231
232  @override
233  final Stream<List<int>> stdout;
234
235  @override
236  final Stream<List<int>> stderr;
237
238  @override
239  bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
240    return true;
241  }
242}
243
244/// A process that prompts the user to proceed, then asynchronously writes
245/// some lines to stdout before it exits.
246class PromptingProcess implements Process {
247  Future<void> showPrompt(String prompt, List<String> outputLines) async {
248    _stdoutController.add(utf8.encode(prompt));
249    final List<int> bytesOnStdin = await _stdin.future;
250    // Echo stdin to stdout.
251    _stdoutController.add(bytesOnStdin);
252    if (bytesOnStdin[0] == utf8.encode('y')[0]) {
253      for (final String line in outputLines)
254        _stdoutController.add(utf8.encode('$line\n'));
255    }
256    await _stdoutController.close();
257  }
258
259  final StreamController<List<int>> _stdoutController = StreamController<List<int>>();
260  final CompleterIOSink _stdin = CompleterIOSink();
261
262  @override
263  Stream<List<int>> get stdout => _stdoutController.stream;
264
265  @override
266  Stream<List<int>> get stderr => const Stream<List<int>>.empty();
267
268  @override
269  IOSink get stdin => _stdin;
270
271  @override
272  Future<int> get exitCode async {
273    await _stdoutController.done;
274    return 0;
275  }
276
277  @override
278  dynamic noSuchMethod(Invocation invocation) => null;
279}
280
281/// An IOSink that completes a future with the first line written to it.
282class CompleterIOSink extends MemoryIOSink {
283  final Completer<List<int>> _completer = Completer<List<int>>();
284
285  Future<List<int>> get future => _completer.future;
286
287  @override
288  void add(List<int> data) {
289    if (!_completer.isCompleted)
290      _completer.complete(data);
291    super.add(data);
292  }
293}
294
295/// An IOSink that collects whatever is written to it.
296class MemoryIOSink implements IOSink {
297  @override
298  Encoding encoding = utf8;
299
300  final List<List<int>> writes = <List<int>>[];
301
302  @override
303  void add(List<int> data) {
304    writes.add(data);
305  }
306
307  @override
308  Future<void> addStream(Stream<List<int>> stream) {
309    final Completer<void> completer = Completer<void>();
310    stream.listen((List<int> data) {
311      add(data);
312    }).onDone(() => completer.complete());
313    return completer.future;
314  }
315
316  @override
317  void writeCharCode(int charCode) {
318    add(<int>[charCode]);
319  }
320
321  @override
322  void write(Object obj) {
323    add(encoding.encode('$obj'));
324  }
325
326  @override
327  void writeln([ Object obj = '' ]) {
328    add(encoding.encode('$obj\n'));
329  }
330
331  @override
332  void writeAll(Iterable<dynamic> objects, [ String separator = '' ]) {
333    bool addSeparator = false;
334    for (dynamic object in objects) {
335      if (addSeparator) {
336        write(separator);
337      }
338      write(object);
339      addSeparator = true;
340    }
341  }
342
343  @override
344  void addError(dynamic error, [ StackTrace stackTrace ]) {
345    throw UnimplementedError();
346  }
347
348  @override
349  Future<void> get done => close();
350
351  @override
352  Future<void> close() async { }
353
354  @override
355  Future<void> flush() async { }
356}
357
358class MemoryStdout extends MemoryIOSink implements io.Stdout {
359  @override
360  bool get hasTerminal => _hasTerminal;
361  set hasTerminal(bool value) {
362    assert(value != null);
363    _hasTerminal = value;
364  }
365  bool _hasTerminal = true;
366
367  @override
368  io.IOSink get nonBlocking => this;
369
370  @override
371  bool get supportsAnsiEscapes => _supportsAnsiEscapes;
372  set supportsAnsiEscapes(bool value) {
373    assert(value != null);
374    _supportsAnsiEscapes = value;
375  }
376  bool _supportsAnsiEscapes = true;
377
378  @override
379  int get terminalColumns {
380    if (_terminalColumns != null)
381      return _terminalColumns;
382    throw const io.StdoutException('unspecified mock value');
383  }
384  set terminalColumns(int value) => _terminalColumns = value;
385  int _terminalColumns;
386
387  @override
388  int get terminalLines {
389    if (_terminalLines != null)
390      return _terminalLines;
391    throw const io.StdoutException('unspecified mock value');
392  }
393  set terminalLines(int value) => _terminalLines = value;
394  int _terminalLines;
395}
396
397/// A Stdio that collects stdout and supports simulated stdin.
398class MockStdio extends Stdio {
399  final MemoryStdout _stdout = MemoryStdout();
400  final MemoryIOSink _stderr = MemoryIOSink();
401  final StreamController<List<int>> _stdin = StreamController<List<int>>();
402
403  @override
404  MemoryStdout get stdout => _stdout;
405
406  @override
407  MemoryIOSink get stderr => _stderr;
408
409  @override
410  Stream<List<int>> get stdin => _stdin.stream;
411
412  void simulateStdin(String line) {
413    _stdin.add(utf8.encode('$line\n'));
414  }
415
416  List<String> get writtenToStdout => _stdout.writes.map<String>(_stdout.encoding.decode).toList();
417  List<String> get writtenToStderr => _stderr.writes.map<String>(_stderr.encoding.decode).toList();
418}
419
420class MockPollingDeviceDiscovery extends PollingDeviceDiscovery {
421  MockPollingDeviceDiscovery() : super('mock');
422
423  final List<Device> _devices = <Device>[];
424  final StreamController<Device> _onAddedController = StreamController<Device>.broadcast();
425  final StreamController<Device> _onRemovedController = StreamController<Device>.broadcast();
426
427  @override
428  Future<List<Device>> pollingGetDevices() async => _devices;
429
430  @override
431  bool get supportsPlatform => true;
432
433  @override
434  bool get canListAnything => true;
435
436  void addDevice(MockAndroidDevice device) {
437    _devices.add(device);
438
439    _onAddedController.add(device);
440  }
441
442  @override
443  Future<List<Device>> get devices async => _devices;
444
445  @override
446  Stream<Device> get onAdded => _onAddedController.stream;
447
448  @override
449  Stream<Device> get onRemoved => _onRemovedController.stream;
450}
451
452class MockIosProject extends Mock implements IosProject {
453  @override
454  String get productBundleIdentifier => 'com.example.test';
455
456  @override
457  String get hostAppBundleName => 'Runner.app';
458}
459
460class MockAndroidDevice extends Mock implements AndroidDevice {
461  @override
462  Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
463
464  @override
465  bool isSupported() => true;
466
467  @override
468  bool isSupportedForProject(FlutterProject flutterProject) => true;
469}
470
471class MockIOSDevice extends Mock implements IOSDevice {
472  @override
473  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
474
475  @override
476  bool isSupported() => true;
477
478  @override
479  bool isSupportedForProject(FlutterProject flutterProject) => true;
480}
481
482class MockIOSSimulator extends Mock implements IOSSimulator {
483  @override
484  Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
485
486  @override
487  bool isSupported() => true;
488
489  @override
490  bool isSupportedForProject(FlutterProject flutterProject) => true;
491}
492
493class MockDeviceLogReader extends DeviceLogReader {
494  @override
495  String get name => 'MockLogReader';
496
497  final StreamController<String> _linesController = StreamController<String>.broadcast();
498
499  @override
500  Stream<String> get logLines => _linesController.stream;
501
502  void addLine(String line) => _linesController.add(line);
503
504  void dispose() {
505    _linesController.close();
506  }
507}
508
509void applyMocksToCommand(FlutterCommand command) {
510  command
511    ..applicationPackages = MockApplicationPackageStore();
512}
513
514/// Common functionality for tracking mock interaction
515class BasicMock {
516  final List<String> messages = <String>[];
517
518  void expectMessages(List<String> expectedMessages) {
519    final List<String> actualMessages = List<String>.from(messages);
520    messages.clear();
521    expect(actualMessages, unorderedEquals(expectedMessages));
522  }
523
524  bool contains(String match) {
525    print('Checking for `$match` in:');
526    print(messages);
527    final bool result = messages.contains(match);
528    messages.clear();
529    return result;
530  }
531}
532
533class MockDevFSOperations extends BasicMock implements DevFSOperations {
534  Map<Uri, DevFSContent> devicePathToContent = <Uri, DevFSContent>{};
535
536  @override
537  Future<Uri> create(String fsName) async {
538    messages.add('create $fsName');
539    return Uri.parse('file:///$fsName');
540  }
541
542  @override
543  Future<dynamic> destroy(String fsName) async {
544    messages.add('destroy $fsName');
545  }
546
547  @override
548  Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content) async {
549    String message = 'writeFile $fsName $deviceUri';
550    if (content is DevFSFileContent) {
551      message += ' ${content.file.path}';
552    }
553    messages.add(message);
554    devicePathToContent[deviceUri] = content;
555  }
556}
557
558class MockResidentCompiler extends BasicMock implements ResidentCompiler {
559  @override
560  void accept() { }
561
562  @override
563  Future<CompilerOutput> reject() async { return null; }
564
565  @override
566  void reset() { }
567
568  @override
569  Future<dynamic> shutdown() async { }
570
571  @override
572  Future<CompilerOutput> compileExpression(
573    String expression,
574    List<String> definitions,
575    List<String> typeDefinitions,
576    String libraryUri,
577    String klass,
578    bool isStatic,
579  ) async {
580    return null;
581  }
582  @override
583  Future<CompilerOutput> recompile(String mainPath, List<Uri> invalidatedFiles, { String outputPath, String packagesFilePath }) async {
584    fs.file(outputPath).createSync(recursive: true);
585    fs.file(outputPath).writeAsStringSync('compiled_kernel_output');
586    return CompilerOutput(outputPath, 0, <Uri>[]);
587  }
588}
589
590/// A fake implementation of [ProcessResult].
591class FakeProcessResult implements ProcessResult {
592  FakeProcessResult({
593    this.exitCode = 0,
594    this.pid = 1,
595    this.stderr,
596    this.stdout,
597  });
598
599  @override
600  final int exitCode;
601
602  @override
603  final int pid;
604
605  @override
606  final dynamic stderr;
607
608  @override
609  final dynamic stdout;
610
611  @override
612  String toString() => stdout?.toString() ?? stderr?.toString() ?? runtimeType.toString();
613}
614
615class MockStdIn extends Mock implements IOSink {
616  final StringBuffer stdInWrites = StringBuffer();
617
618  String getAndClear() {
619    final String result = stdInWrites.toString();
620    stdInWrites.clear();
621    return result;
622  }
623
624  @override
625  void write([ Object o = '' ]) {
626    stdInWrites.write(o);
627  }
628
629  @override
630  void writeln([ Object o = '' ]) {
631    stdInWrites.writeln(o);
632  }
633}
634
635class MockStream extends Mock implements Stream<List<int>> {}
636