// 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. // @dart = 2.8 import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_workflow.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:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; import '../src/fakes.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 emulators = [ 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 fake. 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: ['avdmanager', 'create', 'avd', '-n', 'temp'], stderr: fakeCreateFailureOutput, exitCode: 1, ); void main() { FakeProcessManager fakeProcessManager; FakeAndroidSdk sdk; FileSystem fileSystem; Xcode xcode; setUp(() { fileSystem = MemoryFileSystem.test(); fakeProcessManager = FakeProcessManager.empty(); sdk = FakeAndroidSdk(); xcode = Xcode.test(processManager: fakeProcessManager, fileSystem: fileSystem); sdk ..avdManagerPath = 'avdmanager' ..emulatorPath = 'emulator' ..adbPath = '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([ const FakeCommand( command: ['emulator', '-list-avds'], stdout: 'existing-avd-1', ), ]), androidSdk: sdk, androidWorkflow: AndroidWorkflow( androidSdk: sdk, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); await expectLater(() async => 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([ const FakeCommand( command: ['emulator', '-list-avds'], stdout: 'existing-avd-1', ), ]), androidSdk: null, androidWorkflow: AndroidWorkflow( androidSdk: null, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); await expectLater(() async => emulatorManager.getAllAvailableEmulators(), returnsNormally); }); testWithoutContext('getEmulatorsById', () async { final TestEmulatorManager testEmulatorManager = TestEmulatorManager(emulators); expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5'), [emulator1]); expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5X'), [emulator2]); expect(await testEmulatorManager.getEmulatorsMatching('Nexus_5X_API_27_x86'), [emulator2]); expect(await testEmulatorManager.getEmulatorsMatching('Nexus'), [emulator1, emulator2]); expect(await testEmulatorManager.getEmulatorsMatching('iOS Simulator'), [emulator3]); expect(await testEmulatorManager.getEmulatorsMatching('ios'), [emulator3]); }); testUsingContext('create emulator with a missing avdmanager does not crash.', () async { sdk.avdManagerPath = null; final EmulatorManager emulatorManager = EmulatorManager( fileSystem: MemoryFileSystem.test(), logger: BufferLogger.test(), processManager: FakeProcessManager.list([ const FakeCommand( command: ['emulator', '-list-avds'], stdout: 'existing-avd-1', ), ]), androidSdk: sdk, androidWorkflow: AndroidWorkflow( androidSdk: sdk, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); 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([ const FakeCommand( command: ['emulator', '-list-avds'], stdout: 'existing-avd-1', ), const FakeCommand( command: ['avdmanager', 'list', 'device', '-c'], stdout: 'test\ntest2\npixel\npixel-xl\n', ), kListEmulatorsCommand, const FakeCommand( command: [ 'avdmanager', 'create', 'avd', '-n', 'flutter_emulator', '-k', 'system-images;android-27;google_apis_playstore;x86', '-d', 'pixel', ], ) ]), androidSdk: sdk, androidWorkflow: AndroidWorkflow( androidSdk: sdk, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); 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([ const FakeCommand( command: ['avdmanager', 'list', 'device', '-c'], stdout: 'test\ntest2\npixel\npixel-xl\n', ), kListEmulatorsCommand, const FakeCommand( command: [ '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: sdk, androidWorkflow: AndroidWorkflow( androidSdk: sdk, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); 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([ const FakeCommand( command: ['avdmanager', 'list', 'device', '-c'], stdout: 'test\ntest2\npixel\npixel-xl\n', ), kListEmulatorsCommand, const FakeCommand( command: [ '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: sdk, androidWorkflow: AndroidWorkflow( androidSdk: sdk, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); 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([ const FakeCommand( command: ['emulator', '-list-avds'], stdout: 'existing-avd-1\nflutter_emulator', ), const FakeCommand( command: ['avdmanager', 'list', 'device', '-c'], stdout: 'test\ntest2\npixel\npixel-xl\n', ), kListEmulatorsCommand, const FakeCommand( command: [ '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: sdk, androidWorkflow: AndroidWorkflow( androidSdk: sdk, featureFlags: TestFeatureFlags(), operatingSystemUtils: FakeOperatingSystemUtils(), ), ); final CreateEmulatorResult result = await emulatorManager.createEmulator(); expect(result.success, true); expect(result.emulatorName, 'flutter_emulator_2'); }); }); group('ios_emulators', () { testUsingContext('runs correct launch commands', () async { fileSystem.directory('/fake/Xcode.app/Contents/Developer/Applications/Simulator.app').createSync(recursive: true); fakeProcessManager.addCommands( [ const FakeCommand( command: ['/usr/bin/xcode-select', '--print-path'], stdout: '/fake/Xcode.app/Contents/Developer', ), const FakeCommand(command: [ 'open', '-n', '-a', '/fake/Xcode.app/Contents/Developer/Applications/Simulator.app', ]), const FakeCommand(command: [ 'open', '-a', '/fake/Xcode.app/Contents/Developer/Applications/Simulator.app', ]) ], ); const Emulator emulator = IOSEmulator('ios'); await emulator.launch(); expect(fakeProcessManager.hasRemainingExpectations, isFalse); }, overrides: { ProcessManager: () => fakeProcessManager, Xcode: () => xcode, FileSystem: () => fileSystem, }); }); } class TestEmulatorManager extends EmulatorManager { TestEmulatorManager(this.allEmulators); final List allEmulators; @override Future> getAllAvailableEmulators() { return Future>.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 launch({bool coldBoot = false}) { throw UnimplementedError('Not implemented in Mock'); } } class FakeAndroidSdk extends Fake implements AndroidSdk { @override String avdManagerPath; @override String emulatorPath; @override String adbPath; @override String getAvdManagerPath() => avdManagerPath; @override String getAvdPath() => 'avd'; @override Map get sdkManagerEnv => {}; }