Ian Hickson | 449f4a6 | 2019-11-27 15:04:02 -0800 | [diff] [blame] | 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'dart:async'; |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 6 | import 'dart:convert'; |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 7 | |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 8 | import 'package:collection/collection.dart' show ListEquality; |
| 9 | import 'package:flutter_tools/src/android/android_sdk.dart'; |
| 10 | import 'package:flutter_tools/src/base/config.dart'; |
| 11 | import 'package:flutter_tools/src/base/io.dart'; |
Danny Tuppeny | d9983e1 | 2019-06-19 17:10:39 +0100 | [diff] [blame] | 12 | import 'package:flutter_tools/src/device.dart'; |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 13 | import 'package:flutter_tools/src/emulator.dart'; |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 14 | import 'package:flutter_tools/src/ios/ios_emulators.dart'; |
stuartmorgan | 81c38b2 | 2019-05-24 22:51:02 -0400 | [diff] [blame] | 15 | import 'package:flutter_tools/src/macos/xcode.dart'; |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 16 | import 'package:mockito/mockito.dart'; |
| 17 | import 'package:process/process.dart'; |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 18 | |
Ian Hickson | d919e69 | 2019-07-13 11:51:44 -0700 | [diff] [blame] | 19 | import '../src/common.dart'; |
| 20 | import '../src/context.dart'; |
| 21 | import '../src/mocks.dart'; |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 22 | |
| 23 | void main() { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 24 | MockProcessManager mockProcessManager; |
| 25 | MockConfig mockConfig; |
| 26 | MockAndroidSdk mockSdk; |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 27 | MockXcode mockXcode; |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 28 | |
| 29 | setUp(() { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 30 | mockProcessManager = MockProcessManager(); |
| 31 | mockConfig = MockConfig(); |
| 32 | mockSdk = MockAndroidSdk(); |
| 33 | mockXcode = MockXcode(); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 34 | |
| 35 | when(mockSdk.avdManagerPath).thenReturn('avdmanager'); |
| 36 | when(mockSdk.emulatorPath).thenReturn('emulator'); |
| 37 | }); |
| 38 | |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 39 | group('EmulatorManager', () { |
| 40 | testUsingContext('getEmulators', () async { |
| 41 | // Test that EmulatorManager.getEmulators() doesn't throw. |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 42 | final List<Emulator> emulators = |
| 43 | await emulatorManager.getAllAvailableEmulators(); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 44 | expect(emulators, isList); |
| 45 | }); |
| 46 | |
| 47 | testUsingContext('getEmulatorsById', () async { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 48 | final _MockEmulator emulator1 = |
Danny Tuppeny | 8dbfc82 | 2019-06-24 11:41:02 +0100 | [diff] [blame] | 49 | _MockEmulator('Nexus_5', 'Nexus 5', 'Google'); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 50 | final _MockEmulator emulator2 = |
Danny Tuppeny | 8dbfc82 | 2019-06-24 11:41:02 +0100 | [diff] [blame] | 51 | _MockEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google'); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 52 | final _MockEmulator emulator3 = |
Danny Tuppeny | 8dbfc82 | 2019-06-24 11:41:02 +0100 | [diff] [blame] | 53 | _MockEmulator('iOS Simulator', 'iOS Simulator', 'Apple'); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 54 | final List<Emulator> emulators = <Emulator>[ |
| 55 | emulator1, |
| 56 | emulator2, |
Alexandre Ardhuin | 387f885 | 2019-03-01 08:17:55 +0100 | [diff] [blame] | 57 | emulator3, |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 58 | ]; |
| 59 | final TestEmulatorManager testEmulatorManager = |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 60 | TestEmulatorManager(emulators); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 61 | |
Alexandre Ardhuin | 2d3ff10 | 2018-10-05 07:54:56 +0200 | [diff] [blame] | 62 | Future<void> expectEmulator(String id, List<Emulator> expected) async { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 63 | expect(await testEmulatorManager.getEmulatorsMatching(id), expected); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 64 | } |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 65 | |
xster | cda2c22 | 2018-08-30 20:57:44 -0700 | [diff] [blame] | 66 | await expectEmulator('Nexus_5', <Emulator>[emulator1]); |
| 67 | await expectEmulator('Nexus_5X', <Emulator>[emulator2]); |
| 68 | await expectEmulator('Nexus_5X_API_27_x86', <Emulator>[emulator2]); |
| 69 | await expectEmulator('Nexus', <Emulator>[emulator1, emulator2]); |
| 70 | await expectEmulator('iOS Simulator', <Emulator>[emulator3]); |
| 71 | await expectEmulator('ios', <Emulator>[emulator3]); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 72 | }); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 73 | |
Alexandre Ardhuin | 440ce8f | 2019-03-07 21:09:28 +0100 | [diff] [blame] | 74 | testUsingContext('create emulator with an empty name does not fail', () async { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 75 | final CreateEmulatorResult res = await emulatorManager.createEmulator(); |
| 76 | expect(res.success, equals(true)); |
| 77 | }, overrides: <Type, Generator>{ |
| 78 | ProcessManager: () => mockProcessManager, |
| 79 | Config: () => mockConfig, |
| 80 | AndroidSdk: () => mockSdk, |
| 81 | }); |
| 82 | |
Alexandre Ardhuin | 440ce8f | 2019-03-07 21:09:28 +0100 | [diff] [blame] | 83 | testUsingContext('create emulator with a unique name does not throw', () async { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 84 | final CreateEmulatorResult res = |
| 85 | await emulatorManager.createEmulator(name: 'test'); |
| 86 | expect(res.success, equals(true)); |
| 87 | }, overrides: <Type, Generator>{ |
| 88 | ProcessManager: () => mockProcessManager, |
| 89 | Config: () => mockConfig, |
| 90 | AndroidSdk: () => mockSdk, |
| 91 | }); |
| 92 | |
| 93 | testUsingContext('create emulator with an existing name errors', () async { |
| 94 | final CreateEmulatorResult res = |
| 95 | await emulatorManager.createEmulator(name: 'existing-avd-1'); |
| 96 | expect(res.success, equals(false)); |
| 97 | }, overrides: <Type, Generator>{ |
| 98 | ProcessManager: () => mockProcessManager, |
| 99 | Config: () => mockConfig, |
| 100 | AndroidSdk: () => mockSdk, |
| 101 | }); |
| 102 | |
Alexandre Ardhuin | 440ce8f | 2019-03-07 21:09:28 +0100 | [diff] [blame] | 103 | testUsingContext('create emulator without a name but when default exists adds a suffix', () async { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 104 | // First will get default name. |
| 105 | CreateEmulatorResult res = await emulatorManager.createEmulator(); |
| 106 | expect(res.success, equals(true)); |
| 107 | |
| 108 | final String defaultName = res.emulatorName; |
| 109 | |
| 110 | // Second... |
| 111 | res = await emulatorManager.createEmulator(); |
| 112 | expect(res.success, equals(true)); |
| 113 | expect(res.emulatorName, equals('${defaultName}_2')); |
| 114 | |
| 115 | // Third... |
| 116 | res = await emulatorManager.createEmulator(); |
| 117 | expect(res.success, equals(true)); |
| 118 | expect(res.emulatorName, equals('${defaultName}_3')); |
| 119 | }, overrides: <Type, Generator>{ |
| 120 | ProcessManager: () => mockProcessManager, |
| 121 | Config: () => mockConfig, |
| 122 | AndroidSdk: () => mockSdk, |
| 123 | }); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 124 | }); |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 125 | |
| 126 | group('ios_emulators', () { |
| 127 | bool didAttemptToRunSimulator = false; |
| 128 | setUp(() { |
| 129 | when(mockXcode.xcodeSelectPath).thenReturn('/fake/Xcode.app/Contents/Developer'); |
| 130 | when(mockXcode.getSimulatorPath()).thenAnswer((_) => '/fake/simulator.app'); |
| 131 | when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { |
Alexandre Ardhuin | 980f14e | 2019-11-24 06:54:43 +0100 | [diff] [blame] | 132 | final List<String> args = invocation.positionalArguments[0] as List<String>; |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 133 | if (args.length >= 3 && args[0] == 'open' && args[1] == '-a' && args[2] == '/fake/simulator.app') { |
| 134 | didAttemptToRunSimulator = true; |
| 135 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 136 | return ProcessResult(101, 0, '', ''); |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 137 | }); |
| 138 | }); |
| 139 | testUsingContext('runs correct launch commands', () async { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 140 | final Emulator emulator = IOSEmulator('ios'); |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 141 | await emulator.launch(); |
| 142 | expect(didAttemptToRunSimulator, equals(true)); |
| 143 | }, overrides: <Type, Generator>{ |
| 144 | ProcessManager: () => mockProcessManager, |
| 145 | Config: () => mockConfig, |
| 146 | Xcode: () => mockXcode, |
| 147 | }); |
| 148 | }); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 149 | } |
| 150 | |
| 151 | class TestEmulatorManager extends EmulatorManager { |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 152 | TestEmulatorManager(this.allEmulators); |
| 153 | |
Alexandre Ardhuin | 2ea1d81 | 2018-10-04 07:28:07 +0200 | [diff] [blame] | 154 | final List<Emulator> allEmulators; |
| 155 | |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 156 | @override |
Danny Tuppeny | 3cb539f | 2018-05-03 17:12:31 +0100 | [diff] [blame] | 157 | Future<List<Emulator>> getAllAvailableEmulators() { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 158 | return Future<List<Emulator>>.value(allEmulators); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 159 | } |
| 160 | } |
| 161 | |
| 162 | class _MockEmulator extends Emulator { |
Danny Tuppeny | 8dbfc82 | 2019-06-24 11:41:02 +0100 | [diff] [blame] | 163 | _MockEmulator(String id, this.name, this.manufacturer) |
Alexandre Ardhuin | ef276ff | 2019-01-29 21:47:16 +0100 | [diff] [blame] | 164 | : super(id, true); |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 165 | |
| 166 | @override |
Danny Tuppeny | 4d7c3c7 | 2018-04-18 13:43:22 +0100 | [diff] [blame] | 167 | final String name; |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 168 | |
Danny Tuppeny | 4d7c3c7 | 2018-04-18 13:43:22 +0100 | [diff] [blame] | 169 | @override |
| 170 | final String manufacturer; |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 171 | |
Danny Tuppeny | 4d7c3c7 | 2018-04-18 13:43:22 +0100 | [diff] [blame] | 172 | @override |
Danny Tuppeny | d9983e1 | 2019-06-19 17:10:39 +0100 | [diff] [blame] | 173 | Category get category => Category.mobile; |
| 174 | |
| 175 | @override |
| 176 | PlatformType get platformType => PlatformType.android; |
| 177 | |
| 178 | @override |
Danny Tuppeny | 48f4ff6 | 2018-06-14 07:59:50 +0100 | [diff] [blame] | 179 | Future<void> launch() { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 180 | throw UnimplementedError('Not implemented in Mock'); |
Danny Tuppeny | 4c67885 | 2018-04-18 14:50:16 +0100 | [diff] [blame] | 181 | } |
Danny Tuppeny | 486e953 | 2018-04-18 11:37:59 +0100 | [diff] [blame] | 182 | } |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 183 | |
| 184 | class MockConfig extends Mock implements Config {} |
| 185 | |
| 186 | class MockProcessManager extends Mock implements ProcessManager { |
| 187 | /// We have to send a command that fails in order to get the list of valid |
| 188 | /// system images paths. This is an example of the output to use in the mock. |
| 189 | static const String mockCreateFailureOutput = |
| 190 | 'Error: Package path (-k) not specified. Valid system image paths are:\n' |
| 191 | 'system-images;android-27;google_apis;x86\n' |
| 192 | 'system-images;android-P;google_apis;x86\n' |
| 193 | 'system-images;android-27;google_apis_playstore;x86\n' |
| 194 | 'null\n'; // Yep, these really end with null (on dantup's machine at least) |
| 195 | |
Alexandre Ardhuin | eda03e2 | 2018-08-02 12:02:32 +0200 | [diff] [blame] | 196 | static const ListEquality<String> _equality = ListEquality<String>(); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 197 | final List<String> _existingAvds = <String>['existing-avd-1']; |
| 198 | |
| 199 | @override |
| 200 | ProcessResult runSync( |
| 201 | List<dynamic> command, { |
| 202 | String workingDirectory, |
| 203 | Map<String, String> environment, |
| 204 | bool includeParentEnvironment = true, |
| 205 | bool runInShell = false, |
Konstantin Scheglov | 4fe41ab | 2019-01-29 11:49:57 -0800 | [diff] [blame] | 206 | Encoding stdoutEncoding = systemEncoding, |
Alexandre Ardhuin | 387f885 | 2019-03-01 08:17:55 +0100 | [diff] [blame] | 207 | Encoding stderrEncoding = systemEncoding, |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 208 | }) { |
Alexandre Ardhuin | 980f14e | 2019-11-24 06:54:43 +0100 | [diff] [blame] | 209 | final String program = command[0] as String; |
| 210 | final List<String> args = command.sublist(1) as List<String>; |
| 211 | switch (program) { |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 212 | case '/usr/bin/xcode-select': |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 213 | throw ProcessException(program, args); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 214 | break; |
| 215 | case 'emulator': |
| 216 | return _handleEmulator(args); |
| 217 | case 'avdmanager': |
| 218 | return _handleAvdManager(args); |
| 219 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 220 | throw StateError('Unexpected process call: $command'); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | ProcessResult _handleEmulator(List<String> args) { |
| 224 | if (_equality.equals(args, <String>['-list-avds'])) { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 225 | return ProcessResult(101, 0, '${_existingAvds.join('\n')}\n', ''); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 226 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 227 | throw ProcessException('emulator', args); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 228 | } |
| 229 | |
| 230 | ProcessResult _handleAvdManager(List<String> args) { |
| 231 | if (_equality.equals(args, <String>['list', 'device', '-c'])) { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 232 | return ProcessResult(101, 0, 'test\ntest2\npixel\npixel-xl\n', ''); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 233 | } |
| 234 | if (_equality.equals(args, <String>['create', 'avd', '-n', 'temp'])) { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 235 | return ProcessResult(101, 1, '', mockCreateFailureOutput); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 236 | } |
| 237 | if (args.length == 8 && |
| 238 | _equality.equals(args, |
| 239 | <String>['create', 'avd', '-n', args[3], '-k', args[5], '-d', args[7]])) { |
| 240 | // In order to support testing auto generation of names we need to support |
| 241 | // tracking any created emulators and reject when they already exist so this |
| 242 | // mock will compare the name of the AVD being created with the fake existing |
| 243 | // list and either reject if it exists, or add it to the list and return success. |
| 244 | final String name = args[3]; |
| 245 | // Error if this AVD already existed |
| 246 | if (_existingAvds.contains(name)) { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 247 | return ProcessResult( |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 248 | 101, |
| 249 | 1, |
| 250 | '', |
| 251 | "Error: Android Virtual Device '$name' already exists.\n" |
| 252 | 'Use --force if you want to replace it.'); |
| 253 | } else { |
| 254 | _existingAvds.add(name); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 255 | return ProcessResult(101, 0, '', ''); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 256 | } |
| 257 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 258 | throw ProcessException('emulator', args); |
Danny Tuppeny | cdb0118 | 2018-06-28 08:07:40 +0100 | [diff] [blame] | 259 | } |
| 260 | } |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 261 | |
| 262 | class MockXcode extends Mock implements Xcode {} |