| // Copyright 2014 The Flutter 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:convert'; |
| |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/android/android_workflow.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/base/logger.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'; |
| import '../src/testbed.dart'; |
| |
| const FakeEmulator emulator1 = FakeEmulator('Nexus_5', 'Nexus 5', 'Google'); |
| const FakeEmulator emulator2 = FakeEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google'); |
| const FakeEmulator emulator3 = FakeEmulator('iOS Simulator', 'iOS Simulator', 'Apple'); |
| const List<Emulator> emulators = <Emulator>[ |
| emulator1, |
| emulator2, |
| emulator3, |
| ]; |
| |
| // 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. |
| const String fakeCreateFailureOutput = |
| '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) |
| |
| const FakeCommand kListEmulatorsCommand = FakeCommand( |
| command: <String>['avdmanager', 'create', 'avd', '-n', 'temp'], |
| stderr: fakeCreateFailureOutput, |
| exitCode: 1, |
| ); |
| |
| void main() { |
| MockProcessManager mockProcessManager; |
| MockAndroidSdk mockSdk; |
| MockXcode mockXcode; |
| |
| setUp(() { |
| mockProcessManager = MockProcessManager(); |
| mockSdk = MockAndroidSdk(); |
| mockXcode = MockXcode(); |
| |
| when(mockSdk.avdManagerPath).thenReturn('avdmanager'); |
| when(mockSdk.getAvdManagerPath()).thenReturn('avdmanager'); |
| when(mockSdk.emulatorPath).thenReturn('emulator'); |
| when(mockSdk.adbPath).thenReturn('adb'); |
| }); |
| |
| group('EmulatorManager', () { |
| // iOS discovery uses context. |
| testUsingContext('getEmulators', () async { |
| // Test that EmulatorManager.getEmulators() doesn't throw. |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['emulator', '-list-avds'], |
| stdout: 'existing-avd-1', |
| ), |
| ]), |
| androidSdk: mockSdk, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: mockSdk, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| |
| await expectLater(() async => await emulatorManager.getAllAvailableEmulators(), |
| returnsNormally); |
| }); |
| |
| testUsingContext('getEmulators with no Android SDK', () async { |
| // Test that EmulatorManager.getEmulators() doesn't throw when there's no Android SDK. |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['emulator', '-list-avds'], |
| stdout: 'existing-avd-1', |
| ), |
| ]), |
| androidSdk: null, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: null, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| |
| await expectLater(() async => await emulatorManager.getAllAvailableEmulators(), |
| returnsNormally); |
| }); |
| |
| testWithoutContext('getEmulatorsById', () async { |
| final TestEmulatorManager testEmulatorManager = TestEmulatorManager(emulators); |
| |
| expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5'), <Emulator>[emulator1]); |
| expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5X'), <Emulator>[emulator2]); |
| expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5X_API_27_x86'), <Emulator>[emulator2]); |
| expect(await testEmulatorManager.getEmulatorsMatching('Nexus'), <Emulator>[emulator1, emulator2]); |
| expect(await testEmulatorManager.getEmulatorsMatching('iOS Simulator'), <Emulator>[emulator3]); |
| expect(await testEmulatorManager.getEmulatorsMatching('ios'), <Emulator>[emulator3]); |
| }); |
| |
| testUsingContext('create emulator with a missing avdmanager does not crash.', () async { |
| when(mockSdk.avdManagerPath).thenReturn(null); |
| when(mockSdk.getAvdManagerPath()).thenReturn(null); |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['emulator', '-list-avds'], |
| stdout: 'existing-avd-1', |
| ), |
| ]), |
| androidSdk: mockSdk, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: mockSdk, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| final CreateEmulatorResult result = await emulatorManager.createEmulator(); |
| |
| expect(result.success, false); |
| expect(result.error, contains('avdmanager is missing from the Android SDK')); |
| }); |
| |
| // iOS discovery uses context. |
| testUsingContext('create emulator with an empty name does not fail', () async { |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['emulator', '-list-avds'], |
| stdout: 'existing-avd-1', |
| ), |
| const FakeCommand( |
| command: <String>['avdmanager', 'list', 'device', '-c'], |
| stdout: 'test\ntest2\npixel\npixel-xl\n', |
| ), |
| kListEmulatorsCommand, |
| const FakeCommand( |
| command: <String>[ |
| 'avdmanager', |
| 'create', |
| 'avd', |
| '-n', |
| 'flutter_emulator', |
| '-k', |
| 'system-images;android-27;google_apis_playstore;x86', |
| '-d', |
| 'pixel', |
| ], |
| ) |
| ]), |
| androidSdk: mockSdk, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: mockSdk, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| final CreateEmulatorResult result = await emulatorManager.createEmulator(); |
| |
| expect(result.success, true); |
| }); |
| |
| testWithoutContext('create emulator with a unique name does not throw', () async { |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['avdmanager', 'list', 'device', '-c'], |
| stdout: 'test\ntest2\npixel\npixel-xl\n', |
| ), |
| kListEmulatorsCommand, |
| const FakeCommand( |
| command: <String>[ |
| 'avdmanager', |
| 'create', |
| 'avd', |
| // The specified name is given with the -n flag. |
| '-n', |
| 'test', |
| '-k', |
| 'system-images;android-27;google_apis_playstore;x86', |
| '-d', |
| 'pixel', |
| ], |
| ) |
| ]), |
| androidSdk: mockSdk, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: mockSdk, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'test'); |
| |
| expect(result.success, true); |
| }); |
| |
| testWithoutContext('create emulator with an existing name errors', () async { |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['avdmanager', 'list', 'device', '-c'], |
| stdout: 'test\ntest2\npixel\npixel-xl\n', |
| ), |
| kListEmulatorsCommand, |
| const FakeCommand( |
| command: <String>[ |
| 'avdmanager', |
| 'create', |
| 'avd', |
| '-n', |
| 'existing-avd-1', |
| '-k', |
| 'system-images;android-27;google_apis_playstore;x86', |
| '-d', |
| 'pixel', |
| ], |
| exitCode: 1, |
| stderr: "Error: Android Virtual Device 'existing-avd-1' already exists.\n" |
| 'Use --force if you want to replace it.' |
| ) |
| ]), |
| androidSdk: mockSdk, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: mockSdk, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'existing-avd-1'); |
| |
| expect(result.success, false); |
| }); |
| |
| // iOS discovery uses context. |
| testUsingContext('create emulator without a name but when default exists adds a suffix', () async { |
| final EmulatorManager emulatorManager = EmulatorManager( |
| fileSystem: MemoryFileSystem.test(), |
| logger: BufferLogger.test(), |
| processManager: FakeProcessManager.list(<FakeCommand>[ |
| const FakeCommand( |
| command: <String>['emulator', '-list-avds'], |
| stdout: 'existing-avd-1\nflutter_emulator', |
| ), |
| const FakeCommand( |
| command: <String>['avdmanager', 'list', 'device', '-c'], |
| stdout: 'test\ntest2\npixel\npixel-xl\n', |
| ), |
| kListEmulatorsCommand, |
| const FakeCommand( |
| command: <String>[ |
| 'avdmanager', |
| 'create', |
| 'avd', |
| // a "_2" suffix is added to disambiguate from the existing emulator. |
| '-n', |
| 'flutter_emulator_2', |
| '-k', |
| 'system-images;android-27;google_apis_playstore;x86', |
| '-d', |
| 'pixel', |
| ], |
| ) |
| ]), |
| androidSdk: mockSdk, |
| androidWorkflow: AndroidWorkflow( |
| androidSdk: mockSdk, |
| featureFlags: TestFeatureFlags(), |
| ), |
| ); |
| final CreateEmulatorResult result = await emulatorManager.createEmulator(); |
| |
| expect(result.success, true); |
| expect(result.emulatorName, 'flutter_emulator_2'); |
| }); |
| }); |
| |
| 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] as List<String>; |
| 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 { |
| const Emulator emulator = IOSEmulator('ios'); |
| await emulator.launch(); |
| expect(didAttemptToRunSimulator, equals(true)); |
| }, overrides: <Type, Generator>{ |
| ProcessManager: () => mockProcessManager, |
| 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 FakeEmulator extends Emulator { |
| const FakeEmulator(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 MockProcessManager extends Mock implements ProcessManager { |
| |
| @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] as String; |
| final List<String> args = command.sublist(1) as List<String>; |
| switch (program) { |
| case '/usr/bin/xcode-select': |
| throw ProcessException(program, args); |
| break; |
| } |
| throw StateError('Unexpected process call: $command'); |
| } |
| } |
| |
| class MockXcode extends Mock implements Xcode {} |