| // 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 'package:collection/collection.dart' show ListEquality; |
| import 'package:flutter_tools/src/android/android_sdk.dart'; |
| import 'package:flutter_tools/src/base/config.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/emulator.dart'; |
| import 'package:flutter_tools/src/ios/ios_emulators.dart'; |
| import 'package:flutter_tools/src/macos/xcode.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:process/process.dart'; |
| |
| import 'src/common.dart'; |
| import 'src/context.dart'; |
| import 'src/mocks.dart'; |
| |
| void main() { |
| MockProcessManager mockProcessManager; |
| MockConfig mockConfig; |
| MockAndroidSdk mockSdk; |
| MockXcode mockXcode; |
| |
| setUp(() { |
| mockProcessManager = MockProcessManager(); |
| mockConfig = MockConfig(); |
| mockSdk = MockAndroidSdk(); |
| mockXcode = MockXcode(); |
| |
| when(mockSdk.avdManagerPath).thenReturn('avdmanager'); |
| when(mockSdk.emulatorPath).thenReturn('emulator'); |
| }); |
| |
| group('EmulatorManager', () { |
| testUsingContext('getEmulators', () async { |
| // Test that EmulatorManager.getEmulators() doesn't throw. |
| final List<Emulator> emulators = |
| await emulatorManager.getAllAvailableEmulators(); |
| expect(emulators, isList); |
| }); |
| |
| testUsingContext('getEmulatorsById', () async { |
| final _MockEmulator emulator1 = |
| _MockEmulator('Nexus_5', 'Nexus 5', 'Google'); |
| final _MockEmulator emulator2 = |
| _MockEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google'); |
| final _MockEmulator emulator3 = |
| _MockEmulator('iOS Simulator', 'iOS Simulator', 'Apple'); |
| final List<Emulator> emulators = <Emulator>[ |
| emulator1, |
| emulator2, |
| emulator3, |
| ]; |
| final TestEmulatorManager testEmulatorManager = |
| TestEmulatorManager(emulators); |
| |
| Future<void> expectEmulator(String id, List<Emulator> expected) async { |
| expect(await testEmulatorManager.getEmulatorsMatching(id), expected); |
| } |
| |
| await expectEmulator('Nexus_5', <Emulator>[emulator1]); |
| await expectEmulator('Nexus_5X', <Emulator>[emulator2]); |
| await expectEmulator('Nexus_5X_API_27_x86', <Emulator>[emulator2]); |
| await expectEmulator('Nexus', <Emulator>[emulator1, emulator2]); |
| await expectEmulator('iOS Simulator', <Emulator>[emulator3]); |
| await expectEmulator('ios', <Emulator>[emulator3]); |
| }); |
| |
| testUsingContext('create emulator with an empty name does not fail', () async { |
| final CreateEmulatorResult res = await emulatorManager.createEmulator(); |
| expect(res.success, equals(true)); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| Config: () => mockConfig, |
| AndroidSdk: () => mockSdk, |
| }); |
| |
| testUsingContext('create emulator with a unique name does not throw', () async { |
| final CreateEmulatorResult res = |
| await emulatorManager.createEmulator(name: 'test'); |
| expect(res.success, equals(true)); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| Config: () => mockConfig, |
| AndroidSdk: () => mockSdk, |
| }); |
| |
| testUsingContext('create emulator with an existing name errors', () async { |
| final CreateEmulatorResult res = |
| await emulatorManager.createEmulator(name: 'existing-avd-1'); |
| expect(res.success, equals(false)); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| Config: () => mockConfig, |
| AndroidSdk: () => mockSdk, |
| }); |
| |
| testUsingContext('create emulator without a name but when default exists adds a suffix', () async { |
| // First will get default name. |
| CreateEmulatorResult res = await emulatorManager.createEmulator(); |
| expect(res.success, equals(true)); |
| |
| final String defaultName = res.emulatorName; |
| |
| // Second... |
| res = await emulatorManager.createEmulator(); |
| expect(res.success, equals(true)); |
| expect(res.emulatorName, equals('${defaultName}_2')); |
| |
| // Third... |
| res = await emulatorManager.createEmulator(); |
| expect(res.success, equals(true)); |
| expect(res.emulatorName, equals('${defaultName}_3')); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| Config: () => mockConfig, |
| AndroidSdk: () => mockSdk, |
| }); |
| }); |
| |
| group('ios_emulators', () { |
| bool didAttemptToRunSimulator = false; |
| setUp(() { |
| when(mockXcode.xcodeSelectPath).thenReturn('/fake/Xcode.app/Contents/Developer'); |
| when(mockXcode.getSimulatorPath()).thenAnswer((_) => '/fake/simulator.app'); |
| when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { |
| final List<String> args = invocation.positionalArguments[0]; |
| if (args.length >= 3 && args[0] == 'open' && args[1] == '-a' && args[2] == '/fake/simulator.app') { |
| didAttemptToRunSimulator = true; |
| } |
| return ProcessResult(101, 0, '', ''); |
| }); |
| }); |
| testUsingContext('runs correct launch commands', () async { |
| final Emulator emulator = IOSEmulator('ios'); |
| await emulator.launch(); |
| expect(didAttemptToRunSimulator, equals(true)); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| Config: () => mockConfig, |
| Xcode: () => mockXcode, |
| }); |
| }); |
| } |
| |
| class TestEmulatorManager extends EmulatorManager { |
| TestEmulatorManager(this.allEmulators); |
| |
| final List<Emulator> allEmulators; |
| |
| @override |
| Future<List<Emulator>> getAllAvailableEmulators() { |
| return Future<List<Emulator>>.value(allEmulators); |
| } |
| } |
| |
| class _MockEmulator extends Emulator { |
| _MockEmulator(String id, this.name, this.manufacturer) |
| : super(id, true); |
| |
| @override |
| final String name; |
| |
| @override |
| final String manufacturer; |
| |
| @override |
| Category get category => Category.mobile; |
| |
| @override |
| PlatformType get platformType => PlatformType.android; |
| |
| @override |
| Future<void> launch() { |
| throw UnimplementedError('Not implemented in Mock'); |
| } |
| } |
| |
| class MockConfig extends Mock implements Config {} |
| |
| class MockProcessManager extends Mock implements ProcessManager { |
| /// We have to send a command that fails in order to get the list of valid |
| /// system images paths. This is an example of the output to use in the mock. |
| static const String mockCreateFailureOutput = |
| 'Error: Package path (-k) not specified. Valid system image paths are:\n' |
| 'system-images;android-27;google_apis;x86\n' |
| 'system-images;android-P;google_apis;x86\n' |
| 'system-images;android-27;google_apis_playstore;x86\n' |
| 'null\n'; // Yep, these really end with null (on dantup's machine at least) |
| |
| static const ListEquality<String> _equality = ListEquality<String>(); |
| final List<String> _existingAvds = <String>['existing-avd-1']; |
| |
| @override |
| ProcessResult runSync( |
| List<dynamic> command, { |
| String workingDirectory, |
| Map<String, String> environment, |
| bool includeParentEnvironment = true, |
| bool runInShell = false, |
| Encoding stdoutEncoding = systemEncoding, |
| Encoding stderrEncoding = systemEncoding, |
| }) { |
| final String program = command[0]; |
| final List<String> args = command.sublist(1); |
| switch (command[0]) { |
| case '/usr/bin/xcode-select': |
| throw ProcessException(program, args); |
| break; |
| case 'emulator': |
| return _handleEmulator(args); |
| case 'avdmanager': |
| return _handleAvdManager(args); |
| } |
| throw StateError('Unexpected process call: $command'); |
| } |
| |
| ProcessResult _handleEmulator(List<String> args) { |
| if (_equality.equals(args, <String>['-list-avds'])) { |
| return ProcessResult(101, 0, '${_existingAvds.join('\n')}\n', ''); |
| } |
| throw ProcessException('emulator', args); |
| } |
| |
| ProcessResult _handleAvdManager(List<String> args) { |
| if (_equality.equals(args, <String>['list', 'device', '-c'])) { |
| return ProcessResult(101, 0, 'test\ntest2\npixel\npixel-xl\n', ''); |
| } |
| if (_equality.equals(args, <String>['create', 'avd', '-n', 'temp'])) { |
| return ProcessResult(101, 1, '', mockCreateFailureOutput); |
| } |
| if (args.length == 8 && |
| _equality.equals(args, |
| <String>['create', 'avd', '-n', args[3], '-k', args[5], '-d', args[7]])) { |
| // In order to support testing auto generation of names we need to support |
| // tracking any created emulators and reject when they already exist so this |
| // mock will compare the name of the AVD being created with the fake existing |
| // list and either reject if it exists, or add it to the list and return success. |
| final String name = args[3]; |
| // Error if this AVD already existed |
| if (_existingAvds.contains(name)) { |
| return ProcessResult( |
| 101, |
| 1, |
| '', |
| "Error: Android Virtual Device '$name' already exists.\n" |
| 'Use --force if you want to replace it.'); |
| } else { |
| _existingAvds.add(name); |
| return ProcessResult(101, 0, '', ''); |
| } |
| } |
| throw ProcessException('emulator', args); |
| } |
| } |
| |
| class MockXcode extends Mock implements Xcode {} |