// 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:async'; import 'package:flutter_tools/src/android/android_emulator.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/fake_process_manager.dart'; const emulatorID = 'i1234'; const errorText = '[Android emulator test error]'; const kEmulatorLaunchCommand = ['emulator', '-avd', emulatorID]; void main() { group('android_emulator', () { testWithoutContext('flags emulators without config', () { const emulatorID = '1234'; final emulator = AndroidEmulator( emulatorID, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: FakeAndroidSdk(), ); expect(emulator.id, emulatorID); expect(emulator.hasConfig, false); }); testWithoutContext('flags emulators with config', () { const emulatorID = '1234'; final emulator = AndroidEmulator( emulatorID, properties: const {'name': 'test'}, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: FakeAndroidSdk(), ); expect(emulator.id, emulatorID); expect(emulator.hasConfig, true); }); testWithoutContext('reads expected metadata', () { const emulatorID = '1234'; const manufacturer = 'Me'; const displayName = 'The best one'; final properties = { 'hw.device.manufacturer': manufacturer, 'avd.ini.displayname': displayName, }; final emulator = AndroidEmulator( emulatorID, properties: properties, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: FakeAndroidSdk(), ); expect(emulator.id, emulatorID); expect(emulator.name, displayName); expect(emulator.manufacturer, manufacturer); expect(emulator.category, Category.mobile); expect(emulator.platformType, PlatformType.android); }); testWithoutContext('prefers displayname for name', () { const emulatorID = '1234'; const displayName = 'The best one'; final properties = {'avd.ini.displayname': displayName}; final emulator = AndroidEmulator( emulatorID, properties: properties, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: FakeAndroidSdk(), ); expect(emulator.name, displayName); }); testWithoutContext('uses cleaned up ID if no displayname is set', () { // Android Studio uses the ID with underscores replaced with spaces // for the name if displayname is not set so we do the same. const emulatorID = 'This_is_my_ID'; final properties = {'avd.ini.notadisplayname': 'this is not a display name'}; final emulator = AndroidEmulator( emulatorID, properties: properties, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: FakeAndroidSdk(), ); expect(emulator.name, 'This is my ID'); }); testWithoutContext('parses ini files', () { const iniFile = ''' hw.device.name=My Test Name #hw.device.name=Bad Name hw.device.manufacturer=Me avd.ini.displayname = dispName '''; final Map results = parseIniLines(iniFile.split('\n')); expect(results['hw.device.name'], 'My Test Name'); expect(results['hw.device.manufacturer'], 'Me'); expect(results['avd.ini.displayname'], 'dispName'); }); }); group('Android emulator launch ', () { late FakeAndroidSdk mockSdk; setUp(() { mockSdk = FakeAndroidSdk(); mockSdk.emulatorPath = 'emulator'; }); testWithoutContext('succeeds', () async { final emulator = AndroidEmulator( emulatorID, processManager: FakeProcessManager.list([ const FakeCommand(command: kEmulatorLaunchCommand), ]), androidSdk: mockSdk, logger: BufferLogger.test(), ); await emulator.launch(startupDuration: Duration.zero); }); testWithoutContext('succeeds with coldboot launch', () async { final kEmulatorLaunchColdBootCommand = [ ...kEmulatorLaunchCommand, '-no-snapshot-load', ]; final emulator = AndroidEmulator( emulatorID, processManager: FakeProcessManager.list([ FakeCommand(command: kEmulatorLaunchColdBootCommand), ]), androidSdk: mockSdk, logger: BufferLogger.test(), ); await emulator.launch(startupDuration: Duration.zero, coldBoot: true); }); testWithoutContext('prints error on failure', () async { final logger = BufferLogger.test(); final emulator = AndroidEmulator( emulatorID, processManager: FakeProcessManager.list([ const FakeCommand( command: kEmulatorLaunchCommand, exitCode: 1, stderr: errorText, stdout: 'dummy text', ), ]), androidSdk: mockSdk, logger: logger, ); await emulator.launch(startupDuration: Duration.zero); expect(logger.errorText, contains(errorText)); }); testWithoutContext('prints nothing on late failure with empty stderr', () async { final logger = BufferLogger.test(); final emulator = AndroidEmulator( emulatorID, processManager: FakeProcessManager.list([ FakeCommand( command: kEmulatorLaunchCommand, exitCode: 1, stdout: 'dummy text', completer: Completer(), ), ]), androidSdk: mockSdk, logger: logger, ); await emulator.launch(startupDuration: Duration.zero); expect(logger.errorText, isEmpty); }); testWithoutContext('throws if emulator not found', () async { mockSdk.emulatorPath = null; final emulator = AndroidEmulator( emulatorID, processManager: FakeProcessManager.empty(), androidSdk: mockSdk, logger: BufferLogger.test(), ); await expectLater( () => emulator.launch(startupDuration: Duration.zero), throwsA( isException.having( (Exception exception) => exception.toString(), 'description', contains('Emulator is missing from the Android SDK'), ), ), ); }); }); } class FakeAndroidSdk extends Fake implements AndroidSdk { @override String? emulatorPath; }