This reverts commit 602d8baf34.
This commit is contained in:
@@ -5,19 +5,19 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/io.dart';
|
||||
import '../convert.dart';
|
||||
|
||||
/// Default factory that creates a real Android console connection.
|
||||
///
|
||||
/// The default implementation will create real connections to a device.
|
||||
/// Override this in tests with an implementation that returns mock responses.
|
||||
Future<Socket> kAndroidConsoleSocketFactory(String host, int port) => Socket.connect(host, port);
|
||||
final AndroidConsoleSocketFactory _kAndroidConsoleSocketFactory = (String host, int port) => Socket.connect( host, port);
|
||||
|
||||
/// Currently active implementation of the AndroidConsoleFactory.
|
||||
///
|
||||
/// The default implementation will create real connections to a device.
|
||||
/// Override this in tests with an implementation that returns mock responses.
|
||||
AndroidConsoleSocketFactory get androidConsoleSocketFactory => context.get<AndroidConsoleSocketFactory>() ?? _kAndroidConsoleSocketFactory;
|
||||
|
||||
typedef AndroidConsoleSocketFactory = Future<Socket> Function(String host, int port);
|
||||
|
||||
/// Creates a console connection to an Android emulator that can be used to run
|
||||
|
||||
@@ -14,11 +14,11 @@ import '../base/common.dart' show throwToolExit, unawaited;
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../build_info.dart';
|
||||
import '../convert.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
import '../protocol_discovery.dart';
|
||||
|
||||
@@ -46,11 +46,11 @@ const Map<String, _HardwareType> _kKnownHardware = <String, _HardwareType>{
|
||||
'samsungexynos7570': _HardwareType.physical,
|
||||
};
|
||||
|
||||
bool allowHeapCorruptionOnWindows(int exitCode, Platform platform) {
|
||||
bool allowHeapCorruptionOnWindows(int exitCode) {
|
||||
// In platform tools 29.0.0 adb.exe seems to be ending with this heap
|
||||
// corruption error code on seemingly successful termination.
|
||||
// So we ignore this error on Windows.
|
||||
return exitCode == -1073740940 && platform.isWindows;
|
||||
return exitCode == -1073740940 && globals.platform.isWindows;
|
||||
}
|
||||
|
||||
class AndroidDevice extends Device {
|
||||
@@ -59,36 +59,12 @@ class AndroidDevice extends Device {
|
||||
this.productID,
|
||||
this.modelID,
|
||||
this.deviceCodeName,
|
||||
@required Logger logger,
|
||||
@required ProcessManager processManager,
|
||||
@required Platform platform,
|
||||
@required AndroidSdk androidSdk,
|
||||
@required FileSystem fileSystem,
|
||||
TimeoutConfiguration timeoutConfiguration = const TimeoutConfiguration(),
|
||||
AndroidConsoleSocketFactory androidConsoleSocketFactory = kAndroidConsoleSocketFactory,
|
||||
}) : _logger = logger,
|
||||
_processManager = processManager,
|
||||
_androidSdk = androidSdk,
|
||||
_platform = platform,
|
||||
_fileSystem = fileSystem,
|
||||
_androidConsoleSocketFactory = androidConsoleSocketFactory,
|
||||
_timeoutConfiguration = timeoutConfiguration,
|
||||
_processUtils = ProcessUtils(logger: logger, processManager: processManager),
|
||||
super(
|
||||
id,
|
||||
category: Category.mobile,
|
||||
platformType: PlatformType.android,
|
||||
ephemeral: true,
|
||||
);
|
||||
|
||||
final Logger _logger;
|
||||
final ProcessManager _processManager;
|
||||
final AndroidSdk _androidSdk;
|
||||
final Platform _platform;
|
||||
final FileSystem _fileSystem;
|
||||
final ProcessUtils _processUtils;
|
||||
final AndroidConsoleSocketFactory _androidConsoleSocketFactory;
|
||||
final TimeoutConfiguration _timeoutConfiguration;
|
||||
}) : super(
|
||||
id,
|
||||
category: Category.mobile,
|
||||
platformType: PlatformType.android,
|
||||
ephemeral: true,
|
||||
);
|
||||
|
||||
final String productID;
|
||||
final String modelID;
|
||||
@@ -96,31 +72,31 @@ class AndroidDevice extends Device {
|
||||
|
||||
Map<String, String> _properties;
|
||||
bool _isLocalEmulator;
|
||||
TargetPlatform _applicationPlatform;
|
||||
TargetPlatform _platform;
|
||||
|
||||
Future<String> _getProperty(String name) async {
|
||||
if (_properties == null) {
|
||||
_properties = <String, String>{};
|
||||
|
||||
final List<String> propCommand = adbCommandForDevice(<String>['shell', 'getprop']);
|
||||
_logger.printTrace(propCommand.join(' '));
|
||||
globals.printTrace(propCommand.join(' '));
|
||||
|
||||
try {
|
||||
// We pass an encoding of latin1 so that we don't try and interpret the
|
||||
// `adb shell getprop` result as UTF8.
|
||||
final ProcessResult result = await _processManager.run(
|
||||
final ProcessResult result = await globals.processManager.run(
|
||||
propCommand,
|
||||
stdoutEncoding: latin1,
|
||||
stderrEncoding: latin1,
|
||||
);
|
||||
if (result.exitCode == 0 || allowHeapCorruptionOnWindows(result.exitCode, _platform)) {
|
||||
if (result.exitCode == 0 || allowHeapCorruptionOnWindows(result.exitCode)) {
|
||||
_properties = parseAdbDeviceProperties(result.stdout as String);
|
||||
} else {
|
||||
_logger.printError('Error ${result.exitCode} retrieving device properties for $name:');
|
||||
_logger.printError(result.stderr as String);
|
||||
globals.printError('Error ${result.exitCode} retrieving device properties for $name:');
|
||||
globals.printError(result.stderr as String);
|
||||
}
|
||||
} on ProcessException catch (error) {
|
||||
_logger.printError('Error retrieving device properties for $name: $error');
|
||||
globals.printError('Error retrieving device properties for $name: $error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,14 +107,14 @@ class AndroidDevice extends Device {
|
||||
Future<bool> get isLocalEmulator async {
|
||||
if (_isLocalEmulator == null) {
|
||||
final String hardware = await _getProperty('ro.hardware');
|
||||
_logger.printTrace('ro.hardware = $hardware');
|
||||
globals.printTrace('ro.hardware = $hardware');
|
||||
if (_kKnownHardware.containsKey(hardware)) {
|
||||
// Look for known hardware models.
|
||||
_isLocalEmulator = _kKnownHardware[hardware] == _HardwareType.emulator;
|
||||
} else {
|
||||
// Fall back to a best-effort heuristic-based approach.
|
||||
final String characteristics = await _getProperty('ro.build.characteristics');
|
||||
_logger.printTrace('ro.build.characteristics = $characteristics');
|
||||
globals.printTrace('ro.build.characteristics = $characteristics');
|
||||
_isLocalEmulator = characteristics != null && characteristics.contains('emulator');
|
||||
}
|
||||
}
|
||||
@@ -168,27 +144,27 @@ class AndroidDevice extends Device {
|
||||
|
||||
const String host = 'localhost';
|
||||
final int port = int.parse(portMatch.group(1));
|
||||
_logger.printTrace('Fetching avd name for $name via Android console on $host:$port');
|
||||
globals.printTrace('Fetching avd name for $name via Android console on $host:$port');
|
||||
|
||||
try {
|
||||
final Socket socket = await _androidConsoleSocketFactory(host, port);
|
||||
final Socket socket = await androidConsoleSocketFactory(host, port);
|
||||
final AndroidConsole console = AndroidConsole(socket);
|
||||
|
||||
try {
|
||||
await console
|
||||
.connect()
|
||||
.timeout(_timeoutConfiguration.fastOperation,
|
||||
.timeout(timeoutConfiguration.fastOperation,
|
||||
onTimeout: () => throw TimeoutException('Connection timed out'));
|
||||
|
||||
return await console
|
||||
.getAvdName()
|
||||
.timeout(_timeoutConfiguration.fastOperation,
|
||||
.timeout(timeoutConfiguration.fastOperation,
|
||||
onTimeout: () => throw TimeoutException('"avd name" timed out'));
|
||||
} finally {
|
||||
console.destroy();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
_logger.printTrace('Failed to fetch avd name for emulator at $host:$port: $e');
|
||||
globals.printTrace('Failed to fetch avd name for emulator at $host:$port: $e');
|
||||
// If we fail to connect to the device, we should not fail so just return
|
||||
// an empty name. This data is best-effort.
|
||||
return null;
|
||||
@@ -197,7 +173,7 @@ class AndroidDevice extends Device {
|
||||
|
||||
@override
|
||||
Future<TargetPlatform> get targetPlatform async {
|
||||
if (_applicationPlatform == null) {
|
||||
if (_platform == null) {
|
||||
// http://developer.android.com/ndk/guides/abis.html (x86, armeabi-v7a, ...)
|
||||
switch (await _getProperty('ro.product.cpu.abi')) {
|
||||
case 'arm64-v8a':
|
||||
@@ -207,24 +183,24 @@ class AndroidDevice extends Device {
|
||||
// to assuming 64 bit.
|
||||
final String abilist = await _getProperty('ro.product.cpu.abilist');
|
||||
if (abilist == null || abilist.contains('arm64-v8a')) {
|
||||
_applicationPlatform = TargetPlatform.android_arm64;
|
||||
_platform = TargetPlatform.android_arm64;
|
||||
} else {
|
||||
_applicationPlatform = TargetPlatform.android_arm;
|
||||
_platform = TargetPlatform.android_arm;
|
||||
}
|
||||
break;
|
||||
case 'x86_64':
|
||||
_applicationPlatform = TargetPlatform.android_x64;
|
||||
_platform = TargetPlatform.android_x64;
|
||||
break;
|
||||
case 'x86':
|
||||
_applicationPlatform = TargetPlatform.android_x86;
|
||||
_platform = TargetPlatform.android_x86;
|
||||
break;
|
||||
default:
|
||||
_applicationPlatform = TargetPlatform.android_arm;
|
||||
_platform = TargetPlatform.android_arm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _applicationPlatform;
|
||||
return _platform;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -241,7 +217,7 @@ class AndroidDevice extends Device {
|
||||
AndroidDevicePortForwarder _portForwarder;
|
||||
|
||||
List<String> adbCommandForDevice(List<String> args) {
|
||||
return <String>[getAdbPath(_androidSdk), '-s', id, ...args];
|
||||
return <String>[getAdbPath(globals.androidSdk), '-s', id, ...args];
|
||||
}
|
||||
|
||||
String runAdbCheckedSync(
|
||||
@@ -250,13 +226,13 @@ class AndroidDevice extends Device {
|
||||
bool allowReentrantFlutter = false,
|
||||
Map<String, String> environment,
|
||||
}) {
|
||||
return _processUtils.runSync(
|
||||
return processUtils.runSync(
|
||||
adbCommandForDevice(params),
|
||||
throwOnError: true,
|
||||
workingDirectory: workingDirectory,
|
||||
allowReentrantFlutter: allowReentrantFlutter,
|
||||
environment: environment,
|
||||
whiteListFailures: (int value) => allowHeapCorruptionOnWindows(value, _platform),
|
||||
whiteListFailures: allowHeapCorruptionOnWindows,
|
||||
).stdout.trim();
|
||||
}
|
||||
|
||||
@@ -265,12 +241,12 @@ class AndroidDevice extends Device {
|
||||
String workingDirectory,
|
||||
bool allowReentrantFlutter = false,
|
||||
}) async {
|
||||
return _processUtils.run(
|
||||
return processUtils.run(
|
||||
adbCommandForDevice(params),
|
||||
throwOnError: true,
|
||||
workingDirectory: workingDirectory,
|
||||
allowReentrantFlutter: allowReentrantFlutter,
|
||||
whiteListFailures: (int value) => allowHeapCorruptionOnWindows(value, _platform),
|
||||
whiteListFailures: allowHeapCorruptionOnWindows,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -292,27 +268,27 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_logger.printError(
|
||||
globals.printError(
|
||||
'Unrecognized adb version string $adbVersion. Skipping version check.');
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> _checkForSupportedAdbVersion() async {
|
||||
if (_androidSdk == null) {
|
||||
if (globals.androidSdk == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final RunResult adbVersion = await _processUtils.run(
|
||||
<String>[getAdbPath(_androidSdk), 'version'],
|
||||
final RunResult adbVersion = await processUtils.run(
|
||||
<String>[getAdbPath(globals.androidSdk), 'version'],
|
||||
throwOnError: true,
|
||||
);
|
||||
if (_isValidAdbVersion(adbVersion.stdout)) {
|
||||
return true;
|
||||
}
|
||||
_logger.printError('The ADB at "${getAdbPath(_androidSdk)}" is too old; please install version 1.0.39 or later.');
|
||||
globals.printError('The ADB at "${getAdbPath(globals.androidSdk)}" is too old; please install version 1.0.39 or later.');
|
||||
} on Exception catch (error, trace) {
|
||||
_logger.printError('Error running ADB: $error', stackTrace: trace);
|
||||
globals.printError('Error running ADB: $error', stackTrace: trace);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -324,8 +300,8 @@ class AndroidDevice extends Device {
|
||||
// output lines like this, which we want to ignore:
|
||||
// adb server is out of date. killing..
|
||||
// * daemon started successfully *
|
||||
await _processUtils.run(
|
||||
<String>[getAdbPath(_androidSdk), 'start-server'],
|
||||
await processUtils.run(
|
||||
<String>[getAdbPath(globals.androidSdk), 'start-server'],
|
||||
throwOnError: true,
|
||||
);
|
||||
|
||||
@@ -337,12 +313,12 @@ class AndroidDevice extends Device {
|
||||
|
||||
final int sdkVersionParsed = int.tryParse(sdkVersion);
|
||||
if (sdkVersionParsed == null) {
|
||||
_logger.printError('Unexpected response from getprop: "$sdkVersion"');
|
||||
globals.printError('Unexpected response from getprop: "$sdkVersion"');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sdkVersionParsed < minApiLevel) {
|
||||
_logger.printError(
|
||||
globals.printError(
|
||||
'The Android version ($sdkVersion) on the target device is too old. Please '
|
||||
'use a $minVersionName (version $minApiLevel / $minVersionText) device or later.');
|
||||
return false;
|
||||
@@ -350,8 +326,8 @@ class AndroidDevice extends Device {
|
||||
|
||||
return true;
|
||||
} on Exception catch (e, stacktrace) {
|
||||
_logger.printError('Unexpected failure from adb: $e');
|
||||
_logger.printError('Stacktrace: $stacktrace');
|
||||
globals.printError('Unexpected failure from adb: $e');
|
||||
globals.printError('Stacktrace: $stacktrace');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -361,13 +337,13 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
|
||||
Future<String> _getDeviceApkSha1(AndroidApk apk) async {
|
||||
final RunResult result = await _processUtils.run(
|
||||
final RunResult result = await processUtils.run(
|
||||
adbCommandForDevice(<String>['shell', 'cat', _getDeviceSha1Path(apk)]));
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
String _getSourceSha1(AndroidApk apk) {
|
||||
final File shaFile = _fileSystem.file('${apk.file.path}.sha1');
|
||||
final File shaFile = globals.fs.file('${apk.file.path}.sha1');
|
||||
return shaFile.existsSync() ? shaFile.readAsStringSync() : '';
|
||||
}
|
||||
|
||||
@@ -381,7 +357,7 @@ class AndroidDevice extends Device {
|
||||
final RunResult listOut = await runAdbCheckedAsync(<String>['shell', 'pm', 'list', 'packages', app.id]);
|
||||
return LineSplitter.split(listOut.stdout).contains('package:${app.id}');
|
||||
} on Exception catch (error) {
|
||||
_logger.printTrace('$error');
|
||||
globals.printTrace('$error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -395,7 +371,7 @@ class AndroidDevice extends Device {
|
||||
@override
|
||||
Future<bool> installApp(AndroidApk app) async {
|
||||
if (!app.file.existsSync()) {
|
||||
_logger.printError('"${_fileSystem.path.relative(app.file.path)}" does not exist.');
|
||||
globals.printError('"${globals.fs.path.relative(app.file.path)}" does not exist.');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -404,11 +380,8 @@ class AndroidDevice extends Device {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Status status = _logger.startProgress(
|
||||
'Installing ${_fileSystem.path.relative(app.file.path)}...',
|
||||
timeout: _timeoutConfiguration.slowOperation,
|
||||
);
|
||||
final RunResult installResult = await _processUtils.run(
|
||||
final Status status = globals.logger.startProgress('Installing ${globals.fs.path.relative(app.file.path)}...', timeout: timeoutConfiguration.slowOperation);
|
||||
final RunResult installResult = await processUtils.run(
|
||||
adbCommandForDevice(<String>['install', '-t', '-r', app.file.path]));
|
||||
status.stop();
|
||||
// Some versions of adb exit with exit code 0 even on failure :(
|
||||
@@ -416,12 +389,12 @@ class AndroidDevice extends Device {
|
||||
final RegExp failureExp = RegExp(r'^Failure.*$', multiLine: true);
|
||||
final String failure = failureExp.stringMatch(installResult.stdout);
|
||||
if (failure != null) {
|
||||
_logger.printError('Package install error: $failure');
|
||||
globals.printError('Package install error: $failure');
|
||||
return false;
|
||||
}
|
||||
if (installResult.exitCode != 0) {
|
||||
_logger.printError('Error: ADB exited with exit code ${installResult.exitCode}');
|
||||
_logger.printError('$installResult');
|
||||
globals.printError('Error: ADB exited with exit code ${installResult.exitCode}');
|
||||
globals.printError('$installResult');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
@@ -429,7 +402,7 @@ class AndroidDevice extends Device {
|
||||
'shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app),
|
||||
]);
|
||||
} on ProcessException catch (error) {
|
||||
_logger.printError('adb shell failed to write the SHA hash: $error.');
|
||||
globals.printError('adb shell failed to write the SHA hash: $error.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -444,19 +417,19 @@ class AndroidDevice extends Device {
|
||||
|
||||
String uninstallOut;
|
||||
try {
|
||||
final RunResult uninstallResult = await _processUtils.run(
|
||||
final RunResult uninstallResult = await processUtils.run(
|
||||
adbCommandForDevice(<String>['uninstall', app.id]),
|
||||
throwOnError: true,
|
||||
);
|
||||
uninstallOut = uninstallResult.stdout;
|
||||
} on Exception catch (error) {
|
||||
_logger.printError('adb uninstall failed: $error');
|
||||
globals.printError('adb uninstall failed: $error');
|
||||
return false;
|
||||
}
|
||||
final RegExp failureExp = RegExp(r'^Failure.*$', multiLine: true);
|
||||
final String failure = failureExp.stringMatch(uninstallOut);
|
||||
if (failure != null) {
|
||||
_logger.printError('Package uninstall error: $failure');
|
||||
globals.printError('Package uninstall error: $failure');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -467,21 +440,21 @@ class AndroidDevice extends Device {
|
||||
final bool wasInstalled = await isAppInstalled(package);
|
||||
if (wasInstalled) {
|
||||
if (await isLatestBuildInstalled(package)) {
|
||||
_logger.printTrace('Latest build already installed.');
|
||||
globals.printTrace('Latest build already installed.');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_logger.printTrace('Installing APK.');
|
||||
globals.printTrace('Installing APK.');
|
||||
if (!await installApp(package)) {
|
||||
_logger.printTrace('Warning: Failed to install APK.');
|
||||
globals.printTrace('Warning: Failed to install APK.');
|
||||
if (wasInstalled) {
|
||||
_logger.printStatus('Uninstalling old version...');
|
||||
globals.printStatus('Uninstalling old version...');
|
||||
if (!await uninstallApp(package)) {
|
||||
_logger.printError('Error: Uninstalling old version failed.');
|
||||
globals.printError('Error: Uninstalling old version failed.');
|
||||
return false;
|
||||
}
|
||||
if (!await installApp(package)) {
|
||||
_logger.printError('Error: Failed to install APK again.');
|
||||
globals.printError('Error: Failed to install APK again.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -511,7 +484,7 @@ class AndroidDevice extends Device {
|
||||
final TargetPlatform devicePlatform = await targetPlatform;
|
||||
if (devicePlatform == TargetPlatform.android_x86 &&
|
||||
!debuggingOptions.buildInfo.isDebug) {
|
||||
_logger.printError('Profile and release builds are only supported on ARM/x64 targets.');
|
||||
globals.printError('Profile and release builds are only supported on ARM/x64 targets.');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
@@ -530,12 +503,12 @@ class AndroidDevice extends Device {
|
||||
androidArch = AndroidArch.x86;
|
||||
break;
|
||||
default:
|
||||
_logger.printError('Android platforms are only supported.');
|
||||
globals.printError('Android platforms are only supported.');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
if (!prebuiltApplication || _androidSdk.licensesAvailable && _androidSdk.latestVersion == null) {
|
||||
_logger.printTrace('Building APK');
|
||||
if (!prebuiltApplication || globals.androidSdk.licensesAvailable && globals.androidSdk.latestVersion == null) {
|
||||
globals.printTrace('Building APK');
|
||||
final FlutterProject project = FlutterProject.current();
|
||||
await androidBuilder.buildApk(
|
||||
project: project,
|
||||
@@ -555,7 +528,7 @@ class AndroidDevice extends Device {
|
||||
throwToolExit('Problem building Android application: see above error(s).');
|
||||
}
|
||||
|
||||
_logger.printTrace("Stopping app '${package.name}' on $name.");
|
||||
globals.printTrace("Stopping app '${package.name}' on $name.");
|
||||
await stopApp(package);
|
||||
|
||||
if (!await _installLatestApp(package)) {
|
||||
@@ -563,7 +536,7 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
|
||||
final bool traceStartup = platformArgs['trace-startup'] as bool ?? false;
|
||||
_logger.printTrace('$this startApp');
|
||||
globals.printTrace('$this startApp');
|
||||
|
||||
ProtocolDiscovery observatoryDiscovery;
|
||||
|
||||
@@ -628,7 +601,7 @@ class AndroidDevice extends Device {
|
||||
final String result = (await runAdbCheckedAsync(cmd)).stdout;
|
||||
// This invocation returns 0 even when it fails.
|
||||
if (result.contains('Error: ')) {
|
||||
_logger.printError(result.trim(), wrap: false);
|
||||
globals.printError(result.trim(), wrap: false);
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
@@ -639,7 +612,7 @@ class AndroidDevice extends Device {
|
||||
|
||||
// Wait for the service protocol port here. This will complete once the
|
||||
// device has printed "Observatory is listening on...".
|
||||
_logger.printTrace('Waiting for observatory port to be available...');
|
||||
globals.printTrace('Waiting for observatory port to be available...');
|
||||
|
||||
// TODO(danrubel): Waiting for observatory services can be made common across all devices.
|
||||
try {
|
||||
@@ -647,7 +620,7 @@ class AndroidDevice extends Device {
|
||||
if (debuggingOptions.buildInfo.isDebug || debuggingOptions.buildInfo.isProfile) {
|
||||
observatoryUri = await observatoryDiscovery.uri;
|
||||
if (observatoryUri == null) {
|
||||
_logger.printError(
|
||||
globals.printError(
|
||||
'Error waiting for a debug connection: '
|
||||
'The log reader stopped unexpectedly',
|
||||
);
|
||||
@@ -656,7 +629,7 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
return LaunchResult.succeeded(observatoryUri: observatoryUri);
|
||||
} on Exception catch (error) {
|
||||
_logger.printError('Error waiting for a debug connection: $error');
|
||||
globals.printError('Error waiting for a debug connection: $error');
|
||||
return LaunchResult.failed();
|
||||
} finally {
|
||||
await observatoryDiscovery.cancel();
|
||||
@@ -678,13 +651,13 @@ class AndroidDevice extends Device {
|
||||
return Future<bool>.value(false);
|
||||
}
|
||||
final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]);
|
||||
return _processUtils.stream(command).then<bool>(
|
||||
(int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode, _platform));
|
||||
return processUtils.stream(command).then<bool>(
|
||||
(int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MemoryInfo> queryMemoryInfo() async {
|
||||
final RunResult runResult = await _processUtils.run(adbCommandForDevice(<String>[
|
||||
final RunResult runResult = await processUtils.run(adbCommandForDevice(<String>[
|
||||
'shell',
|
||||
'dumpsys',
|
||||
'meminfo',
|
||||
@@ -700,7 +673,7 @@ class AndroidDevice extends Device {
|
||||
|
||||
@override
|
||||
void clearLogs() {
|
||||
_processUtils.runSync(adbCommandForDevice(<String>['logcat', '-c']));
|
||||
processUtils.runSync(adbCommandForDevice(<String>['logcat', '-c']));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -712,23 +685,23 @@ class AndroidDevice extends Device {
|
||||
if (includePastLogs) {
|
||||
return _pastLogReader ??= await AdbLogReader.createLogReader(
|
||||
this,
|
||||
_processManager,
|
||||
globals.processManager,
|
||||
includePastLogs: true,
|
||||
);
|
||||
} else {
|
||||
return _logReader ??= await AdbLogReader.createLogReader(
|
||||
this,
|
||||
_processManager,
|
||||
globals.processManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
DevicePortForwarder get portForwarder => _portForwarder ??= AndroidDevicePortForwarder(
|
||||
processManager: _processManager,
|
||||
logger: _logger,
|
||||
processManager: globals.processManager,
|
||||
logger: globals.logger,
|
||||
deviceId: id,
|
||||
adbPath: _androidSdk.adbPath,
|
||||
adbPath: globals.androidSdk.adbPath,
|
||||
);
|
||||
|
||||
static final RegExp _timeRegExp = RegExp(r'^\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}', multiLine: true);
|
||||
@@ -742,7 +715,7 @@ class AndroidDevice extends Device {
|
||||
'shell', '-x', 'logcat', '-v', 'time', '-t', '1'
|
||||
]);
|
||||
} on Exception catch (error) {
|
||||
_logger.printError('Failed to extract the most recent timestamp from the Android log: $error.');
|
||||
globals.printError('Failed to extract the most recent timestamp from the Android log: $error.');
|
||||
return null;
|
||||
}
|
||||
final Match timeMatch = _timeRegExp.firstMatch(output);
|
||||
@@ -759,7 +732,7 @@ class AndroidDevice extends Device {
|
||||
Future<void> takeScreenshot(File outputFile) async {
|
||||
const String remotePath = '/data/local/tmp/flutter_screenshot.png';
|
||||
await runAdbCheckedAsync(<String>['shell', 'screencap', '-p', remotePath]);
|
||||
await _processUtils.run(
|
||||
await processUtils.run(
|
||||
adbCommandForDevice(<String>['pull', remotePath, outputFile.path]),
|
||||
throwOnError: true,
|
||||
);
|
||||
|
||||
@@ -6,10 +6,8 @@ import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart' as globals;
|
||||
@@ -98,12 +96,6 @@ class AndroidDevices extends PollingDeviceDiscovery {
|
||||
String text, {
|
||||
List<AndroidDevice> devices,
|
||||
List<String> diagnostics,
|
||||
AndroidSdk androidSdk,
|
||||
FileSystem fileSystem,
|
||||
Logger logger,
|
||||
Platform platform,
|
||||
ProcessManager processManager,
|
||||
TimeoutConfiguration timeoutConfiguration,
|
||||
}) {
|
||||
// Check for error messages from adb
|
||||
if (!text.contains('List of devices')) {
|
||||
@@ -162,12 +154,6 @@ class AndroidDevices extends PollingDeviceDiscovery {
|
||||
productID: info['product'],
|
||||
modelID: info['model'] ?? deviceID,
|
||||
deviceCodeName: info['device'],
|
||||
androidSdk: androidSdk ?? globals.androidSdk,
|
||||
fileSystem: fileSystem ?? globals.fs,
|
||||
logger: logger ?? globals.logger,
|
||||
platform: platform ?? globals.platform,
|
||||
processManager: processManager ?? globals.processManager,
|
||||
timeoutConfiguration: timeoutConfiguration,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_device.dart';
|
||||
import 'package:flutter_tools/src/android/android_device_discovery.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/android_workflow.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
@@ -71,15 +69,7 @@ void main() {
|
||||
List of devices attached
|
||||
05a02bac device usb:336592896X product:razor model:Nexus_7 device:flo
|
||||
|
||||
''',
|
||||
devices: devices,
|
||||
androidSdk: MockAndroidSdk(),
|
||||
logger: BufferLogger.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
timeoutConfiguration: const TimeoutConfiguration(),
|
||||
platform: FakePlatform(),
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
);
|
||||
''', devices: devices);
|
||||
|
||||
expect(devices, hasLength(1));
|
||||
expect(devices.first.name, 'Nexus 7');
|
||||
@@ -94,15 +84,7 @@ localhost:36790 device
|
||||
0149947A0D01500C device usb:340787200X
|
||||
emulator-5612 host features:shell_2
|
||||
|
||||
''',
|
||||
devices: devices,
|
||||
androidSdk: MockAndroidSdk(),
|
||||
logger: BufferLogger.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
timeoutConfiguration: const TimeoutConfiguration(),
|
||||
platform: FakePlatform(),
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
);
|
||||
''', devices: devices);
|
||||
|
||||
expect(devices, hasLength(3));
|
||||
expect(devices.first.name, 'localhost:36790');
|
||||
@@ -113,15 +95,7 @@ emulator-5612 host features:shell_2
|
||||
AndroidDevices.parseADBDeviceOutput('''
|
||||
List of devices attached
|
||||
ZX1G22JJWR device usb:3-3 product:shamu model:Nexus_6 device:shamu features:cmd,shell_v2
|
||||
''',
|
||||
devices: devices,
|
||||
androidSdk: MockAndroidSdk(),
|
||||
logger: BufferLogger.test(),
|
||||
processManager: FakeProcessManager.any(),
|
||||
timeoutConfiguration: const TimeoutConfiguration(),
|
||||
platform: FakePlatform(),
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
);
|
||||
''', devices: devices);
|
||||
|
||||
expect(devices, hasLength(1));
|
||||
expect(devices.first.name, 'Nexus 6');
|
||||
|
||||
@@ -7,8 +7,6 @@ import 'package:flutter_tools/src/android/android_device.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/application_package.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
@@ -57,16 +55,11 @@ void main() {
|
||||
TargetPlatform.android_arm64,
|
||||
TargetPlatform.android_x64,
|
||||
]) {
|
||||
testWithoutContext('AndroidDevice.startApp allows release builds on $targetPlatform', () async {
|
||||
testUsingContext('AndroidDevice.startApp allows release builds on $targetPlatform', () async {
|
||||
const String deviceId = '1234';
|
||||
final String arch = getNameForAndroidArch(
|
||||
getAndroidArchForName(getNameForTargetPlatform(targetPlatform)));
|
||||
final AndroidDevice device = AndroidDevice('1234', modelID: 'TestModel',
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: BufferLogger.test(),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
androidSdk: androidSdk,
|
||||
);
|
||||
final AndroidDevice device = AndroidDevice(deviceId, modelID: 'TestModel');
|
||||
final File apkFile = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk apk = AndroidApk(
|
||||
id: 'FlutterApp',
|
||||
@@ -124,17 +117,16 @@ void main() {
|
||||
|
||||
expect(launchResult.started, true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => androidSdk,
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
}
|
||||
|
||||
testWithoutContext('AndroidDevice.startApp does not allow release builds on x86', () async {
|
||||
final AndroidDevice device = AndroidDevice('1234', modelID: 'TestModel',
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: BufferLogger.test(),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
androidSdk: androidSdk,
|
||||
);
|
||||
testUsingContext('AndroidDevice.startApp does not allow release builds on x86', () async {
|
||||
const String deviceId = '1234';
|
||||
final AndroidDevice device = AndroidDevice(deviceId, modelID: 'TestModel');
|
||||
final File apkFile = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk apk = AndroidApk(
|
||||
id: 'FlutterApp',
|
||||
@@ -163,16 +155,15 @@ void main() {
|
||||
|
||||
expect(launchResult.started, false);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => androidSdk,
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice.startApp forwards all supported debugging options', () async {
|
||||
final AndroidDevice device = AndroidDevice('1234', modelID: 'TestModel',
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: BufferLogger.test(),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
androidSdk: androidSdk,
|
||||
);
|
||||
testUsingContext('AndroidDevice.startApp forwards all supported debugging options', () async {
|
||||
const String deviceId = '1234';
|
||||
final AndroidDevice device = AndroidDevice(deviceId, modelID: 'TestModel');
|
||||
final File apkFile = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk apk = AndroidApk(
|
||||
id: 'FlutterApp',
|
||||
@@ -273,6 +264,10 @@ void main() {
|
||||
// This fails to start due to observatory discovery issues.
|
||||
expect(launchResult.started, false);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidSdk: () => androidSdk,
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,28 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_device.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
testWithoutContext('AndroidDevice.stopApp handles a null ApplicationPackage', () async {
|
||||
final AndroidDevice androidDevice = AndroidDevice('1234',
|
||||
androidSdk: MockAndroidSdk(),
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
logger: BufferLogger.test(),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
processManager: FakeProcessManager.any(),
|
||||
);
|
||||
final AndroidDevice androidDevice = AndroidDevice('2');
|
||||
|
||||
expect(await androidDevice.stopApp(null), false);
|
||||
});
|
||||
}
|
||||
|
||||
class MockAndroidSdk extends Mock implements AndroidSdk {}
|
||||
|
||||
@@ -9,13 +9,13 @@ import 'dart:typed_data';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_console.dart';
|
||||
import 'package:flutter_tools/src/android/android_device.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/application_package.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
@@ -24,150 +24,239 @@ import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
testWithoutContext('AndroidDevice stores the requested id', () {
|
||||
final AndroidDevice device = setUpAndroidDevice();
|
||||
|
||||
expect(device.id, '1234');
|
||||
testUsingContext('AndroidDevice stores the requested id', () {
|
||||
const String deviceId = '1234';
|
||||
final AndroidDevice device = AndroidDevice(deviceId);
|
||||
expect(device.id, deviceId);
|
||||
});
|
||||
|
||||
testWithoutContext('parseAdbDeviceProperties parses adb shell output', () {
|
||||
final Map<String, String> properties = parseAdbDeviceProperties(kAdbShellGetprop);
|
||||
|
||||
expect(properties, isNotNull);
|
||||
expect(properties['ro.build.characteristics'], 'emulator');
|
||||
expect(properties['ro.product.cpu.abi'], 'x86_64');
|
||||
expect(properties['ro.build.version.sdk'], '23');
|
||||
group('parseAdbDeviceProperties', () {
|
||||
test('parse adb shell output', () {
|
||||
final Map<String, String> properties = parseAdbDeviceProperties(kAdbShellGetprop);
|
||||
expect(properties, isNotNull);
|
||||
expect(properties['ro.build.characteristics'], 'emulator');
|
||||
expect(properties['ro.product.cpu.abi'], 'x86_64');
|
||||
expect(properties['ro.build.version.sdk'], '23');
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('adb exiting with heap corruption is only allowed on windows', () async {
|
||||
final List<FakeCommand> commands = <FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [goldfish]\n[ro.build.characteristics]: [unused]',
|
||||
// Heap corruption exit code.
|
||||
exitCode: -1073740940,
|
||||
)
|
||||
];
|
||||
group('adb.exe exiting with heap corruption on windows', () {
|
||||
final ProcessManager mockProcessManager = MockProcessManager();
|
||||
String hardware;
|
||||
String buildCharacteristics;
|
||||
|
||||
final AndroidDevice windowsDevice = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(commands.toList()),
|
||||
platform: FakePlatform(operatingSystem: 'windows'),
|
||||
);
|
||||
final AndroidDevice linuxDevice = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(commands.toList()),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
);
|
||||
final AndroidDevice macOsDevice = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(commands.toList()),
|
||||
platform: FakePlatform(operatingSystem: 'macos')
|
||||
);
|
||||
setUp(() {
|
||||
hardware = 'goldfish';
|
||||
buildCharacteristics = 'unused';
|
||||
exitCode = -1;
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('getprop')),
|
||||
stderrEncoding: anyNamed('stderrEncoding'),
|
||||
stdoutEncoding: anyNamed('stdoutEncoding'),
|
||||
)).thenAnswer((_) {
|
||||
final StringBuffer buf = StringBuffer()
|
||||
..writeln('[ro.hardware]: [$hardware]')..writeln(
|
||||
'[ro.build.characteristics]: [$buildCharacteristics]');
|
||||
final ProcessResult result = ProcessResult(1, exitCode, buf.toString(), '');
|
||||
return Future<ProcessResult>.value(result);
|
||||
});
|
||||
});
|
||||
|
||||
// Parsing succeedes despite the error.
|
||||
expect(await windowsDevice.isLocalEmulator, true);
|
||||
testUsingContext('nonHeapCorruptionErrorOnWindows', () async {
|
||||
exitCode = -1073740941;
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Platform: () => FakePlatform(
|
||||
operatingSystem: 'windows',
|
||||
environment: <String, String>{
|
||||
'ANDROID_HOME': '/',
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
// Parsing fails and these default to false.
|
||||
expect(await linuxDevice.isLocalEmulator, false);
|
||||
expect(await macOsDevice.isLocalEmulator, false);
|
||||
testUsingContext('heapCorruptionOnWindows', () async {
|
||||
exitCode = -1073740940;
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Platform: () => FakePlatform(
|
||||
operatingSystem: 'windows',
|
||||
environment: <String, String>{
|
||||
'ANDROID_HOME': '/',
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
testUsingContext('heapCorruptionExitCodeOnLinux', () async {
|
||||
exitCode = -1073740940;
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Platform: () => FakePlatform(
|
||||
operatingSystem: 'linux',
|
||||
environment: <String, String>{
|
||||
'ANDROID_HOME': '/',
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
testUsingContext('noErrorOnLinux', () async {
|
||||
exitCode = 0;
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Platform: () => FakePlatform(
|
||||
operatingSystem: 'linux',
|
||||
environment: <String, String>{
|
||||
'ANDROID_HOME': '/',
|
||||
},
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice can detect TargetPlatform from property '
|
||||
'abi and abiList', () async {
|
||||
// The format is [ABI, ABI list]: expected target platform.
|
||||
final Map<List<String>, TargetPlatform> values = <List<String>, TargetPlatform>{
|
||||
<String>['x86_64', 'unknown']: TargetPlatform.android_x64,
|
||||
<String>['x86', 'unknown']: TargetPlatform.android_x86,
|
||||
// The default ABI is arm32
|
||||
<String>['???', 'unknown']: TargetPlatform.android_arm,
|
||||
<String>['arm64-v8a', 'arm64-v8a,']: TargetPlatform.android_arm64,
|
||||
// The Kindle Fire runs 32 bit apps on 64 bit hardware.
|
||||
<String>['arm64-v8a', 'arm']: TargetPlatform.android_arm,
|
||||
};
|
||||
group('ABI detection', () {
|
||||
ProcessManager mockProcessManager;
|
||||
String cpu;
|
||||
String abilist;
|
||||
|
||||
for (final MapEntry<List<String>, TargetPlatform> entry in values.entries) {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: const <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.product.cpu.abi]: [${entry.key.first}]\n'
|
||||
'[ro.product.cpu.abilist]: [${entry.key.last}]'
|
||||
)
|
||||
]),
|
||||
);
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
cpu = 'unknown';
|
||||
abilist = 'unknown';
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('getprop')),
|
||||
stderrEncoding: anyNamed('stderrEncoding'),
|
||||
stdoutEncoding: anyNamed('stdoutEncoding'),
|
||||
)).thenAnswer((_) {
|
||||
final StringBuffer buf = StringBuffer()
|
||||
..writeln('[ro.product.cpu.abi]: [$cpu]')
|
||||
..writeln('[ro.product.cpu.abilist]: [$abilist]');
|
||||
final ProcessResult result = ProcessResult(1, 0, buf.toString(), '');
|
||||
return Future<ProcessResult>.value(result);
|
||||
});
|
||||
});
|
||||
|
||||
expect(await device.targetPlatform, entry.value);
|
||||
}
|
||||
testUsingContext('detects x64', () async {
|
||||
cpu = 'x86_64';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
|
||||
expect(await device.targetPlatform, TargetPlatform.android_x64);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager
|
||||
});
|
||||
|
||||
testUsingContext('detects x86', () async {
|
||||
cpu = 'x86';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
|
||||
expect(await device.targetPlatform, TargetPlatform.android_x86);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager
|
||||
});
|
||||
|
||||
testUsingContext('unknown device defaults to 32bit arm', () async {
|
||||
cpu = '???';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
|
||||
expect(await device.targetPlatform, TargetPlatform.android_arm);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager
|
||||
});
|
||||
|
||||
testUsingContext('detects 64 bit arm', () async {
|
||||
cpu = 'arm64-v8a';
|
||||
abilist = 'arm64-v8a,';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
|
||||
// If both abi properties agree, we are 64 bit.
|
||||
expect(await device.targetPlatform, TargetPlatform.android_arm64);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager
|
||||
});
|
||||
|
||||
testUsingContext('detects kindle fire ABI', () async {
|
||||
cpu = 'arm64-v8a';
|
||||
abilist = 'arm';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
|
||||
// If one does not contain arm64, assume 32 bit.
|
||||
expect(await device.targetPlatform, TargetPlatform.android_arm);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice can detect local emulator for known types', () async {
|
||||
final Set<String> knownPhyiscal = <String>{
|
||||
'qcom',
|
||||
'samsungexynos7420',
|
||||
'samsungexynos7580',
|
||||
'samsungexynos7870',
|
||||
'samsungexynos7880',
|
||||
'samsungexynos8890',
|
||||
'samsungexynos8895',
|
||||
'samsungexynos9810',
|
||||
'samsungexynos7570',
|
||||
};
|
||||
final Set<String> knownEmulator = <String>{
|
||||
'goldfish',
|
||||
'ranchu',
|
||||
};
|
||||
group('isLocalEmulator', () {
|
||||
final ProcessManager mockProcessManager = MockProcessManager();
|
||||
String hardware;
|
||||
String buildCharacteristics;
|
||||
|
||||
for (final String hardware in knownPhyiscal.followedBy(knownEmulator)) {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'adb', '-s', '1234', 'shell', 'getprop',
|
||||
],
|
||||
stdout: '[ro.hardware]: [$hardware]\n'
|
||||
'[ro.build.characteristics]: [unused]'
|
||||
),
|
||||
])
|
||||
);
|
||||
setUp(() {
|
||||
hardware = 'unknown';
|
||||
buildCharacteristics = 'unused';
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('getprop')),
|
||||
stderrEncoding: anyNamed('stderrEncoding'),
|
||||
stdoutEncoding: anyNamed('stdoutEncoding'),
|
||||
)).thenAnswer((_) {
|
||||
final StringBuffer buf = StringBuffer()
|
||||
..writeln('[ro.hardware]: [$hardware]')
|
||||
..writeln('[ro.build.characteristics]: [$buildCharacteristics]');
|
||||
final ProcessResult result = ProcessResult(1, 0, buf.toString(), '');
|
||||
return Future<ProcessResult>.value(result);
|
||||
});
|
||||
});
|
||||
|
||||
expect(await device.isLocalEmulator, knownEmulator.contains(hardware));
|
||||
}
|
||||
testUsingContext('knownPhysical', () async {
|
||||
hardware = 'samsungexynos7420';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('knownPhysical Samsung SM G570M', () async {
|
||||
hardware = 'samsungexynos7570';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('knownEmulator', () async {
|
||||
hardware = 'goldfish';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, true);
|
||||
expect(await device.supportsHardwareRendering, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('unknownPhysical', () async {
|
||||
buildCharacteristics = 'att';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('unknownEmulator', () async {
|
||||
buildCharacteristics = 'att,emulator';
|
||||
final AndroidDevice device = AndroidDevice('test');
|
||||
expect(await device.isLocalEmulator, true);
|
||||
expect(await device.supportsHardwareRendering, true);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice can detect unknown hardware', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'adb', '-s', '1234', 'shell', 'getprop',
|
||||
],
|
||||
stdout: '[ro.hardware]: [unknown]\n'
|
||||
'[ro.build.characteristics]: [att]'
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
expect(await device.isLocalEmulator, false);
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice can detect unknown emulator', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'adb', '-s', '1234', 'shell', 'getprop',
|
||||
],
|
||||
stdout: '[ro.hardware]: [unknown]\n'
|
||||
'[ro.build.characteristics]: [att,emulator]'
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
expect(await device.isLocalEmulator, true);
|
||||
expect(await device.supportsHardwareRendering, true);
|
||||
});
|
||||
|
||||
testWithoutContext('isSupportedForProject is true on module project', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
fileSystem.file('pubspec.yaml')
|
||||
testUsingContext('isSupportedForProject is true on module project', () async {
|
||||
globals.fs.file('pubspec.yaml')
|
||||
..createSync()
|
||||
..writeAsStringSync(r'''
|
||||
name: example
|
||||
@@ -175,188 +264,174 @@ name: example
|
||||
flutter:
|
||||
module: {}
|
||||
''');
|
||||
fileSystem.file('.packages').createSync();
|
||||
final FlutterProject flutterProject = FlutterProjectFactory(
|
||||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
).fromDirectory(fileSystem.currentDirectory);
|
||||
final AndroidDevice device = setUpAndroidDevice(fileSystem: fileSystem);
|
||||
globals.fs.file('.packages').createSync();
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
|
||||
expect(device.isSupportedForProject(flutterProject), true);
|
||||
expect(AndroidDevice('test').isSupportedForProject(flutterProject), true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testWithoutContext('isSupportedForProject is true with editable host app', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
fileSystem.file('pubspec.yaml').createSync();
|
||||
fileSystem.file('.packages').createSync();
|
||||
fileSystem.directory('android').createSync();
|
||||
final FlutterProject flutterProject = FlutterProjectFactory(
|
||||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
).fromDirectory(fileSystem.currentDirectory);
|
||||
testUsingContext('isSupportedForProject is true with editable host app', () async {
|
||||
globals.fs.file('pubspec.yaml').createSync();
|
||||
globals.fs.file('.packages').createSync();
|
||||
globals.fs.directory('android').createSync();
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
|
||||
final AndroidDevice device = setUpAndroidDevice(fileSystem: fileSystem);
|
||||
|
||||
expect(device.isSupportedForProject(flutterProject), true);
|
||||
expect(AndroidDevice('test').isSupportedForProject(flutterProject), true);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testWithoutContext('isSupportedForProject is false with no host app and no module', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
fileSystem.file('pubspec.yaml').createSync();
|
||||
fileSystem.file('.packages').createSync();
|
||||
final FlutterProject flutterProject = FlutterProjectFactory(
|
||||
fileSystem: fileSystem,
|
||||
logger: BufferLogger.test(),
|
||||
).fromDirectory(fileSystem.currentDirectory);
|
||||
testUsingContext('isSupportedForProject is false with no host app and no module', () async {
|
||||
globals.fs.file('pubspec.yaml').createSync();
|
||||
globals.fs.file('.packages').createSync();
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
|
||||
final AndroidDevice device = setUpAndroidDevice(fileSystem: fileSystem);
|
||||
|
||||
expect(device.isSupportedForProject(flutterProject), false);
|
||||
expect(AndroidDevice('test').isSupportedForProject(flutterProject), false);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice returns correct ID for responsive emulator', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', 'emulator-5555', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [goldfish]'
|
||||
)
|
||||
]),
|
||||
id: 'emulator-5555',
|
||||
androidConsoleSocketFactory: (String host, int port) async =>
|
||||
MockWorkingAndroidConsoleSocket('dummyEmulatorId'),
|
||||
);
|
||||
group('emulatorId', () {
|
||||
final ProcessManager mockProcessManager = MockProcessManager();
|
||||
const String dummyEmulatorId = 'dummyEmulatorId';
|
||||
final Future<Socket> Function(String host, int port) unresponsiveSocket =
|
||||
(String host, int port) async => MockUnresponsiveAndroidConsoleSocket();
|
||||
final Future<Socket> Function(String host, int port) disconnectingSocket =
|
||||
(String host, int port) async => MockDisconnectingAndroidConsoleSocket();
|
||||
final Future<Socket> Function(String host, int port) workingSocket =
|
||||
(String host, int port) async => MockWorkingAndroidConsoleSocket(dummyEmulatorId);
|
||||
String hardware;
|
||||
bool socketWasCreated;
|
||||
|
||||
expect(await device.emulatorId, equals('dummyEmulatorId'));
|
||||
});
|
||||
setUp(() {
|
||||
hardware = 'goldfish'; // Known emulator
|
||||
socketWasCreated = false;
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('getprop')),
|
||||
stderrEncoding: anyNamed('stderrEncoding'),
|
||||
stdoutEncoding: anyNamed('stdoutEncoding'),
|
||||
)).thenAnswer((_) {
|
||||
final StringBuffer buf = StringBuffer()
|
||||
..writeln('[ro.hardware]: [$hardware]');
|
||||
final ProcessResult result = ProcessResult(1, 0, buf.toString(), '');
|
||||
return Future<ProcessResult>.value(result);
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice does not create socket for non-emulator devices', () async {
|
||||
bool socketWasCreated = false;
|
||||
testUsingContext('returns correct ID for responsive emulator', () async {
|
||||
final AndroidDevice device = AndroidDevice('emulator-5555');
|
||||
expect(await device.emulatorId, equals(dummyEmulatorId));
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidConsoleSocketFactory: () => workingSocket,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
// Still use an emulator-looking ID so we can be sure the failure is due
|
||||
// to the isLocalEmulator field and not because the ID doesn't contain a
|
||||
// port.
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
id: 'emulator-5555',
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', 'emulator-5555', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [samsungexynos7420]'
|
||||
)
|
||||
]),
|
||||
androidConsoleSocketFactory: (String host, int port) async {
|
||||
testUsingContext('does not create socket for non-emulator devices', () async {
|
||||
hardware = 'samsungexynos7420';
|
||||
|
||||
// Still use an emulator-looking ID so we can be sure the failure is due
|
||||
// to the isLocalEmulator field and not because the ID doesn't contain a
|
||||
// port.
|
||||
final AndroidDevice device = AndroidDevice('emulator-5555');
|
||||
expect(await device.emulatorId, isNull);
|
||||
expect(socketWasCreated, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidConsoleSocketFactory: () => (String host, int port) async {
|
||||
socketWasCreated = true;
|
||||
throw 'Socket was created for non-emulator';
|
||||
}
|
||||
);
|
||||
},
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
expect(await device.emulatorId, isNull);
|
||||
expect(socketWasCreated, isFalse);
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice does not create socket for emulators with no port', () async {
|
||||
bool socketWasCreated = false;
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [goldfish]'
|
||||
)
|
||||
]),
|
||||
androidConsoleSocketFactory: (String host, int port) async {
|
||||
testUsingContext('does not create socket for emulators with no port', () async {
|
||||
final AndroidDevice device = AndroidDevice('emulator-noport');
|
||||
expect(await device.emulatorId, isNull);
|
||||
expect(socketWasCreated, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidConsoleSocketFactory: () => (String host, int port) async {
|
||||
socketWasCreated = true;
|
||||
throw 'Socket was created for emulator without port in ID';
|
||||
},
|
||||
);
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
expect(await device.emulatorId, isNull);
|
||||
expect(socketWasCreated, isFalse);
|
||||
testUsingContext('returns null for connection error', () async {
|
||||
final AndroidDevice device = AndroidDevice('emulator-5555');
|
||||
expect(await device.emulatorId, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidConsoleSocketFactory: () {
|
||||
return (String host, int port) => throw Exception('Fake socket error');
|
||||
},
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('returns null for unresponsive device', () async {
|
||||
final AndroidDevice device = AndroidDevice('emulator-5555');
|
||||
expect(await device.emulatorId, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidConsoleSocketFactory: () => unresponsiveSocket,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('returns null on early disconnect', () async {
|
||||
final AndroidDevice device = AndroidDevice('emulator-5555');
|
||||
expect(await device.emulatorId, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidConsoleSocketFactory: () => disconnectingSocket,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice.emulatorId is null for connection error', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [goldfish]'
|
||||
)
|
||||
]),
|
||||
androidConsoleSocketFactory: (String host, int port) => throw Exception('Fake socket error'),
|
||||
);
|
||||
group('logcat', () {
|
||||
final ProcessManager mockProcessManager = MockProcessManager();
|
||||
final AndroidDevice device = AndroidDevice('1234');
|
||||
|
||||
expect(await device.emulatorId, isNull);
|
||||
testUsingContext('lastLogcatTimestamp returns null if shell command failed', () async {
|
||||
when(mockProcessManager.runSync(argThat(contains('logcat'))))
|
||||
.thenReturn(ProcessResult(0, 1, '', ''));
|
||||
expect(device.lastLogcatTimestamp, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('AdbLogReaders for past+future and future logs are not the same', () async {
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('getprop')),
|
||||
stderrEncoding: anyNamed('stderrEncoding'),
|
||||
stdoutEncoding: anyNamed('stdoutEncoding'),
|
||||
)).thenAnswer((_) {
|
||||
final StringBuffer buf = StringBuffer()
|
||||
..writeln('[ro.build.version.sdk]: [23]');
|
||||
final ProcessResult result = ProcessResult(1, exitCode, buf.toString(), '');
|
||||
return Future<ProcessResult>.value(result);
|
||||
});
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('shell')),
|
||||
stderrEncoding: anyNamed('stderrEncoding'),
|
||||
stdoutEncoding: anyNamed('stdoutEncoding'),
|
||||
)).thenAnswer((_) {
|
||||
final StringBuffer buf = StringBuffer()
|
||||
..writeln('11-27 15:39:04.506');
|
||||
final ProcessResult result = ProcessResult(1, exitCode, buf.toString(), '');
|
||||
return Future<ProcessResult>.value(result);
|
||||
});
|
||||
final DeviceLogReader pastLogReader = await device.getLogReader(includePastLogs: true);
|
||||
final DeviceLogReader defaultLogReader = await device.getLogReader();
|
||||
expect(pastLogReader, isNot(equals(defaultLogReader)));
|
||||
// Getting again is cached.
|
||||
expect(pastLogReader, equals(await device.getLogReader(includePastLogs: true)));
|
||||
expect(defaultLogReader, equals(await device.getLogReader()));
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice.emulatorId is null for unresponsive device', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [goldfish]'
|
||||
)
|
||||
]),
|
||||
androidConsoleSocketFactory: (String host, int port) async =>
|
||||
MockUnresponsiveAndroidConsoleSocket(),
|
||||
);
|
||||
|
||||
expect(await device.emulatorId, isNull);
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice.emulatorId is null on early disconnect', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.hardware]: [goldfish]'
|
||||
)
|
||||
]),
|
||||
androidConsoleSocketFactory: (String host, int port) async =>
|
||||
MockDisconnectingAndroidConsoleSocket()
|
||||
);
|
||||
|
||||
expect(await device.emulatorId, isNull);
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice lastLogcatTimestamp returns null if shell command failed', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', '-x', 'logcat', '-v', 'time', '-t', '1'],
|
||||
exitCode: 1,
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
expect(device.lastLogcatTimestamp, isNull);
|
||||
});
|
||||
|
||||
testWithoutContext('AndroidDevice AdbLogReaders for past+future and future logs are not the same', () async {
|
||||
final AndroidDevice device = setUpAndroidDevice(
|
||||
processManager: FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.build.version.sdk]: [23]',
|
||||
exitCode: 1,
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'logcat', '-v', 'time', '-s', 'flutter'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'logcat', '-v', 'time'],
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
final DeviceLogReader pastLogReader = await device.getLogReader(includePastLogs: true);
|
||||
final DeviceLogReader defaultLogReader = await device.getLogReader();
|
||||
expect(pastLogReader, isNot(equals(defaultLogReader)));
|
||||
|
||||
// Getting again is cached.
|
||||
expect(pastLogReader, equals(await device.getLogReader(includePastLogs: true)));
|
||||
expect(defaultLogReader, equals(await device.getLogReader()));
|
||||
});
|
||||
|
||||
testWithoutContext('Can parse adb shell dumpsys info', () {
|
||||
test('Can parse adb shell dumpsys info', () {
|
||||
const String exampleOutput = r'''
|
||||
Applications Memory Usage (in Kilobytes):
|
||||
Uptime: 441088659 Realtime: 521464097
|
||||
@@ -442,28 +517,6 @@ Uptime: 441088659 Realtime: 521464097
|
||||
});
|
||||
}
|
||||
|
||||
AndroidDevice setUpAndroidDevice({
|
||||
String id,
|
||||
AndroidSdk androidSdk,
|
||||
FileSystem fileSystem,
|
||||
ProcessManager processManager,
|
||||
Platform platform,
|
||||
AndroidConsoleSocketFactory androidConsoleSocketFactory = kAndroidConsoleSocketFactory,
|
||||
}) {
|
||||
androidSdk ??= MockAndroidSdk();
|
||||
when(androidSdk.adbPath).thenReturn('adb');
|
||||
return AndroidDevice(id ?? '1234',
|
||||
logger: BufferLogger.test(),
|
||||
platform: platform ?? FakePlatform(operatingSystem: 'linux'),
|
||||
androidSdk: androidSdk,
|
||||
fileSystem: fileSystem ?? MemoryFileSystem.test(),
|
||||
processManager: processManager ?? FakeProcessManager.any(),
|
||||
androidConsoleSocketFactory: androidConsoleSocketFactory,
|
||||
timeoutConfiguration: const TimeoutConfiguration(),
|
||||
);
|
||||
}
|
||||
|
||||
class MockAndroidSdk extends Mock implements AndroidSdk {}
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
|
||||
const String kAdbShellGetprop = '''
|
||||
@@ -684,3 +737,10 @@ class MockDisconnectingAndroidConsoleSocket extends Mock implements Socket {
|
||||
_controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
class AndroidPackageTest extends ApplicationPackage {
|
||||
AndroidPackageTest() : super(id: 'app-id');
|
||||
|
||||
@override
|
||||
String get name => 'app-package';
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:flutter_tools/src/android/android_sdk.dart'
|
||||
import 'package:flutter_tools/src/android/android_emulator.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
|
||||
@@ -130,7 +131,7 @@ void main() {
|
||||
|
||||
testUsingContext('succeeds', () async {
|
||||
final AndroidEmulator emulator = AndroidEmulator(emulatorID);
|
||||
expect(getEmulatorPath(mockSdk), mockSdk.emulatorPath);
|
||||
expect(getEmulatorPath(globals.androidSdk), mockSdk.emulatorPath);
|
||||
final Completer<void> completer = Completer<void>();
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
unawaited(emulator.launch().whenComplete(completer.complete));
|
||||
|
||||
@@ -7,8 +7,7 @@ import 'package:flutter_tools/src/android/android_device.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/application_package.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
@@ -29,50 +28,66 @@ const FakeCommand kStoreShaCommand = FakeCommand(
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWithoutContext('Cannot install app on API level below 16', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
testUsingContext('Cannot install app on API level below 16', () async {
|
||||
final File apk = globals.fs.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
id: 'app',
|
||||
versionCode: 22,
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = AndroidDevice('1234');
|
||||
when(globals.androidSdk.adbPath).thenReturn('adb');
|
||||
final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), false);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
|
||||
kAdbVersionCommand,
|
||||
kAdbStartServerCommand,
|
||||
const FakeCommand(
|
||||
command: <String>['adb', '-s', '1234', 'shell', 'getprop'],
|
||||
stdout: '[ro.build.version.sdk]: [11]',
|
||||
),
|
||||
]);
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk')..createSync();
|
||||
]),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
AndroidSdk: () => MockAndroidSdk(),
|
||||
});
|
||||
|
||||
testUsingContext('Cannot install app if APK file is missing', () async {
|
||||
final File apk = globals.fs.file('app.apk');
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
id: 'app',
|
||||
versionCode: 22,
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
);
|
||||
final AndroidDevice androidDevice = AndroidDevice('1234');
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
AndroidSdk: () => MockAndroidSdk(),
|
||||
});
|
||||
|
||||
testUsingContext('Can install app on API level 16 or greater', () async {
|
||||
final File apk = globals.fs.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
id: 'app',
|
||||
versionCode: 22,
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = AndroidDevice('1234');
|
||||
when(globals.androidSdk.adbPath).thenReturn('adb');
|
||||
final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testWithoutContext('Cannot install app if APK file is missing', () async {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk');
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
id: 'app',
|
||||
versionCode: 22,
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
);
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), false);
|
||||
});
|
||||
|
||||
testWithoutContext('Can install app on API level 16 or greater', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
|
||||
kAdbVersionCommand,
|
||||
kAdbStartServerCommand,
|
||||
const FakeCommand(
|
||||
@@ -81,26 +96,27 @@ void main() {
|
||||
),
|
||||
kInstallCommand,
|
||||
kStoreShaCommand,
|
||||
]);
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk')..createSync();
|
||||
]),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
AndroidSdk: () => MockAndroidSdk(),
|
||||
});
|
||||
|
||||
testUsingContext('Defaults to API level 16 if adb returns a null response', () async {
|
||||
final File apk = globals.fs.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
id: 'app',
|
||||
versionCode: 22,
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
);
|
||||
final AndroidDevice androidDevice = AndroidDevice('1234');
|
||||
when(globals.androidSdk.adbPath).thenReturn('adb');
|
||||
final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
testWithoutContext('Defaults to API level 16 if adb returns a null response', () async {
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
|
||||
kAdbVersionCommand,
|
||||
kAdbStartServerCommand,
|
||||
const FakeCommand(
|
||||
@@ -108,39 +124,10 @@ void main() {
|
||||
),
|
||||
kInstallCommand,
|
||||
kStoreShaCommand,
|
||||
]);
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
final File apk = fileSystem.file('app.apk')..createSync();
|
||||
final AndroidApk androidApk = AndroidApk(
|
||||
file: apk,
|
||||
id: 'app',
|
||||
versionCode: 22,
|
||||
launchActivity: 'Main',
|
||||
);
|
||||
final AndroidDevice androidDevice = setUpAndroidDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
);
|
||||
|
||||
expect(await androidDevice.installApp(androidApk), true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
]),
|
||||
FileSystem: () => MemoryFileSystem.test(),
|
||||
AndroidSdk: () => MockAndroidSdk(),
|
||||
});
|
||||
}
|
||||
|
||||
AndroidDevice setUpAndroidDevice({
|
||||
AndroidSdk androidSdk,
|
||||
FileSystem fileSystem,
|
||||
ProcessManager processManager,
|
||||
}) {
|
||||
androidSdk ??= MockAndroidSdk();
|
||||
when(androidSdk.adbPath).thenReturn('adb');
|
||||
return AndroidDevice('1234',
|
||||
logger: BufferLogger.test(),
|
||||
platform: FakePlatform(operatingSystem: 'linux'),
|
||||
androidSdk: androidSdk,
|
||||
fileSystem: fileSystem ?? MemoryFileSystem.test(),
|
||||
processManager: processManager ?? FakeProcessManager.any(),
|
||||
);
|
||||
}
|
||||
|
||||
class MockAndroidSdk extends Mock implements AndroidSdk {}
|
||||
|
||||
Reference in New Issue
Block a user