Unverified Commit f640ad69 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] ensure emulator command does not crash with missing avdmanager (#57703)

parent ffc56ff7
...@@ -5,36 +5,131 @@ ...@@ -5,36 +5,131 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../android/android_workflow.dart'; import '../android/android_workflow.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../convert.dart'; import '../convert.dart';
import '../device.dart'; import '../device.dart';
import '../emulator.dart'; import '../emulator.dart';
import '../globals.dart' as globals;
import 'android_sdk.dart'; import 'android_sdk.dart';
class AndroidEmulators extends EmulatorDiscovery { class AndroidEmulators extends EmulatorDiscovery {
AndroidEmulators({
@required AndroidSdk androidSdk,
@required AndroidWorkflow androidWorkflow,
@required FileSystem fileSystem,
@required Logger logger,
@required ProcessManager processManager,
}) : _androidSdk = androidSdk,
_androidWorkflow = androidWorkflow,
_fileSystem = fileSystem,
_logger = logger,
_processManager = processManager,
_processUtils = ProcessUtils(logger: logger, processManager: processManager);
final AndroidWorkflow _androidWorkflow;
final AndroidSdk _androidSdk;
final FileSystem _fileSystem;
final Logger _logger;
final ProcessManager _processManager;
final ProcessUtils _processUtils;
@override @override
bool get supportsPlatform => true; bool get supportsPlatform => true;
@override @override
bool get canListAnything => androidWorkflow.canListEmulators; bool get canListAnything => _androidWorkflow.canListEmulators;
@override
bool get canLaunchAnything => _androidWorkflow.canListEmulators
&& _androidSdk.getAvdManagerPath() != null;
@override @override
Future<List<Emulator>> get emulators async => getEmulatorAvds(); Future<List<Emulator>> get emulators => _getEmulatorAvds();
/// Return the list of available emulator AVDs.
Future<List<AndroidEmulator>> _getEmulatorAvds() async {
final String emulatorPath = getEmulatorPath(_androidSdk);
if (emulatorPath == null) {
return <AndroidEmulator>[];
}
final String listAvdsOutput = (await _processUtils.run(
<String>[emulatorPath, '-list-avds'])).stdout.trim();
final List<AndroidEmulator> emulators = <AndroidEmulator>[];
if (listAvdsOutput != null) {
_extractEmulatorAvdInfo(listAvdsOutput, emulators);
}
return emulators;
}
/// Parse the given `emulator -list-avds` output in [text], and fill out the given list
/// of emulators by reading information from the relevant ini files.
void _extractEmulatorAvdInfo(String text, List<AndroidEmulator> emulators) {
for (final String id in text.trim().split('\n').where((String l) => l != '')) {
emulators.add(_loadEmulatorInfo(id));
}
}
AndroidEmulator _loadEmulatorInfo(String id) {
id = id.trim();
final String avdPath = getAvdPath();
final AndroidEmulator androidEmulatorWithoutProperties = AndroidEmulator(
id,
processManager: _processManager,
logger: _logger,
androidSdk: _androidSdk,
);
if (avdPath == null) {
return androidEmulatorWithoutProperties;
}
final File iniFile = _fileSystem.file(_fileSystem.path.join(avdPath, '$id.ini'));
if (!iniFile.existsSync()) {
return androidEmulatorWithoutProperties;
}
final Map<String, String> ini = parseIniLines(iniFile.readAsLinesSync());
if (ini['path'] == null) {
return androidEmulatorWithoutProperties;
}
final File configFile = _fileSystem.file(_fileSystem.path.join(ini['path'], 'config.ini'));
if (!configFile.existsSync()) {
return androidEmulatorWithoutProperties;
}
final Map<String, String> properties = parseIniLines(configFile.readAsLinesSync());
return AndroidEmulator(
id,
properties: properties,
processManager: _processManager,
logger: _logger,
androidSdk: _androidSdk,
);
}
} }
class AndroidEmulator extends Emulator { class AndroidEmulator extends Emulator {
AndroidEmulator(String id, [this._properties]) AndroidEmulator(String id, {
: super(id, _properties != null && _properties.isNotEmpty); Map<String, String> properties,
@required Logger logger,
@required AndroidSdk androidSdk,
@required ProcessManager processManager,
}) : _properties = properties,
_logger = logger,
_androidSdk = androidSdk,
_processUtils = ProcessUtils(logger: logger, processManager: processManager),
super(id, properties != null && properties.isNotEmpty);
final Map<String, String> _properties; final Map<String, String> _properties;
final Logger _logger;
final ProcessUtils _processUtils;
final AndroidSdk _androidSdk;
// Android Studio uses the ID with underscores replaced with spaces // Android Studio uses the ID with underscores replaced with spaces
// for the name if displayname is not set so we do the same. // for the name if displayname is not set so we do the same.
...@@ -54,8 +149,8 @@ class AndroidEmulator extends Emulator { ...@@ -54,8 +149,8 @@ class AndroidEmulator extends Emulator {
@override @override
Future<void> launch() async { Future<void> launch() async {
final Process process = await processUtils.start( final Process process = await _processUtils.start(
<String>[getEmulatorPath(globals.androidSdk), '-avd', id], <String>[getEmulatorPath(_androidSdk), '-avd', id],
); );
// Record output from the emulator process. // Record output from the emulator process.
...@@ -81,7 +176,7 @@ class AndroidEmulator extends Emulator { ...@@ -81,7 +176,7 @@ class AndroidEmulator extends Emulator {
bool earlyFailure = true; bool earlyFailure = true;
unawaited(process.exitCode.then((int status) async { unawaited(process.exitCode.then((int status) async {
if (status == 0) { if (status == 0) {
globals.printTrace('The Android emulator exited successfully'); _logger.printTrace('The Android emulator exited successfully');
return; return;
} }
// Make sure the process' stdout and stderr are drained. // Make sure the process' stdout and stderr are drained.
...@@ -89,18 +184,18 @@ class AndroidEmulator extends Emulator { ...@@ -89,18 +184,18 @@ class AndroidEmulator extends Emulator {
unawaited(stdoutSubscription.cancel()); unawaited(stdoutSubscription.cancel());
unawaited(stderrSubscription.cancel()); unawaited(stderrSubscription.cancel());
if (stdoutList.isNotEmpty) { if (stdoutList.isNotEmpty) {
globals.printTrace('Android emulator stdout:'); _logger.printTrace('Android emulator stdout:');
stdoutList.forEach(globals.printTrace); stdoutList.forEach(_logger.printTrace);
} }
if (!earlyFailure && stderrList.isEmpty) { if (!earlyFailure && stderrList.isEmpty) {
globals.printStatus('The Android emulator exited with code $status'); _logger.printStatus('The Android emulator exited with code $status');
return; return;
} }
final String when = earlyFailure ? 'during startup' : 'after startup'; final String when = earlyFailure ? 'during startup' : 'after startup';
globals.printError('The Android emulator exited with code $status $when'); _logger.printError('The Android emulator exited with code $status $when');
globals.printError('Android emulator stderr:'); _logger.printError('Android emulator stderr:');
stderrList.forEach(globals.printError); stderrList.forEach(_logger.printError);
globals.printError('Address these issues and try again.'); _logger.printError('Address these issues and try again.');
})); }));
// Wait a few seconds for the emulator to start. // Wait a few seconds for the emulator to start.
...@@ -110,52 +205,6 @@ class AndroidEmulator extends Emulator { ...@@ -110,52 +205,6 @@ class AndroidEmulator extends Emulator {
} }
} }
/// Return the list of available emulator AVDs.
List<AndroidEmulator> getEmulatorAvds() {
final String emulatorPath = getEmulatorPath(globals.androidSdk);
if (emulatorPath == null) {
return <AndroidEmulator>[];
}
final String listAvdsOutput = processUtils.runSync(
<String>[emulatorPath, '-list-avds']).stdout.trim();
final List<AndroidEmulator> emulators = <AndroidEmulator>[];
if (listAvdsOutput != null) {
extractEmulatorAvdInfo(listAvdsOutput, emulators);
}
return emulators;
}
/// Parse the given `emulator -list-avds` output in [text], and fill out the given list
/// of emulators by reading information from the relevant ini files.
void extractEmulatorAvdInfo(String text, List<AndroidEmulator> emulators) {
for (final String id in text.trim().split('\n').where((String l) => l != '')) {
emulators.add(_loadEmulatorInfo(id));
}
}
AndroidEmulator _loadEmulatorInfo(String id) {
id = id.trim();
final String avdPath = getAvdPath();
if (avdPath != null) {
final File iniFile = globals.fs.file(globals.fs.path.join(avdPath, '$id.ini'));
if (iniFile.existsSync()) {
final Map<String, String> ini = parseIniLines(iniFile.readAsLinesSync());
if (ini['path'] != null) {
final File configFile =
globals.fs.file(globals.fs.path.join(ini['path'], 'config.ini'));
if (configFile.existsSync()) {
final Map<String, String> properties =
parseIniLines(configFile.readAsLinesSync());
return AndroidEmulator(id, properties);
}
}
}
}
return AndroidEmulator(id);
}
@visibleForTesting @visibleForTesting
Map<String, String> parseIniLines(List<String> contents) { Map<String, String> parseIniLines(List<String> contents) {
......
...@@ -44,17 +44,23 @@ final RegExp licenseNotAccepted = RegExp(r'licenses? not accepted', caseSensitiv ...@@ -44,17 +44,23 @@ final RegExp licenseNotAccepted = RegExp(r'licenses? not accepted', caseSensitiv
final RegExp licenseAccepted = RegExp(r'All SDK package licenses accepted.'); final RegExp licenseAccepted = RegExp(r'All SDK package licenses accepted.');
class AndroidWorkflow implements Workflow { class AndroidWorkflow implements Workflow {
AndroidWorkflow({
@required AndroidSdk androidSdk,
}) : _androidSdk = androidSdk;
final AndroidSdk _androidSdk;
@override @override
bool get appliesToHostPlatform => true; bool get appliesToHostPlatform => true;
@override @override
bool get canListDevices => getAdbPath(globals.androidSdk) != null; bool get canListDevices => getAdbPath(_androidSdk) != null;
@override @override
bool get canLaunchDevices => globals.androidSdk != null && globals.androidSdk.validateSdkWellFormed().isEmpty; bool get canLaunchDevices => _androidSdk != null && _androidSdk.validateSdkWellFormed().isEmpty;
@override @override
bool get canListEmulators => getEmulatorPath(globals.androidSdk) != null; bool get canListEmulators => getEmulatorPath(_androidSdk) != null;
} }
class AndroidValidator extends DoctorValidator { class AndroidValidator extends DoctorValidator {
......
...@@ -7,6 +7,7 @@ import 'dart:async'; ...@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../android/android_workflow.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
...@@ -1039,7 +1040,13 @@ class EmulatorDomain extends Domain { ...@@ -1039,7 +1040,13 @@ class EmulatorDomain extends Domain {
registerHandler('create', create); registerHandler('create', create);
} }
EmulatorManager emulators = EmulatorManager(); EmulatorManager emulators = EmulatorManager(
fileSystem: globals.fs,
logger: globals.logger,
androidSdk: globals.androidSdk,
processManager: globals.processManager,
androidWorkflow: androidWorkflow,
);
Future<List<Map<String, dynamic>>> getEmulators([ Map<String, dynamic> args ]) async { Future<List<Map<String, dynamic>>> getEmulators([ Map<String, dynamic> args ]) async {
final List<Emulator> list = await emulators.getAllAvailableEmulators(); final List<Emulator> list = await emulators.getAllAvailableEmulators();
......
...@@ -104,7 +104,7 @@ class EmulatorsCommand extends FlutterCommand { ...@@ -104,7 +104,7 @@ class EmulatorsCommand extends FlutterCommand {
void _printEmulatorList(List<Emulator> emulators, String message) { void _printEmulatorList(List<Emulator> emulators, String message) {
globals.printStatus('$message\n'); globals.printStatus('$message\n');
Emulator.printEmulators(emulators); Emulator.printEmulators(emulators, globals.logger);
_printAdditionalInfo(showCreateInstruction: true, showRunInstruction: true); _printAdditionalInfo(showCreateInstruction: true, showRunInstruction: true);
} }
......
...@@ -79,7 +79,9 @@ Future<T> runInContext<T>( ...@@ -79,7 +79,9 @@ Future<T> runInContext<T>(
processManager: globals.processManager, processManager: globals.processManager,
userMessages: globals.userMessages, userMessages: globals.userMessages,
), ),
AndroidWorkflow: () => AndroidWorkflow(), AndroidWorkflow: () => AndroidWorkflow(
androidSdk: globals.androidSdk,
),
ApplicationPackageFactory: () => ApplicationPackageFactory(), ApplicationPackageFactory: () => ApplicationPackageFactory(),
Artifacts: () => CachedArtifacts( Artifacts: () => CachedArtifacts(
fileSystem: globals.fs, fileSystem: globals.fs,
...@@ -125,7 +127,13 @@ Future<T> runInContext<T>( ...@@ -125,7 +127,13 @@ Future<T> runInContext<T>(
DeviceManager: () => DeviceManager(), DeviceManager: () => DeviceManager(),
Doctor: () => Doctor(logger: globals.logger), Doctor: () => Doctor(logger: globals.logger),
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
EmulatorManager: () => EmulatorManager(), EmulatorManager: () => EmulatorManager(
androidSdk: globals.androidSdk,
processManager: globals.processManager,
logger: globals.logger,
fileSystem: globals.fs,
androidWorkflow: androidWorkflow,
),
FeatureFlags: () => const FeatureFlags(), FeatureFlags: () => const FeatureFlags(),
FlutterVersion: () => FlutterVersion(const SystemClock()), FlutterVersion: () => FlutterVersion(const SystemClock()),
FuchsiaArtifacts: () => FuchsiaArtifacts.find(), FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
......
...@@ -6,28 +6,49 @@ import 'dart:async'; ...@@ -6,28 +6,49 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'android/android_emulator.dart'; import 'android/android_emulator.dart';
import 'android/android_sdk.dart'; import 'android/android_sdk.dart';
import 'android/android_workflow.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/process.dart'; import 'base/process.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart' as globals;
import 'ios/ios_emulators.dart'; import 'ios/ios_emulators.dart';
EmulatorManager get emulatorManager => context.get<EmulatorManager>(); EmulatorManager get emulatorManager => context.get<EmulatorManager>();
/// A class to get all available emulators. /// A class to get all available emulators.
class EmulatorManager { class EmulatorManager {
/// Constructing EmulatorManager is cheap; they only do expensive work if some EmulatorManager({
/// of their methods are called. @required AndroidSdk androidSdk,
EmulatorManager() { @required Logger logger,
// Register the known discoverers. @required ProcessManager processManager,
_emulatorDiscoverers.add(AndroidEmulators()); @required AndroidWorkflow androidWorkflow,
_emulatorDiscoverers.add(IOSEmulators()); @required FileSystem fileSystem,
}) : _androidSdk = androidSdk,
_processUtils = ProcessUtils(logger: logger, processManager: processManager),
_androidEmulators = AndroidEmulators(
androidSdk: androidSdk,
logger: logger,
processManager: processManager,
fileSystem: fileSystem,
androidWorkflow: androidWorkflow
) {
_emulatorDiscoverers.add(_androidEmulators);
} }
final List<EmulatorDiscovery> _emulatorDiscoverers = <EmulatorDiscovery>[]; final AndroidSdk _androidSdk;
final AndroidEmulators _androidEmulators;
final ProcessUtils _processUtils;
// Constructing EmulatorManager is cheap; they only do expensive work if some
// of their methods are called.
final List<EmulatorDiscovery> _emulatorDiscoverers = <EmulatorDiscovery>[
IOSEmulators(),
];
Future<List<Emulator>> getEmulatorsMatching(String searchText) async { Future<List<Emulator>> getEmulatorsMatching(String searchText) async {
final List<Emulator> emulators = await getAllAvailableEmulators(); final List<Emulator> emulators = await getAllAvailableEmulators();
...@@ -64,7 +85,7 @@ class EmulatorManager { ...@@ -64,7 +85,7 @@ class EmulatorManager {
/// Return the list of all available emulators. /// Return the list of all available emulators.
Future<CreateEmulatorResult> createEmulator({ String name }) async { Future<CreateEmulatorResult> createEmulator({ String name }) async {
if (name == null || name == '') { if (name == null || name.isEmpty) {
const String autoName = 'flutter_emulator'; const String autoName = 'flutter_emulator';
// Don't use getEmulatorsMatching here, as it will only return one // Don't use getEmulatorsMatching here, as it will only return one
// if there's an exact match and we need all those with this prefix // if there's an exact match and we need all those with this prefix
...@@ -80,6 +101,11 @@ class EmulatorManager { ...@@ -80,6 +101,11 @@ class EmulatorManager {
name = '${autoName}_${++suffix}'; name = '${autoName}_${++suffix}';
} }
} }
if (!_androidEmulators.canLaunchAnything) {
return CreateEmulatorResult(name,
success: false, error: 'avdmanager is missing from the Android SDK'
);
}
final String device = await _getPreferredAvailableDevice(); final String device = await _getPreferredAvailableDevice();
if (device == null) { if (device == null) {
...@@ -113,17 +139,15 @@ class EmulatorManager { ...@@ -113,17 +139,15 @@ class EmulatorManager {
.join('\n') .join('\n')
.trim(); .trim();
} }
final RunResult runResult = await _processUtils.run(<String>[
final List<String> args = <String>[ getAvdManagerPath(_androidSdk),
getAvdManagerPath(globals.androidSdk), 'create',
'create', 'avd',
'avd', '-n', name,
'-n', name, '-k', sdkId,
'-k', sdkId, '-d', device,
'-d', device, ], environment: _androidSdk?.sdkManagerEnv,
]; );
final RunResult runResult = processUtils.runSync(args,
environment: globals.androidSdk?.sdkManagerEnv);
return CreateEmulatorResult( return CreateEmulatorResult(
name, name,
success: runResult.exitCode == 0, success: runResult.exitCode == 0,
...@@ -136,15 +160,16 @@ class EmulatorManager { ...@@ -136,15 +160,16 @@ class EmulatorManager {
'pixel', 'pixel',
'pixel_xl', 'pixel_xl',
]; ];
Future<String> _getPreferredAvailableDevice() async { Future<String> _getPreferredAvailableDevice() async {
final List<String> args = <String>[ final List<String> args = <String>[
getAvdManagerPath(globals.androidSdk), getAvdManagerPath(_androidSdk),
'list', 'list',
'device', 'device',
'-c', '-c',
]; ];
final RunResult runResult = processUtils.runSync(args, final RunResult runResult = await _processUtils.run(args,
environment: globals.androidSdk?.sdkManagerEnv); environment: _androidSdk?.sdkManagerEnv);
if (runResult.exitCode != 0) { if (runResult.exitCode != 0) {
return null; return null;
} }
...@@ -160,29 +185,30 @@ class EmulatorManager { ...@@ -160,29 +185,30 @@ class EmulatorManager {
); );
} }
RegExp androidApiVersion = RegExp(r';android-(\d+);'); static final RegExp _androidApiVersion = RegExp(r';android-(\d+);');
Future<String> _getPreferredSdkId() async { Future<String> _getPreferredSdkId() async {
// It seems that to get the available list of images, we need to send a // It seems that to get the available list of images, we need to send a
// request to create without the image and it'll provide us a list :-( // request to create without the image and it'll provide us a list :-(
final List<String> args = <String>[ final List<String> args = <String>[
getAvdManagerPath(globals.androidSdk), getAvdManagerPath(_androidSdk),
'create', 'create',
'avd', 'avd',
'-n', 'temp', '-n', 'temp',
]; ];
final RunResult runResult = processUtils.runSync(args, final RunResult runResult = await _processUtils.run(args,
environment: globals.androidSdk?.sdkManagerEnv); environment: _androidSdk?.sdkManagerEnv);
// Get the list of IDs that match our criteria // Get the list of IDs that match our criteria
final List<String> availableIDs = runResult.stderr final List<String> availableIDs = runResult.stderr
.split('\n') .split('\n')
.where((String l) => androidApiVersion.hasMatch(l)) .where((String l) => _androidApiVersion.hasMatch(l))
.where((String l) => l.contains('system-images')) .where((String l) => l.contains('system-images'))
.where((String l) => l.contains('google_apis_playstore')) .where((String l) => l.contains('google_apis_playstore'))
.toList(); .toList();
final List<int> availableApiVersions = availableIDs final List<int> availableApiVersions = availableIDs
.map<String>((String id) => androidApiVersion.firstMatch(id).group(1)) .map<String>((String id) => _androidApiVersion.firstMatch(id).group(1))
.map<int>((String apiVersion) => int.parse(apiVersion)) .map<int>((String apiVersion) => int.parse(apiVersion))
.toList(); .toList();
...@@ -209,10 +235,12 @@ class EmulatorManager { ...@@ -209,10 +235,12 @@ class EmulatorManager {
abstract class EmulatorDiscovery { abstract class EmulatorDiscovery {
bool get supportsPlatform; bool get supportsPlatform;
/// Whether this emulator discovery is capable of listing any emulators given the /// Whether this emulator discovery is capable of listing any emulators.
/// current environment configuration.
bool get canListAnything; bool get canListAnything;
/// Whether this emulator discovery is capabale of launching new emulators.
bool get canLaunchAnything;
Future<List<Emulator>> get emulators; Future<List<Emulator>> get emulators;
} }
...@@ -280,8 +308,8 @@ abstract class Emulator { ...@@ -280,8 +308,8 @@ abstract class Emulator {
.toList(); .toList();
} }
static void printEmulators(List<Emulator> emulators) { static void printEmulators(List<Emulator> emulators, Logger logger) {
descriptions(emulators).forEach(globals.printStatus); descriptions(emulators).forEach(logger.printStatus);
} }
} }
......
...@@ -19,6 +19,9 @@ class IOSEmulators extends EmulatorDiscovery { ...@@ -19,6 +19,9 @@ class IOSEmulators extends EmulatorDiscovery {
@override @override
Future<List<Emulator>> get emulators async => getEmulators(); Future<List<Emulator>> get emulators async => getEmulators();
@override
bool get canLaunchAnything => canListAnything;
} }
class IOSEmulator extends Emulator { class IOSEmulator extends Emulator {
......
...@@ -20,7 +20,9 @@ void main() { ...@@ -20,7 +20,9 @@ void main() {
final AndroidDevices androidDevices = AndroidDevices( final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(null), androidSdk: MockAndroidSdk(null),
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow(), androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(null),
),
processManager: FakeProcessManager.list(<FakeCommand>[]), processManager: FakeProcessManager.list(<FakeCommand>[]),
); );
...@@ -39,7 +41,9 @@ void main() { ...@@ -39,7 +41,9 @@ void main() {
final AndroidDevices androidDevices = AndroidDevices( final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(), androidSdk: MockAndroidSdk(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow(), androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(),
),
processManager: processManager, processManager: processManager,
); );
...@@ -57,7 +61,9 @@ void main() { ...@@ -57,7 +61,9 @@ void main() {
final AndroidDevices androidDevices = AndroidDevices( final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(), androidSdk: MockAndroidSdk(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow(), androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(),
),
processManager: processManager, processManager: processManager,
); );
......
...@@ -4,12 +4,11 @@ ...@@ -4,12 +4,11 @@
import 'dart:async'; import 'dart:async';
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_sdk.dart'
show getEmulatorPath, AndroidSdk; show getEmulatorPath;
import 'package:flutter_tools/src/android/android_emulator.dart'; import 'package:flutter_tools/src/android/android_emulator.dart';
import 'package:flutter_tools/src/base/common.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:flutter_tools/src/device.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:quiver/testing/async.dart'; import 'package:quiver/testing/async.dart';
...@@ -19,24 +18,42 @@ import '../../src/context.dart'; ...@@ -19,24 +18,42 @@ import '../../src/context.dart';
import '../../src/fake_process_manager.dart'; import '../../src/fake_process_manager.dart';
import '../../src/mocks.dart' show MockAndroidSdk; 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() { void main() {
group('android_emulator', () { group('android_emulator', () {
testUsingContext('flags emulators without config', () { testWithoutContext('flags emulators without config', () {
const String emulatorID = '1234'; const String emulatorID = '1234';
final AndroidEmulator emulator = AndroidEmulator(emulatorID);
final AndroidEmulator emulator = AndroidEmulator(
emulatorID,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
androidSdk: MockAndroidSdk(),
);
expect(emulator.id, emulatorID); expect(emulator.id, emulatorID);
expect(emulator.hasConfig, false); expect(emulator.hasConfig, false);
}); });
testUsingContext('flags emulators with config', () {
testWithoutContext('flags emulators with config', () {
const String emulatorID = '1234'; const String emulatorID = '1234';
final AndroidEmulator emulator = AndroidEmulator( final AndroidEmulator emulator = AndroidEmulator(
emulatorID, emulatorID,
const <String, String>{'name': 'test'}, properties: const <String, String>{'name': 'test'},
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
androidSdk: MockAndroidSdk(),
); );
expect(emulator.id, emulatorID); expect(emulator.id, emulatorID);
expect(emulator.hasConfig, true); expect(emulator.hasConfig, true);
}); });
testUsingContext('reads expected metadata', () {
testWithoutContext('reads expected metadata', () {
const String emulatorID = '1234'; const String emulatorID = '1234';
const String manufacturer = 'Me'; const String manufacturer = 'Me';
const String displayName = 'The best one'; const String displayName = 'The best one';
...@@ -44,33 +61,57 @@ void main() { ...@@ -44,33 +61,57 @@ void main() {
'hw.device.manufacturer': manufacturer, 'hw.device.manufacturer': manufacturer,
'avd.ini.displayname': displayName, 'avd.ini.displayname': displayName,
}; };
final AndroidEmulator emulator = AndroidEmulator(emulatorID, properties); final AndroidEmulator emulator = AndroidEmulator(
emulatorID,
properties: properties,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
androidSdk: MockAndroidSdk(),
);
expect(emulator.id, emulatorID); expect(emulator.id, emulatorID);
expect(emulator.name, displayName); expect(emulator.name, displayName);
expect(emulator.manufacturer, manufacturer); expect(emulator.manufacturer, manufacturer);
expect(emulator.category, Category.mobile); expect(emulator.category, Category.mobile);
expect(emulator.platformType, PlatformType.android); expect(emulator.platformType, PlatformType.android);
}); });
testUsingContext('prefers displayname for name', () {
testWithoutContext('prefers displayname for name', () {
const String emulatorID = '1234'; const String emulatorID = '1234';
const String displayName = 'The best one'; const String displayName = 'The best one';
final Map<String, String> properties = <String, String>{ final Map<String, String> properties = <String, String>{
'avd.ini.displayname': displayName, 'avd.ini.displayname': displayName,
}; };
final AndroidEmulator emulator = AndroidEmulator(emulatorID, properties); final AndroidEmulator emulator = AndroidEmulator(
emulatorID,
properties: properties,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
androidSdk: MockAndroidSdk(),
);
expect(emulator.name, displayName); expect(emulator.name, displayName);
}); });
testUsingContext('uses cleaned up ID if no displayname is set', () {
testWithoutContext('uses cleaned up ID if no displayname is set', () {
// Android Studio uses the ID with underscores replaced with spaces // Android Studio uses the ID with underscores replaced with spaces
// for the name if displayname is not set so we do the same. // for the name if displayname is not set so we do the same.
const String emulatorID = 'This_is_my_ID'; const String emulatorID = 'This_is_my_ID';
final Map<String, String> properties = <String, String>{ final Map<String, String> properties = <String, String>{
'avd.ini.notadisplayname': 'this is not a display name', 'avd.ini.notadisplayname': 'this is not a display name',
}; };
final AndroidEmulator emulator = AndroidEmulator(emulatorID, properties); final AndroidEmulator emulator = AndroidEmulator(
emulatorID,
properties: properties,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
androidSdk: MockAndroidSdk(),
);
expect(emulator.name, 'This is my ID'); expect(emulator.name, 'This is my ID');
}); });
testUsingContext('parses ini files', () {
testWithoutContext('parses ini files', () {
const String iniFile = ''' const String iniFile = '''
hw.device.name=My Test Name hw.device.name=My Test Name
#hw.device.name=Bad Name #hw.device.name=Bad Name
...@@ -79,6 +120,7 @@ void main() { ...@@ -79,6 +120,7 @@ void main() {
avd.ini.displayname = dispName avd.ini.displayname = dispName
'''; ''';
final Map<String, String> results = parseIniLines(iniFile.split('\n')); final Map<String, String> results = parseIniLines(iniFile.split('\n'));
expect(results['hw.device.name'], 'My Test Name'); expect(results['hw.device.name'], 'My Test Name');
expect(results['hw.device.manufacturer'], 'Me'); expect(results['hw.device.manufacturer'], 'Me');
expect(results['avd.ini.displayname'], 'dispName'); expect(results['avd.ini.displayname'], 'dispName');
...@@ -86,51 +128,24 @@ void main() { ...@@ -86,51 +128,24 @@ void main() {
}); });
group('Android emulator launch ', () { group('Android emulator launch ', () {
const String emulatorID = 'i1234';
const String errorText = '[Android emulator test error]';
MockAndroidSdk mockSdk; MockAndroidSdk mockSdk;
FakeProcessManager successProcessManager;
FakeProcessManager errorProcessManager;
FakeProcessManager lateFailureProcessManager;
MemoryFileSystem fs;
setUp(() { setUp(() {
fs = MemoryFileSystem();
mockSdk = MockAndroidSdk(); mockSdk = MockAndroidSdk();
when(mockSdk.emulatorPath).thenReturn('emulator'); when(mockSdk.emulatorPath).thenReturn('emulator');
const List<String> command = <String>[
'emulator', '-avd', emulatorID,
];
successProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: command),
]);
errorProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: command,
exitCode: 1,
stderr: errorText,
stdout: 'dummy text',
duration: Duration(seconds: 1),
),
]);
lateFailureProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: command,
exitCode: 1,
stderr: '',
stdout: 'dummy text',
duration: Duration(seconds: 4),
),
]);
}); });
testUsingContext('succeeds', () async { testWithoutContext('succeeds', () async {
final AndroidEmulator emulator = AndroidEmulator(emulatorID); final AndroidEmulator emulator = AndroidEmulator(emulatorID,
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: kEmulatorLaunchCommand),
]),
androidSdk: mockSdk,
logger: BufferLogger.test(),
);
expect(getEmulatorPath(mockSdk), mockSdk.emulatorPath); expect(getEmulatorPath(mockSdk), mockSdk.emulatorPath);
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
unawaited(emulator.launch().whenComplete(completer.complete)); unawaited(emulator.launch().whenComplete(completer.complete));
...@@ -138,15 +153,24 @@ void main() { ...@@ -138,15 +153,24 @@ void main() {
time.flushMicrotasks(); time.flushMicrotasks();
}); });
await completer.future; await completer.future;
}, overrides: <Type, Generator>{
ProcessManager: () => successProcessManager,
AndroidSdk: () => mockSdk,
FileSystem: () => fs,
}); });
testUsingContext('prints error on failure', () async { testWithoutContext('prints error on failure', () async {
final AndroidEmulator emulator = AndroidEmulator(emulatorID); 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>(); final Completer<void> completer = Completer<void>();
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
unawaited(emulator.launch().whenComplete(completer.complete)); unawaited(emulator.launch().whenComplete(completer.complete));
...@@ -155,15 +179,24 @@ void main() { ...@@ -155,15 +179,24 @@ void main() {
}); });
await completer.future; await completer.future;
expect(testLogger.errorText, contains(errorText)); expect(logger.errorText, contains(errorText));
}, overrides: <Type, Generator>{
ProcessManager: () => errorProcessManager,
AndroidSdk: () => mockSdk,
FileSystem: () => fs,
}); });
testUsingContext('prints nothing on late failure with empty stderr', () async { testWithoutContext('prints nothing on late failure with empty stderr', () async {
final AndroidEmulator emulator = AndroidEmulator(emulatorID); 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>(); final Completer<void> completer = Completer<void>();
FakeAsync().run((FakeAsync time) async { FakeAsync().run((FakeAsync time) async {
unawaited(emulator.launch().whenComplete(completer.complete)); unawaited(emulator.launch().whenComplete(completer.complete));
...@@ -171,11 +204,8 @@ void main() { ...@@ -171,11 +204,8 @@ void main() {
time.flushMicrotasks(); time.flushMicrotasks();
}); });
await completer.future; await completer.future;
expect(testLogger.errorText, isEmpty);
}, overrides: <Type, Generator>{ expect(logger.errorText, isEmpty);
ProcessManager: () => lateFailureProcessManager,
AndroidSdk: () => mockSdk,
FileSystem: () => fs,
}); });
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment