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