// 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/base/common.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:mockito/mockito.dart'; import 'package:fake_async/fake_async.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fake_process_manager.dart'; import '../../src/mocks.dart' show MockAndroidSdk; const String emulatorID = 'i1234'; const String errorText = '[Android emulator test error]'; const List<String> kEmulatorLaunchCommand = <String>[ 'emulator', '-avd', emulatorID, ]; void main() { group('android_emulator', () { testWithoutContext('flags emulators without config', () { const String emulatorID = '1234'; final AndroidEmulator emulator = AndroidEmulator( emulatorID, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: MockAndroidSdk(), ); expect(emulator.id, emulatorID); expect(emulator.hasConfig, false); }); testWithoutContext('flags emulators with config', () { const String emulatorID = '1234'; final AndroidEmulator emulator = AndroidEmulator( emulatorID, properties: const <String, String>{'name': 'test'}, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: MockAndroidSdk(), ); expect(emulator.id, emulatorID); expect(emulator.hasConfig, true); }); testWithoutContext('reads expected metadata', () { const String emulatorID = '1234'; const String manufacturer = 'Me'; const String displayName = 'The best one'; final Map<String, String> properties = <String, String>{ 'hw.device.manufacturer': manufacturer, 'avd.ini.displayname': displayName, }; final AndroidEmulator emulator = AndroidEmulator( emulatorID, properties: properties, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: MockAndroidSdk(), ); 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 String emulatorID = '1234'; const String displayName = 'The best one'; final Map<String, String> properties = <String, String>{ 'avd.ini.displayname': displayName, }; final AndroidEmulator emulator = AndroidEmulator( emulatorID, properties: properties, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: MockAndroidSdk(), ); 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 String emulatorID = 'This_is_my_ID'; final Map<String, String> properties = <String, String>{ 'avd.ini.notadisplayname': 'this is not a display name', }; final AndroidEmulator emulator = AndroidEmulator( emulatorID, properties: properties, logger: BufferLogger.test(), processManager: FakeProcessManager.any(), androidSdk: MockAndroidSdk(), ); expect(emulator.name, 'This is my ID'); }); testWithoutContext('parses ini files', () { const String iniFile = ''' hw.device.name=My Test Name #hw.device.name=Bad Name hw.device.manufacturer=Me avd.ini.displayname = dispName '''; final Map<String, String> 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 ', () { MockAndroidSdk mockSdk; setUp(() { mockSdk = MockAndroidSdk(); when(mockSdk.emulatorPath).thenReturn('emulator'); }); testWithoutContext('succeeds', () async { final AndroidEmulator emulator = AndroidEmulator(emulatorID, processManager: FakeProcessManager.list(<FakeCommand>[ const FakeCommand(command: kEmulatorLaunchCommand), ]), androidSdk: mockSdk, logger: BufferLogger.test(), ); final Completer<void> completer = Completer<void>(); FakeAsync().run((FakeAsync time) { unawaited(emulator.launch().whenComplete(completer.complete)); time.elapse(const Duration(seconds: 5)); time.flushMicrotasks(); }); await completer.future; }); testWithoutContext('prints error on failure', () async { final BufferLogger logger = BufferLogger.test(); final AndroidEmulator emulator = AndroidEmulator(emulatorID, processManager: FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: kEmulatorLaunchCommand, exitCode: 1, stderr: errorText, stdout: 'dummy text', duration: Duration(seconds: 1), ), ]), androidSdk: mockSdk, logger: logger, ); final Completer<void> completer = Completer<void>(); FakeAsync().run((FakeAsync time) { unawaited(emulator.launch().whenComplete(completer.complete)); time.elapse(const Duration(seconds: 5)); time.flushMicrotasks(); }); await completer.future; expect(logger.errorText, contains(errorText)); }); testWithoutContext('prints nothing on late failure with empty stderr', () async { final BufferLogger logger = BufferLogger.test(); final AndroidEmulator emulator = AndroidEmulator(emulatorID, processManager: FakeProcessManager.list(<FakeCommand>[ const FakeCommand( command: kEmulatorLaunchCommand, exitCode: 1, stderr: '', stdout: 'dummy text', duration: Duration(seconds: 4), ), ]), androidSdk: mockSdk, logger: logger, ); final Completer<void> completer = Completer<void>(); await FakeAsync().run((FakeAsync time) async { unawaited(emulator.launch().whenComplete(completer.complete)); time.elapse(const Duration(seconds: 5)); time.flushMicrotasks(); }); await completer.future; expect(logger.errorText, isEmpty); }, skip: true); // TODO(jonahwilliams): clean up with https://github.com/flutter/flutter/issues/60675 }); }