blob: 5a1a8e93ff7b4baa203a9853c9b7acffb9026044 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Danny Tuppeny486e9532018-04-18 11:37:59 +01002// 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';
Danny Tuppenycdb01182018-06-28 08:07:40 +01006import 'dart:convert';
Danny Tuppeny486e9532018-04-18 11:37:59 +01007
Danny Tuppenycdb01182018-06-28 08:07:40 +01008import 'package:collection/collection.dart' show ListEquality;
9import 'package:flutter_tools/src/android/android_sdk.dart';
10import 'package:flutter_tools/src/base/config.dart';
11import 'package:flutter_tools/src/base/io.dart';
Danny Tuppenyd9983e12019-06-19 17:10:39 +010012import 'package:flutter_tools/src/device.dart';
Danny Tuppeny486e9532018-04-18 11:37:59 +010013import 'package:flutter_tools/src/emulator.dart';
Danny Tuppenyd3f61282018-07-19 10:32:44 +010014import 'package:flutter_tools/src/ios/ios_emulators.dart';
stuartmorgan81c38b22019-05-24 22:51:02 -040015import 'package:flutter_tools/src/macos/xcode.dart';
Danny Tuppenycdb01182018-06-28 08:07:40 +010016import 'package:mockito/mockito.dart';
17import 'package:process/process.dart';
Danny Tuppeny486e9532018-04-18 11:37:59 +010018
Ian Hicksond919e692019-07-13 11:51:44 -070019import '../src/common.dart';
20import '../src/context.dart';
21import '../src/mocks.dart';
Danny Tuppeny486e9532018-04-18 11:37:59 +010022
23void main() {
Danny Tuppenycdb01182018-06-28 08:07:40 +010024 MockProcessManager mockProcessManager;
25 MockConfig mockConfig;
26 MockAndroidSdk mockSdk;
Danny Tuppenyd3f61282018-07-19 10:32:44 +010027 MockXcode mockXcode;
Danny Tuppenycdb01182018-06-28 08:07:40 +010028
29 setUp(() {
Alexandre Ardhuind927c932018-09-12 08:29:29 +020030 mockProcessManager = MockProcessManager();
31 mockConfig = MockConfig();
32 mockSdk = MockAndroidSdk();
33 mockXcode = MockXcode();
Danny Tuppenycdb01182018-06-28 08:07:40 +010034
35 when(mockSdk.avdManagerPath).thenReturn('avdmanager');
36 when(mockSdk.emulatorPath).thenReturn('emulator');
37 });
38
Danny Tuppeny486e9532018-04-18 11:37:59 +010039 group('EmulatorManager', () {
40 testUsingContext('getEmulators', () async {
41 // Test that EmulatorManager.getEmulators() doesn't throw.
Danny Tuppenycdb01182018-06-28 08:07:40 +010042 final List<Emulator> emulators =
43 await emulatorManager.getAllAvailableEmulators();
Danny Tuppeny486e9532018-04-18 11:37:59 +010044 expect(emulators, isList);
45 });
46
47 testUsingContext('getEmulatorsById', () async {
Danny Tuppenycdb01182018-06-28 08:07:40 +010048 final _MockEmulator emulator1 =
Danny Tuppeny8dbfc822019-06-24 11:41:02 +010049 _MockEmulator('Nexus_5', 'Nexus 5', 'Google');
Danny Tuppenycdb01182018-06-28 08:07:40 +010050 final _MockEmulator emulator2 =
Danny Tuppeny8dbfc822019-06-24 11:41:02 +010051 _MockEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google');
Danny Tuppenycdb01182018-06-28 08:07:40 +010052 final _MockEmulator emulator3 =
Danny Tuppeny8dbfc822019-06-24 11:41:02 +010053 _MockEmulator('iOS Simulator', 'iOS Simulator', 'Apple');
Danny Tuppenycdb01182018-06-28 08:07:40 +010054 final List<Emulator> emulators = <Emulator>[
55 emulator1,
56 emulator2,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +010057 emulator3,
Danny Tuppenycdb01182018-06-28 08:07:40 +010058 ];
59 final TestEmulatorManager testEmulatorManager =
Alexandre Ardhuind927c932018-09-12 08:29:29 +020060 TestEmulatorManager(emulators);
Danny Tuppeny486e9532018-04-18 11:37:59 +010061
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +020062 Future<void> expectEmulator(String id, List<Emulator> expected) async {
Danny Tuppenycdb01182018-06-28 08:07:40 +010063 expect(await testEmulatorManager.getEmulatorsMatching(id), expected);
Danny Tuppeny486e9532018-04-18 11:37:59 +010064 }
Danny Tuppenycdb01182018-06-28 08:07:40 +010065
xstercda2c222018-08-30 20:57:44 -070066 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 Tuppeny486e9532018-04-18 11:37:59 +010072 });
Danny Tuppenycdb01182018-06-28 08:07:40 +010073
Alexandre Ardhuin440ce8f2019-03-07 21:09:28 +010074 testUsingContext('create emulator with an empty name does not fail', () async {
Danny Tuppenycdb01182018-06-28 08:07:40 +010075 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 Ardhuin440ce8f2019-03-07 21:09:28 +010083 testUsingContext('create emulator with a unique name does not throw', () async {
Danny Tuppenycdb01182018-06-28 08:07:40 +010084 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 Ardhuin440ce8f2019-03-07 21:09:28 +0100103 testUsingContext('create emulator without a name but when default exists adds a suffix', () async {
Danny Tuppenycdb01182018-06-28 08:07:40 +0100104 // 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 Tuppeny486e9532018-04-18 11:37:59 +0100124 });
Danny Tuppenyd3f61282018-07-19 10:32:44 +0100125
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 Ardhuin980f14e2019-11-24 06:54:43 +0100132 final List<String> args = invocation.positionalArguments[0] as List<String>;
Danny Tuppenyd3f61282018-07-19 10:32:44 +0100133 if (args.length >= 3 && args[0] == 'open' && args[1] == '-a' && args[2] == '/fake/simulator.app') {
134 didAttemptToRunSimulator = true;
135 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200136 return ProcessResult(101, 0, '', '');
Danny Tuppenyd3f61282018-07-19 10:32:44 +0100137 });
138 });
139 testUsingContext('runs correct launch commands', () async {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200140 final Emulator emulator = IOSEmulator('ios');
Danny Tuppenyd3f61282018-07-19 10:32:44 +0100141 await emulator.launch();
142 expect(didAttemptToRunSimulator, equals(true));
143 }, overrides: <Type, Generator>{
144 ProcessManager: () => mockProcessManager,
145 Config: () => mockConfig,
146 Xcode: () => mockXcode,
147 });
148 });
Danny Tuppeny486e9532018-04-18 11:37:59 +0100149}
150
151class TestEmulatorManager extends EmulatorManager {
Danny Tuppeny486e9532018-04-18 11:37:59 +0100152 TestEmulatorManager(this.allEmulators);
153
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200154 final List<Emulator> allEmulators;
155
Danny Tuppeny486e9532018-04-18 11:37:59 +0100156 @override
Danny Tuppeny3cb539f2018-05-03 17:12:31 +0100157 Future<List<Emulator>> getAllAvailableEmulators() {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200158 return Future<List<Emulator>>.value(allEmulators);
Danny Tuppeny486e9532018-04-18 11:37:59 +0100159 }
160}
161
162class _MockEmulator extends Emulator {
Danny Tuppeny8dbfc822019-06-24 11:41:02 +0100163 _MockEmulator(String id, this.name, this.manufacturer)
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100164 : super(id, true);
Danny Tuppeny486e9532018-04-18 11:37:59 +0100165
166 @override
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +0100167 final String name;
Danny Tuppenycdb01182018-06-28 08:07:40 +0100168
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +0100169 @override
170 final String manufacturer;
Danny Tuppenycdb01182018-06-28 08:07:40 +0100171
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +0100172 @override
Danny Tuppenyd9983e12019-06-19 17:10:39 +0100173 Category get category => Category.mobile;
174
175 @override
176 PlatformType get platformType => PlatformType.android;
177
178 @override
Danny Tuppeny48f4ff62018-06-14 07:59:50 +0100179 Future<void> launch() {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200180 throw UnimplementedError('Not implemented in Mock');
Danny Tuppeny4c678852018-04-18 14:50:16 +0100181 }
Danny Tuppeny486e9532018-04-18 11:37:59 +0100182}
Danny Tuppenycdb01182018-06-28 08:07:40 +0100183
184class MockConfig extends Mock implements Config {}
185
186class 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 Ardhuineda03e22018-08-02 12:02:32 +0200196 static const ListEquality<String> _equality = ListEquality<String>();
Danny Tuppenycdb01182018-06-28 08:07:40 +0100197 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 Scheglov4fe41ab2019-01-29 11:49:57 -0800206 Encoding stdoutEncoding = systemEncoding,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100207 Encoding stderrEncoding = systemEncoding,
Danny Tuppenycdb01182018-06-28 08:07:40 +0100208 }) {
Alexandre Ardhuin980f14e2019-11-24 06:54:43 +0100209 final String program = command[0] as String;
210 final List<String> args = command.sublist(1) as List<String>;
211 switch (program) {
Danny Tuppenycdb01182018-06-28 08:07:40 +0100212 case '/usr/bin/xcode-select':
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200213 throw ProcessException(program, args);
Danny Tuppenycdb01182018-06-28 08:07:40 +0100214 break;
215 case 'emulator':
216 return _handleEmulator(args);
217 case 'avdmanager':
218 return _handleAvdManager(args);
219 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200220 throw StateError('Unexpected process call: $command');
Danny Tuppenycdb01182018-06-28 08:07:40 +0100221 }
222
223 ProcessResult _handleEmulator(List<String> args) {
224 if (_equality.equals(args, <String>['-list-avds'])) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200225 return ProcessResult(101, 0, '${_existingAvds.join('\n')}\n', '');
Danny Tuppenycdb01182018-06-28 08:07:40 +0100226 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200227 throw ProcessException('emulator', args);
Danny Tuppenycdb01182018-06-28 08:07:40 +0100228 }
229
230 ProcessResult _handleAvdManager(List<String> args) {
231 if (_equality.equals(args, <String>['list', 'device', '-c'])) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200232 return ProcessResult(101, 0, 'test\ntest2\npixel\npixel-xl\n', '');
Danny Tuppenycdb01182018-06-28 08:07:40 +0100233 }
234 if (_equality.equals(args, <String>['create', 'avd', '-n', 'temp'])) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200235 return ProcessResult(101, 1, '', mockCreateFailureOutput);
Danny Tuppenycdb01182018-06-28 08:07:40 +0100236 }
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 Ardhuind927c932018-09-12 08:29:29 +0200247 return ProcessResult(
Danny Tuppenycdb01182018-06-28 08:07:40 +0100248 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 Ardhuind927c932018-09-12 08:29:29 +0200255 return ProcessResult(101, 0, '', '');
Danny Tuppenycdb01182018-06-28 08:07:40 +0100256 }
257 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200258 throw ProcessException('emulator', args);
Danny Tuppenycdb01182018-06-28 08:07:40 +0100259 }
260}
Danny Tuppenyd3f61282018-07-19 10:32:44 +0100261
262class MockXcode extends Mock implements Xcode {}