From 5e33ecee467e800df64674beb94907d8ddbfa01e Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 19 May 2020 13:06:14 -0700 Subject: [PATCH] Revert "[flutter_tools] remove globals/context for android testing (#57445)" (#57611) This reverts commit 602d8baf34ec9d875fd6e74e99699bf62885d9f3. --- .../lib/src/android/android_console.dart | 8 +- .../lib/src/android/android_device.dart | 201 +++--- .../src/android/android_device_discovery.dart | 14 - .../android_device_discovery_test.dart | 32 +- .../android/android_device_start_test.dart | 47 +- .../android/android_device_stop_test.dart | 16 +- .../android/android_device_test.dart | 678 ++++++++++-------- .../android/android_emulator_test.dart | 3 +- .../android/android_install_test.dart | 135 ++-- 9 files changed, 548 insertions(+), 586 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_console.dart b/packages/flutter_tools/lib/src/android/android_console.dart index a58c853480..fa8e2cfacf 100644 --- a/packages/flutter_tools/lib/src/android/android_console.dart +++ b/packages/flutter_tools/lib/src/android/android_console.dart @@ -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 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() ?? _kAndroidConsoleSocketFactory; + typedef AndroidConsoleSocketFactory = Future Function(String host, int port); /// Creates a console connection to an Android emulator that can be used to run diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index ea5cf1fafc..aa685a6d80 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -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 _kKnownHardware = { '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 _properties; bool _isLocalEmulator; - TargetPlatform _applicationPlatform; + TargetPlatform _platform; Future _getProperty(String name) async { if (_properties == null) { _properties = {}; final List propCommand = adbCommandForDevice(['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 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 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 adbCommandForDevice(List args) { - return [getAdbPath(_androidSdk), '-s', id, ...args]; + return [getAdbPath(globals.androidSdk), '-s', id, ...args]; } String runAdbCheckedSync( @@ -250,13 +226,13 @@ class AndroidDevice extends Device { bool allowReentrantFlutter = false, Map 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 _checkForSupportedAdbVersion() async { - if (_androidSdk == null) { + if (globals.androidSdk == null) { return false; } try { - final RunResult adbVersion = await _processUtils.run( - [getAdbPath(_androidSdk), 'version'], + final RunResult adbVersion = await processUtils.run( + [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( - [getAdbPath(_androidSdk), 'start-server'], + await processUtils.run( + [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 _getDeviceApkSha1(AndroidApk apk) async { - final RunResult result = await _processUtils.run( + final RunResult result = await processUtils.run( adbCommandForDevice(['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(['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 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(['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(['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.value(false); } final List command = adbCommandForDevice(['shell', 'am', 'force-stop', app.id]); - return _processUtils.stream(command).then( - (int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode, _platform)); + return processUtils.stream(command).then( + (int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode)); } @override Future queryMemoryInfo() async { - final RunResult runResult = await _processUtils.run(adbCommandForDevice([ + final RunResult runResult = await processUtils.run(adbCommandForDevice([ 'shell', 'dumpsys', 'meminfo', @@ -700,7 +673,7 @@ class AndroidDevice extends Device { @override void clearLogs() { - _processUtils.runSync(adbCommandForDevice(['logcat', '-c'])); + processUtils.runSync(adbCommandForDevice(['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 takeScreenshot(File outputFile) async { const String remotePath = '/data/local/tmp/flutter_screenshot.png'; await runAdbCheckedAsync(['shell', 'screencap', '-p', remotePath]); - await _processUtils.run( + await processUtils.run( adbCommandForDevice(['pull', remotePath, outputFile.path]), throwOnError: true, ); diff --git a/packages/flutter_tools/lib/src/android/android_device_discovery.dart b/packages/flutter_tools/lib/src/android/android_device_discovery.dart index 74571c018b..7e634f3af0 100644 --- a/packages/flutter_tools/lib/src/android/android_device_discovery.dart +++ b/packages/flutter_tools/lib/src/android/android_device_discovery.dart @@ -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 devices, List 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 { diff --git a/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart index 3c2393cce7..ad8a36c42f 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart @@ -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'); diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart index 4172cddbe3..2b699ab38e 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart @@ -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: { + 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: { + 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: { + AndroidSdk: () => androidSdk, + FileSystem: () => fileSystem, + ProcessManager: () => processManager, }); } diff --git a/packages/flutter_tools/test/general.shard/android/android_device_stop_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_stop_test.dart index b25a2cf47b..c7fc8db9b1 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_stop_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_stop_test.dart @@ -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 {} diff --git a/packages/flutter_tools/test/general.shard/android/android_device_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_test.dart index e5cffb6943..b3d0987e05 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_test.dart @@ -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 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 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 commands = [ - const FakeCommand( - command: ['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.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: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + operatingSystem: 'windows', + environment: { + '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: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + operatingSystem: 'windows', + environment: { + 'ANDROID_HOME': '/', + }, + ), + }); + + testUsingContext('heapCorruptionExitCodeOnLinux', () async { + exitCode = -1073740940; + final AndroidDevice device = AndroidDevice('test'); + expect(await device.isLocalEmulator, false); + }, overrides: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + operatingSystem: 'linux', + environment: { + 'ANDROID_HOME': '/', + }, + ), + }); + + testUsingContext('noErrorOnLinux', () async { + exitCode = 0; + final AndroidDevice device = AndroidDevice('test'); + expect(await device.isLocalEmulator, true); + }, overrides: { + ProcessManager: () => mockProcessManager, + Platform: () => FakePlatform( + operatingSystem: 'linux', + environment: { + 'ANDROID_HOME': '/', + }, + ), + }); }); - testWithoutContext('AndroidDevice can detect TargetPlatform from property ' - 'abi and abiList', () async { - // The format is [ABI, ABI list]: expected target platform. - final Map, TargetPlatform> values = , TargetPlatform>{ - ['x86_64', 'unknown']: TargetPlatform.android_x64, - ['x86', 'unknown']: TargetPlatform.android_x86, - // The default ABI is arm32 - ['???', 'unknown']: TargetPlatform.android_arm, - ['arm64-v8a', 'arm64-v8a,']: TargetPlatform.android_arm64, - // The Kindle Fire runs 32 bit apps on 64 bit hardware. - ['arm64-v8a', 'arm']: TargetPlatform.android_arm, - }; + group('ABI detection', () { + ProcessManager mockProcessManager; + String cpu; + String abilist; - for (final MapEntry, TargetPlatform> entry in values.entries) { - final AndroidDevice device = setUpAndroidDevice( - processManager: FakeProcessManager.list([ - FakeCommand( - command: const ['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.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: { + ProcessManager: () => mockProcessManager + }); + + testUsingContext('detects x86', () async { + cpu = 'x86'; + final AndroidDevice device = AndroidDevice('test'); + + expect(await device.targetPlatform, TargetPlatform.android_x86); + }, overrides: { + ProcessManager: () => mockProcessManager + }); + + testUsingContext('unknown device defaults to 32bit arm', () async { + cpu = '???'; + final AndroidDevice device = AndroidDevice('test'); + + expect(await device.targetPlatform, TargetPlatform.android_arm); + }, overrides: { + 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: { + 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: { + ProcessManager: () => mockProcessManager + }); }); - testWithoutContext('AndroidDevice can detect local emulator for known types', () async { - final Set knownPhyiscal = { - 'qcom', - 'samsungexynos7420', - 'samsungexynos7580', - 'samsungexynos7870', - 'samsungexynos7880', - 'samsungexynos8890', - 'samsungexynos8895', - 'samsungexynos9810', - 'samsungexynos7570', - }; - final Set knownEmulator = { - '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( - command: const [ - '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.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: { + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('knownPhysical Samsung SM G570M', () async { + hardware = 'samsungexynos7570'; + final AndroidDevice device = AndroidDevice('test'); + expect(await device.isLocalEmulator, false); + }, overrides: { + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('knownEmulator', () async { + hardware = 'goldfish'; + final AndroidDevice device = AndroidDevice('test'); + expect(await device.isLocalEmulator, true); + expect(await device.supportsHardwareRendering, true); + }, overrides: { + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('unknownPhysical', () async { + buildCharacteristics = 'att'; + final AndroidDevice device = AndroidDevice('test'); + expect(await device.isLocalEmulator, false); + }, overrides: { + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('unknownEmulator', () async { + buildCharacteristics = 'att,emulator'; + final AndroidDevice device = AndroidDevice('test'); + expect(await device.isLocalEmulator, true); + expect(await device.supportsHardwareRendering, true); + }, overrides: { + ProcessManager: () => mockProcessManager, + }); }); - testWithoutContext('AndroidDevice can detect unknown hardware', () async { - final AndroidDevice device = setUpAndroidDevice( - processManager: FakeProcessManager.list([ - const FakeCommand( - command: [ - '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([ - const FakeCommand( - command: [ - '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: { + 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: { + 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: { + FileSystem: () => MemoryFileSystem(), + ProcessManager: () => FakeProcessManager.any(), }); - testWithoutContext('AndroidDevice returns correct ID for responsive emulator', () async { - final AndroidDevice device = setUpAndroidDevice( - processManager: FakeProcessManager.list([ - const FakeCommand( - command: ['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 Function(String host, int port) unresponsiveSocket = + (String host, int port) async => MockUnresponsiveAndroidConsoleSocket(); + final Future Function(String host, int port) disconnectingSocket = + (String host, int port) async => MockDisconnectingAndroidConsoleSocket(); + final Future 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.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: { + 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([ - const FakeCommand( - command: ['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: { + 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([ - const FakeCommand( - command: ['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: { + 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: { + 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: { + AndroidConsoleSocketFactory: () => unresponsiveSocket, + ProcessManager: () => mockProcessManager, + }); + + testUsingContext('returns null on early disconnect', () async { + final AndroidDevice device = AndroidDevice('emulator-5555'); + expect(await device.emulatorId, isNull); + }, overrides: { + AndroidConsoleSocketFactory: () => disconnectingSocket, + ProcessManager: () => mockProcessManager, + }); }); - testWithoutContext('AndroidDevice.emulatorId is null for connection error', () async { - final AndroidDevice device = setUpAndroidDevice( - processManager: FakeProcessManager.list([ - const FakeCommand( - command: ['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: { + 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.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.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: { + ProcessManager: () => mockProcessManager, + }); }); - testWithoutContext('AndroidDevice.emulatorId is null for unresponsive device', () async { - final AndroidDevice device = setUpAndroidDevice( - processManager: FakeProcessManager.list([ - const FakeCommand( - command: ['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([ - const FakeCommand( - command: ['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([ - const FakeCommand( - command: ['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([ - const FakeCommand( - command: ['adb', '-s', '1234', 'shell', 'getprop'], - stdout: '[ro.build.version.sdk]: [23]', - exitCode: 1, - ), - const FakeCommand( - command: ['adb', '-s', '1234', 'logcat', '-v', 'time', '-s', 'flutter'], - ), - const FakeCommand( - command: ['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'; +} diff --git a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart index 959b5c5e75..883065425c 100644 --- a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart @@ -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 completer = Completer(); FakeAsync().run((FakeAsync time) { unawaited(emulator.launch().whenComplete(completer.complete)); diff --git a/packages/flutter_tools/test/general.shard/android/android_install_test.dart b/packages/flutter_tools/test/general.shard/android/android_install_test.dart index f1c61b6092..75688c2929 100644 --- a/packages/flutter_tools/test/general.shard/android/android_install_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_install_test.dart @@ -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([ + 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: { + ProcessManager: () => FakeProcessManager.list([ kAdbVersionCommand, kAdbStartServerCommand, const FakeCommand( command: ['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: { + ProcessManager: () => FakeProcessManager.list([]), + 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([ + }, overrides: { + ProcessManager: () => FakeProcessManager.list([ 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([ + }, overrides: { + ProcessManager: () => FakeProcessManager.list([ 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 {}