mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_tools] ensure emulator command does not crash with missing avdmanager (#57703)
This commit is contained in:
parent
ffc56ff735
commit
f640ad6914
@ -5,36 +5,131 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../android/android_sdk.dart';
|
||||
import '../android/android_workflow.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../convert.dart';
|
||||
import '../device.dart';
|
||||
import '../emulator.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import 'android_sdk.dart';
|
||||
|
||||
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
|
||||
bool get supportsPlatform => true;
|
||||
|
||||
@override
|
||||
bool get canListAnything => androidWorkflow.canListEmulators;
|
||||
bool get canListAnything => _androidWorkflow.canListEmulators;
|
||||
|
||||
@override
|
||||
Future<List<Emulator>> get emulators async => getEmulatorAvds();
|
||||
bool get canLaunchAnything => _androidWorkflow.canListEmulators
|
||||
&& _androidSdk.getAvdManagerPath() != null;
|
||||
|
||||
@override
|
||||
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 {
|
||||
AndroidEmulator(String id, [this._properties])
|
||||
: super(id, _properties != null && _properties.isNotEmpty);
|
||||
AndroidEmulator(String id, {
|
||||
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 Logger _logger;
|
||||
final ProcessUtils _processUtils;
|
||||
final AndroidSdk _androidSdk;
|
||||
|
||||
// Android Studio uses the ID with underscores replaced with spaces
|
||||
// for the name if displayname is not set so we do the same.
|
||||
@ -54,8 +149,8 @@ class AndroidEmulator extends Emulator {
|
||||
|
||||
@override
|
||||
Future<void> launch() async {
|
||||
final Process process = await processUtils.start(
|
||||
<String>[getEmulatorPath(globals.androidSdk), '-avd', id],
|
||||
final Process process = await _processUtils.start(
|
||||
<String>[getEmulatorPath(_androidSdk), '-avd', id],
|
||||
);
|
||||
|
||||
// Record output from the emulator process.
|
||||
@ -81,7 +176,7 @@ class AndroidEmulator extends Emulator {
|
||||
bool earlyFailure = true;
|
||||
unawaited(process.exitCode.then((int status) async {
|
||||
if (status == 0) {
|
||||
globals.printTrace('The Android emulator exited successfully');
|
||||
_logger.printTrace('The Android emulator exited successfully');
|
||||
return;
|
||||
}
|
||||
// Make sure the process' stdout and stderr are drained.
|
||||
@ -89,18 +184,18 @@ class AndroidEmulator extends Emulator {
|
||||
unawaited(stdoutSubscription.cancel());
|
||||
unawaited(stderrSubscription.cancel());
|
||||
if (stdoutList.isNotEmpty) {
|
||||
globals.printTrace('Android emulator stdout:');
|
||||
stdoutList.forEach(globals.printTrace);
|
||||
_logger.printTrace('Android emulator stdout:');
|
||||
stdoutList.forEach(_logger.printTrace);
|
||||
}
|
||||
if (!earlyFailure && stderrList.isEmpty) {
|
||||
globals.printStatus('The Android emulator exited with code $status');
|
||||
_logger.printStatus('The Android emulator exited with code $status');
|
||||
return;
|
||||
}
|
||||
final String when = earlyFailure ? 'during startup' : 'after startup';
|
||||
globals.printError('The Android emulator exited with code $status $when');
|
||||
globals.printError('Android emulator stderr:');
|
||||
stderrList.forEach(globals.printError);
|
||||
globals.printError('Address these issues and try again.');
|
||||
_logger.printError('The Android emulator exited with code $status $when');
|
||||
_logger.printError('Android emulator stderr:');
|
||||
stderrList.forEach(_logger.printError);
|
||||
_logger.printError('Address these issues and try again.');
|
||||
}));
|
||||
|
||||
// Wait a few seconds for the emulator to start.
|
||||
@ -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
|
||||
Map<String, String> parseIniLines(List<String> contents) {
|
||||
|
@ -44,17 +44,23 @@ final RegExp licenseNotAccepted = RegExp(r'licenses? not accepted', caseSensitiv
|
||||
final RegExp licenseAccepted = RegExp(r'All SDK package licenses accepted.');
|
||||
|
||||
class AndroidWorkflow implements Workflow {
|
||||
AndroidWorkflow({
|
||||
@required AndroidSdk androidSdk,
|
||||
}) : _androidSdk = androidSdk;
|
||||
|
||||
final AndroidSdk _androidSdk;
|
||||
|
||||
@override
|
||||
bool get appliesToHostPlatform => true;
|
||||
|
||||
@override
|
||||
bool get canListDevices => getAdbPath(globals.androidSdk) != null;
|
||||
bool get canListDevices => getAdbPath(_androidSdk) != null;
|
||||
|
||||
@override
|
||||
bool get canLaunchDevices => globals.androidSdk != null && globals.androidSdk.validateSdkWellFormed().isEmpty;
|
||||
bool get canLaunchDevices => _androidSdk != null && _androidSdk.validateSdkWellFormed().isEmpty;
|
||||
|
||||
@override
|
||||
bool get canListEmulators => getEmulatorPath(globals.androidSdk) != null;
|
||||
bool get canListEmulators => getEmulatorPath(_androidSdk) != null;
|
||||
}
|
||||
|
||||
class AndroidValidator extends DoctorValidator {
|
||||
|
@ -7,6 +7,7 @@ import 'dart:async';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../android/android_workflow.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
@ -1039,7 +1040,13 @@ class EmulatorDomain extends Domain {
|
||||
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 {
|
||||
final List<Emulator> list = await emulators.getAllAvailableEmulators();
|
||||
|
@ -104,7 +104,7 @@ class EmulatorsCommand extends FlutterCommand {
|
||||
|
||||
void _printEmulatorList(List<Emulator> emulators, String message) {
|
||||
globals.printStatus('$message\n');
|
||||
Emulator.printEmulators(emulators);
|
||||
Emulator.printEmulators(emulators, globals.logger);
|
||||
_printAdditionalInfo(showCreateInstruction: true, showRunInstruction: true);
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,9 @@ Future<T> runInContext<T>(
|
||||
processManager: globals.processManager,
|
||||
userMessages: globals.userMessages,
|
||||
),
|
||||
AndroidWorkflow: () => AndroidWorkflow(),
|
||||
AndroidWorkflow: () => AndroidWorkflow(
|
||||
androidSdk: globals.androidSdk,
|
||||
),
|
||||
ApplicationPackageFactory: () => ApplicationPackageFactory(),
|
||||
Artifacts: () => CachedArtifacts(
|
||||
fileSystem: globals.fs,
|
||||
@ -125,7 +127,13 @@ Future<T> runInContext<T>(
|
||||
DeviceManager: () => DeviceManager(),
|
||||
Doctor: () => Doctor(logger: globals.logger),
|
||||
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
|
||||
EmulatorManager: () => EmulatorManager(),
|
||||
EmulatorManager: () => EmulatorManager(
|
||||
androidSdk: globals.androidSdk,
|
||||
processManager: globals.processManager,
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
androidWorkflow: androidWorkflow,
|
||||
),
|
||||
FeatureFlags: () => const FeatureFlags(),
|
||||
FlutterVersion: () => FlutterVersion(const SystemClock()),
|
||||
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
|
||||
|
@ -6,28 +6,49 @@ import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import 'android/android_emulator.dart';
|
||||
import 'android/android_sdk.dart';
|
||||
import 'android/android_workflow.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'base/process.dart';
|
||||
import 'device.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'ios/ios_emulators.dart';
|
||||
|
||||
EmulatorManager get emulatorManager => context.get<EmulatorManager>();
|
||||
|
||||
/// A class to get all available emulators.
|
||||
class EmulatorManager {
|
||||
/// Constructing EmulatorManager is cheap; they only do expensive work if some
|
||||
/// of their methods are called.
|
||||
EmulatorManager() {
|
||||
// Register the known discoverers.
|
||||
_emulatorDiscoverers.add(AndroidEmulators());
|
||||
_emulatorDiscoverers.add(IOSEmulators());
|
||||
EmulatorManager({
|
||||
@required AndroidSdk androidSdk,
|
||||
@required Logger logger,
|
||||
@required ProcessManager processManager,
|
||||
@required AndroidWorkflow androidWorkflow,
|
||||
@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 {
|
||||
final List<Emulator> emulators = await getAllAvailableEmulators();
|
||||
@ -64,7 +85,7 @@ class EmulatorManager {
|
||||
|
||||
/// Return the list of all available emulators.
|
||||
Future<CreateEmulatorResult> createEmulator({ String name }) async {
|
||||
if (name == null || name == '') {
|
||||
if (name == null || name.isEmpty) {
|
||||
const String autoName = 'flutter_emulator';
|
||||
// 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
|
||||
@ -80,6 +101,11 @@ class EmulatorManager {
|
||||
name = '${autoName}_${++suffix}';
|
||||
}
|
||||
}
|
||||
if (!_androidEmulators.canLaunchAnything) {
|
||||
return CreateEmulatorResult(name,
|
||||
success: false, error: 'avdmanager is missing from the Android SDK'
|
||||
);
|
||||
}
|
||||
|
||||
final String device = await _getPreferredAvailableDevice();
|
||||
if (device == null) {
|
||||
@ -113,17 +139,15 @@ class EmulatorManager {
|
||||
.join('\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
final List<String> args = <String>[
|
||||
getAvdManagerPath(globals.androidSdk),
|
||||
'create',
|
||||
'avd',
|
||||
'-n', name,
|
||||
'-k', sdkId,
|
||||
'-d', device,
|
||||
];
|
||||
final RunResult runResult = processUtils.runSync(args,
|
||||
environment: globals.androidSdk?.sdkManagerEnv);
|
||||
final RunResult runResult = await _processUtils.run(<String>[
|
||||
getAvdManagerPath(_androidSdk),
|
||||
'create',
|
||||
'avd',
|
||||
'-n', name,
|
||||
'-k', sdkId,
|
||||
'-d', device,
|
||||
], environment: _androidSdk?.sdkManagerEnv,
|
||||
);
|
||||
return CreateEmulatorResult(
|
||||
name,
|
||||
success: runResult.exitCode == 0,
|
||||
@ -136,15 +160,16 @@ class EmulatorManager {
|
||||
'pixel',
|
||||
'pixel_xl',
|
||||
];
|
||||
|
||||
Future<String> _getPreferredAvailableDevice() async {
|
||||
final List<String> args = <String>[
|
||||
getAvdManagerPath(globals.androidSdk),
|
||||
getAvdManagerPath(_androidSdk),
|
||||
'list',
|
||||
'device',
|
||||
'-c',
|
||||
];
|
||||
final RunResult runResult = processUtils.runSync(args,
|
||||
environment: globals.androidSdk?.sdkManagerEnv);
|
||||
final RunResult runResult = await _processUtils.run(args,
|
||||
environment: _androidSdk?.sdkManagerEnv);
|
||||
if (runResult.exitCode != 0) {
|
||||
return null;
|
||||
}
|
||||
@ -160,29 +185,30 @@ class EmulatorManager {
|
||||
);
|
||||
}
|
||||
|
||||
RegExp androidApiVersion = RegExp(r';android-(\d+);');
|
||||
static final RegExp _androidApiVersion = RegExp(r';android-(\d+);');
|
||||
|
||||
Future<String> _getPreferredSdkId() async {
|
||||
// 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 :-(
|
||||
final List<String> args = <String>[
|
||||
getAvdManagerPath(globals.androidSdk),
|
||||
getAvdManagerPath(_androidSdk),
|
||||
'create',
|
||||
'avd',
|
||||
'-n', 'temp',
|
||||
];
|
||||
final RunResult runResult = processUtils.runSync(args,
|
||||
environment: globals.androidSdk?.sdkManagerEnv);
|
||||
final RunResult runResult = await _processUtils.run(args,
|
||||
environment: _androidSdk?.sdkManagerEnv);
|
||||
|
||||
// Get the list of IDs that match our criteria
|
||||
final List<String> availableIDs = runResult.stderr
|
||||
.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('google_apis_playstore'))
|
||||
.toList();
|
||||
|
||||
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))
|
||||
.toList();
|
||||
|
||||
@ -209,10 +235,12 @@ class EmulatorManager {
|
||||
abstract class EmulatorDiscovery {
|
||||
bool get supportsPlatform;
|
||||
|
||||
/// Whether this emulator discovery is capable of listing any emulators given the
|
||||
/// current environment configuration.
|
||||
/// Whether this emulator discovery is capable of listing any emulators.
|
||||
bool get canListAnything;
|
||||
|
||||
/// Whether this emulator discovery is capabale of launching new emulators.
|
||||
bool get canLaunchAnything;
|
||||
|
||||
Future<List<Emulator>> get emulators;
|
||||
}
|
||||
|
||||
@ -280,8 +308,8 @@ abstract class Emulator {
|
||||
.toList();
|
||||
}
|
||||
|
||||
static void printEmulators(List<Emulator> emulators) {
|
||||
descriptions(emulators).forEach(globals.printStatus);
|
||||
static void printEmulators(List<Emulator> emulators, Logger logger) {
|
||||
descriptions(emulators).forEach(logger.printStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,9 @@ class IOSEmulators extends EmulatorDiscovery {
|
||||
|
||||
@override
|
||||
Future<List<Emulator>> get emulators async => getEmulators();
|
||||
|
||||
@override
|
||||
bool get canLaunchAnything => canListAnything;
|
||||
}
|
||||
|
||||
class IOSEmulator extends Emulator {
|
||||
|
@ -20,7 +20,9 @@ void main() {
|
||||
final AndroidDevices androidDevices = AndroidDevices(
|
||||
androidSdk: MockAndroidSdk(null),
|
||||
logger: BufferLogger.test(),
|
||||
androidWorkflow: AndroidWorkflow(),
|
||||
androidWorkflow: AndroidWorkflow(
|
||||
androidSdk: MockAndroidSdk(null),
|
||||
),
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[]),
|
||||
);
|
||||
|
||||
@ -39,7 +41,9 @@ void main() {
|
||||
final AndroidDevices androidDevices = AndroidDevices(
|
||||
androidSdk: MockAndroidSdk(),
|
||||
logger: BufferLogger.test(),
|
||||
androidWorkflow: AndroidWorkflow(),
|
||||
androidWorkflow: AndroidWorkflow(
|
||||
androidSdk: MockAndroidSdk(),
|
||||
),
|
||||
processManager: processManager,
|
||||
);
|
||||
|
||||
@ -57,7 +61,9 @@ void main() {
|
||||
final AndroidDevices androidDevices = AndroidDevices(
|
||||
androidSdk: MockAndroidSdk(),
|
||||
logger: BufferLogger.test(),
|
||||
androidWorkflow: AndroidWorkflow(),
|
||||
androidWorkflow: AndroidWorkflow(
|
||||
androidSdk: MockAndroidSdk(),
|
||||
),
|
||||
processManager: processManager,
|
||||
);
|
||||
|
||||
|
@ -4,12 +4,11 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.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/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:quiver/testing/async.dart';
|
||||
@ -19,24 +18,42 @@ 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', () {
|
||||
testUsingContext('flags emulators without config', () {
|
||||
testWithoutContext('flags emulators without config', () {
|
||||
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.hasConfig, false);
|
||||
});
|
||||
testUsingContext('flags emulators with config', () {
|
||||
|
||||
testWithoutContext('flags emulators with config', () {
|
||||
const String emulatorID = '1234';
|
||||
final AndroidEmulator emulator = AndroidEmulator(
|
||||
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.hasConfig, true);
|
||||
});
|
||||
testUsingContext('reads expected metadata', () {
|
||||
|
||||
testWithoutContext('reads expected metadata', () {
|
||||
const String emulatorID = '1234';
|
||||
const String manufacturer = 'Me';
|
||||
const String displayName = 'The best one';
|
||||
@ -44,33 +61,57 @@ void main() {
|
||||
'hw.device.manufacturer': manufacturer,
|
||||
'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.name, displayName);
|
||||
expect(emulator.manufacturer, manufacturer);
|
||||
expect(emulator.category, Category.mobile);
|
||||
expect(emulator.platformType, PlatformType.android);
|
||||
});
|
||||
testUsingContext('prefers displayname for name', () {
|
||||
|
||||
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);
|
||||
final AndroidEmulator emulator = AndroidEmulator(
|
||||
emulatorID,
|
||||
properties: properties,
|
||||
logger: BufferLogger.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
androidSdk: MockAndroidSdk(),
|
||||
);
|
||||
|
||||
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
|
||||
// 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);
|
||||
final AndroidEmulator emulator = AndroidEmulator(
|
||||
emulatorID,
|
||||
properties: properties,
|
||||
logger: BufferLogger.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
androidSdk: MockAndroidSdk(),
|
||||
);
|
||||
|
||||
expect(emulator.name, 'This is my ID');
|
||||
});
|
||||
testUsingContext('parses ini files', () {
|
||||
|
||||
testWithoutContext('parses ini files', () {
|
||||
const String iniFile = '''
|
||||
hw.device.name=My Test Name
|
||||
#hw.device.name=Bad Name
|
||||
@ -79,6 +120,7 @@ void main() {
|
||||
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');
|
||||
@ -86,51 +128,49 @@ void main() {
|
||||
});
|
||||
|
||||
group('Android emulator launch ', () {
|
||||
const String emulatorID = 'i1234';
|
||||
const String errorText = '[Android emulator test error]';
|
||||
MockAndroidSdk mockSdk;
|
||||
FakeProcessManager successProcessManager;
|
||||
FakeProcessManager errorProcessManager;
|
||||
FakeProcessManager lateFailureProcessManager;
|
||||
MemoryFileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
fs = MemoryFileSystem();
|
||||
mockSdk = MockAndroidSdk();
|
||||
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 {
|
||||
final AndroidEmulator emulator = AndroidEmulator(emulatorID);
|
||||
testWithoutContext('succeeds', () async {
|
||||
final AndroidEmulator emulator = AndroidEmulator(emulatorID,
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(command: kEmulatorLaunchCommand),
|
||||
]),
|
||||
androidSdk: mockSdk,
|
||||
logger: BufferLogger.test(),
|
||||
);
|
||||
|
||||
expect(getEmulatorPath(mockSdk), mockSdk.emulatorPath);
|
||||
|
||||
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));
|
||||
@ -139,31 +179,24 @@ void main() {
|
||||
});
|
||||
await completer.future;
|
||||
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => successProcessManager,
|
||||
AndroidSdk: () => mockSdk,
|
||||
FileSystem: () => fs,
|
||||
expect(logger.errorText, contains(errorText));
|
||||
});
|
||||
|
||||
testUsingContext('prints error on failure', () async {
|
||||
final AndroidEmulator emulator = AndroidEmulator(emulatorID);
|
||||
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(testLogger.errorText, contains(errorText));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => errorProcessManager,
|
||||
AndroidSdk: () => mockSdk,
|
||||
FileSystem: () => fs,
|
||||
});
|
||||
|
||||
testUsingContext('prints nothing on late failure with empty stderr', () async {
|
||||
final AndroidEmulator emulator = AndroidEmulator(emulatorID);
|
||||
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>();
|
||||
FakeAsync().run((FakeAsync time) async {
|
||||
unawaited(emulator.launch().whenComplete(completer.complete));
|
||||
@ -171,11 +204,8 @@ void main() {
|
||||
time.flushMicrotasks();
|
||||
});
|
||||
await completer.future;
|
||||
expect(testLogger.errorText, isEmpty);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => lateFailureProcessManager,
|
||||
AndroidSdk: () => mockSdk,
|
||||
FileSystem: () => fs,
|
||||
|
||||
expect(logger.errorText, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -5,10 +5,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart' show ListEquality;
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/base/config.dart';
|
||||
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';
|
||||
@ -20,106 +20,252 @@ import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/mocks.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;
|
||||
MockConfig mockConfig;
|
||||
MockAndroidSdk mockSdk;
|
||||
MockXcode mockXcode;
|
||||
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockConfig = MockConfig();
|
||||
mockSdk = MockAndroidSdk();
|
||||
mockXcode = MockXcode();
|
||||
|
||||
when(mockSdk.avdManagerPath).thenReturn('avdmanager');
|
||||
when(mockSdk.getAvdManagerPath()).thenReturn('avdmanager');
|
||||
when(mockSdk.emulatorPath).thenReturn('emulator');
|
||||
});
|
||||
|
||||
group('EmulatorManager', () {
|
||||
// iOS discovery uses context.
|
||||
testUsingContext('getEmulators', () async {
|
||||
// Test that EmulatorManager.getEmulators() doesn't throw.
|
||||
final List<Emulator> emulators =
|
||||
await emulatorManager.getAllAvailableEmulators();
|
||||
expect(emulators, isList);
|
||||
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,
|
||||
),
|
||||
);
|
||||
|
||||
await expectLater(() async => await emulatorManager.getAllAvailableEmulators(),
|
||||
returnsNormally);
|
||||
});
|
||||
|
||||
testUsingContext('getEmulatorsById', () async {
|
||||
const _MockEmulator emulator1 =
|
||||
_MockEmulator('Nexus_5', 'Nexus 5', 'Google');
|
||||
const _MockEmulator emulator2 =
|
||||
_MockEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google');
|
||||
const _MockEmulator emulator3 =
|
||||
_MockEmulator('iOS Simulator', 'iOS Simulator', 'Apple');
|
||||
final List<Emulator> emulators = <Emulator>[
|
||||
emulator1,
|
||||
emulator2,
|
||||
emulator3,
|
||||
];
|
||||
final TestEmulatorManager testEmulatorManager =
|
||||
TestEmulatorManager(emulators);
|
||||
testWithoutContext('getEmulatorsById', () async {
|
||||
final TestEmulatorManager testEmulatorManager = TestEmulatorManager(emulators);
|
||||
|
||||
Future<void> expectEmulator(String id, List<Emulator> expected) async {
|
||||
expect(await testEmulatorManager.getEmulatorsMatching(id), expected);
|
||||
}
|
||||
|
||||
await expectEmulator('Nexus_5', <Emulator>[emulator1]);
|
||||
await expectEmulator('Nexus_5X', <Emulator>[emulator2]);
|
||||
await expectEmulator('Nexus_5X_API_27_x86', <Emulator>[emulator2]);
|
||||
await expectEmulator('Nexus', <Emulator>[emulator1, emulator2]);
|
||||
await expectEmulator('iOS Simulator', <Emulator>[emulator3]);
|
||||
await expectEmulator('ios', <Emulator>[emulator3]);
|
||||
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,
|
||||
),
|
||||
);
|
||||
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 CreateEmulatorResult res = await emulatorManager.createEmulator();
|
||||
expect(res.success, equals(true));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Config: () => mockConfig,
|
||||
AndroidSdk: () => mockSdk,
|
||||
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,
|
||||
),
|
||||
);
|
||||
final CreateEmulatorResult result = await emulatorManager.createEmulator();
|
||||
|
||||
expect(result.success, true);
|
||||
});
|
||||
|
||||
testUsingContext('create emulator with a unique name does not throw', () async {
|
||||
final CreateEmulatorResult res =
|
||||
await emulatorManager.createEmulator(name: 'test');
|
||||
expect(res.success, equals(true));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Config: () => mockConfig,
|
||||
AndroidSdk: () => mockSdk,
|
||||
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,
|
||||
),
|
||||
);
|
||||
final CreateEmulatorResult result = await emulatorManager.createEmulator(name: 'test');
|
||||
|
||||
expect(result.success, true);
|
||||
});
|
||||
|
||||
testUsingContext('create emulator with an existing name errors', () async {
|
||||
final CreateEmulatorResult res =
|
||||
await emulatorManager.createEmulator(name: 'existing-avd-1');
|
||||
expect(res.success, equals(false));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Config: () => mockConfig,
|
||||
AndroidSdk: () => mockSdk,
|
||||
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,
|
||||
),
|
||||
);
|
||||
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 {
|
||||
// First will get default name.
|
||||
CreateEmulatorResult res = await emulatorManager.createEmulator();
|
||||
expect(res.success, equals(true));
|
||||
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,
|
||||
),
|
||||
);
|
||||
final CreateEmulatorResult result = await emulatorManager.createEmulator();
|
||||
|
||||
final String defaultName = res.emulatorName;
|
||||
|
||||
// Second...
|
||||
res = await emulatorManager.createEmulator();
|
||||
expect(res.success, equals(true));
|
||||
expect(res.emulatorName, equals('${defaultName}_2'));
|
||||
|
||||
// Third...
|
||||
res = await emulatorManager.createEmulator();
|
||||
expect(res.success, equals(true));
|
||||
expect(res.emulatorName, equals('${defaultName}_3'));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Config: () => mockConfig,
|
||||
AndroidSdk: () => mockSdk,
|
||||
expect(result.success, true);
|
||||
expect(result.emulatorName, 'flutter_emulator_2');
|
||||
});
|
||||
});
|
||||
|
||||
@ -142,7 +288,6 @@ void main() {
|
||||
expect(didAttemptToRunSimulator, equals(true));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Config: () => mockConfig,
|
||||
Xcode: () => mockXcode,
|
||||
});
|
||||
});
|
||||
@ -159,8 +304,8 @@ class TestEmulatorManager extends EmulatorManager {
|
||||
}
|
||||
}
|
||||
|
||||
class _MockEmulator extends Emulator {
|
||||
const _MockEmulator(String id, this.name, this.manufacturer)
|
||||
class FakeEmulator extends Emulator {
|
||||
const FakeEmulator(String id, this.name, this.manufacturer)
|
||||
: super(id, true);
|
||||
|
||||
@override
|
||||
@ -181,20 +326,7 @@ class _MockEmulator extends Emulator {
|
||||
}
|
||||
}
|
||||
|
||||
class MockConfig extends Mock implements Config {}
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {
|
||||
/// 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.
|
||||
static const String mockCreateFailureOutput =
|
||||
'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)
|
||||
|
||||
static const ListEquality<String> _equality = ListEquality<String>();
|
||||
final List<String> _existingAvds = <String>['existing-avd-1'];
|
||||
|
||||
@override
|
||||
ProcessResult runSync(
|
||||
@ -212,51 +344,9 @@ class MockProcessManager extends Mock implements ProcessManager {
|
||||
case '/usr/bin/xcode-select':
|
||||
throw ProcessException(program, args);
|
||||
break;
|
||||
case 'emulator':
|
||||
return _handleEmulator(args);
|
||||
case 'avdmanager':
|
||||
return _handleAvdManager(args);
|
||||
}
|
||||
throw StateError('Unexpected process call: $command');
|
||||
}
|
||||
|
||||
ProcessResult _handleEmulator(List<String> args) {
|
||||
if (_equality.equals(args, <String>['-list-avds'])) {
|
||||
return ProcessResult(101, 0, '${_existingAvds.join('\n')}\n', '');
|
||||
}
|
||||
throw ProcessException('emulator', args);
|
||||
}
|
||||
|
||||
ProcessResult _handleAvdManager(List<String> args) {
|
||||
if (_equality.equals(args, <String>['list', 'device', '-c'])) {
|
||||
return ProcessResult(101, 0, 'test\ntest2\npixel\npixel-xl\n', '');
|
||||
}
|
||||
if (_equality.equals(args, <String>['create', 'avd', '-n', 'temp'])) {
|
||||
return ProcessResult(101, 1, '', mockCreateFailureOutput);
|
||||
}
|
||||
if (args.length == 8 &&
|
||||
_equality.equals(args,
|
||||
<String>['create', 'avd', '-n', args[3], '-k', args[5], '-d', args[7]])) {
|
||||
// In order to support testing auto generation of names we need to support
|
||||
// tracking any created emulators and reject when they already exist so this
|
||||
// mock will compare the name of the AVD being created with the fake existing
|
||||
// list and either reject if it exists, or add it to the list and return success.
|
||||
final String name = args[3];
|
||||
// Error if this AVD already existed
|
||||
if (_existingAvds.contains(name)) {
|
||||
return ProcessResult(
|
||||
101,
|
||||
1,
|
||||
'',
|
||||
"Error: Android Virtual Device '$name' already exists.\n"
|
||||
'Use --force if you want to replace it.');
|
||||
} else {
|
||||
_existingAvds.add(name);
|
||||
return ProcessResult(101, 0, '', '');
|
||||
}
|
||||
}
|
||||
throw ProcessException('emulator', args);
|
||||
}
|
||||
}
|
||||
|
||||
class MockXcode extends Mock implements Xcode {}
|
||||
|
Loading…
Reference in New Issue
Block a user