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 @@
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
bool get canLaunchAnything => _androidWorkflow.canListEmulators
&& _androidSdk.getAvdManagerPath() != null;
@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 {
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),
final RunResult runResult = await _processUtils.run(<String>[
getAvdManagerPath(_androidSdk),
'create',
'avd',
'-n', name,
'-k', sdkId,
'-d', device,
];
final RunResult runResult = processUtils.runSync(args,
environment: globals.androidSdk?.sdkManagerEnv);
], 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,24 @@ 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));
......@@ -138,15 +153,24 @@ void main() {
time.flushMicrotasks();
});
await completer.future;
}, overrides: <Type, Generator>{
ProcessManager: () => successProcessManager,
AndroidSdk: () => mockSdk,
FileSystem: () => fs,
});
testUsingContext('prints error on failure', () async {
final AndroidEmulator emulator = AndroidEmulator(emulatorID);
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));
......@@ -155,15 +179,24 @@ void main() {
});
await completer.future;
expect(testLogger.errorText, contains(errorText));
}, overrides: <Type, Generator>{
ProcessManager: () => errorProcessManager,
AndroidSdk: () => mockSdk,
FileSystem: () => fs,
expect(logger.errorText, contains(errorText));
});
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);
});
});
}
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