Unverified Commit 6180a4c1 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] fix documentation, globals, and todos in the android codebase (#66980)

Cleans up some undocumented classes and re-organizes the AndroidDevices class to avoid the need for the static testing only member. Adds a script for tracking globals.
parent 5339cba6
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
final RegExp _whitespaceRegex = RegExp(r'\s+'); final RegExp _whitespace = RegExp(r'\s+');
/// Convert adb device names into more human readable descriptions.
String cleanAdbDeviceName(String name) { String cleanAdbDeviceName(String name) {
// Some emulators use `___` in the name as separators. // Some emulators use `___` in the name as separators.
name = name.replaceAll('___', ', '); name = name.replaceAll('___', ', ');
...@@ -11,7 +12,7 @@ String cleanAdbDeviceName(String name) { ...@@ -11,7 +12,7 @@ String cleanAdbDeviceName(String name) {
// Convert `Nexus_7` / `Nexus_5X` style names to `Nexus 7` ones. // Convert `Nexus_7` / `Nexus_5X` style names to `Nexus 7` ones.
name = name.replaceAll('_', ' '); name = name.replaceAll('_', ' ');
name = name.replaceAll(_whitespaceRegex, ' ').trim(); name = name.replaceAll(_whitespace, ' ').trim();
return name; return name;
} }
...@@ -26,33 +26,32 @@ import 'android.dart'; ...@@ -26,33 +26,32 @@ import 'android.dart';
import 'android_console.dart'; import 'android_console.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
// TODO(jonahwilliams): update google3 client after roll to remove export. /// Whether the [AndroidDevice] is believed to be a physical device or an emulator.
export 'android_device_discovery.dart'; enum HardwareType { emulator, physical }
enum _HardwareType { emulator, physical }
/// Map to help our `isLocalEmulator` detection. /// Map to help our `isLocalEmulator` detection.
const Map<String, _HardwareType> _kKnownHardware = <String, _HardwareType>{ ///
'goldfish': _HardwareType.emulator, /// See [AndroidDevice] for more explanation of why this is needed.
'qcom': _HardwareType.physical, const Map<String, HardwareType> kKnownHardware = <String, HardwareType>{
'ranchu': _HardwareType.emulator, 'goldfish': HardwareType.emulator,
'samsungexynos7420': _HardwareType.physical, 'qcom': HardwareType.physical,
'samsungexynos7580': _HardwareType.physical, 'ranchu': HardwareType.emulator,
'samsungexynos7870': _HardwareType.physical, 'samsungexynos7420': HardwareType.physical,
'samsungexynos7880': _HardwareType.physical, 'samsungexynos7580': HardwareType.physical,
'samsungexynos8890': _HardwareType.physical, 'samsungexynos7870': HardwareType.physical,
'samsungexynos8895': _HardwareType.physical, 'samsungexynos7880': HardwareType.physical,
'samsungexynos9810': _HardwareType.physical, 'samsungexynos8890': HardwareType.physical,
'samsungexynos7570': _HardwareType.physical, 'samsungexynos8895': HardwareType.physical,
'samsungexynos9810': HardwareType.physical,
'samsungexynos7570': HardwareType.physical,
}; };
bool allowHeapCorruptionOnWindows(int exitCode, Platform platform) { /// A physical Android device or emulator.
// In platform tools 29.0.0 adb.exe seems to be ending with this heap ///
// corruption error code on seemingly successful termination. /// While [isEmulator] attempts to distinguish between the device categories,
// So we ignore this error on Windows. /// this is a best effort process and not a guarantee; certain phyiscal devices
return exitCode == -1073740940 && platform.isWindows; /// identify as emulators. These device identifiers may be added to the [kKnownHardware]
} /// map to specify that they are actually physical devices.
class AndroidDevice extends Device { class AndroidDevice extends Device {
AndroidDevice( AndroidDevice(
String id, { String id, {
...@@ -113,7 +112,7 @@ class AndroidDevice extends Device { ...@@ -113,7 +112,7 @@ class AndroidDevice extends Device {
stdoutEncoding: latin1, stdoutEncoding: latin1,
stderrEncoding: latin1, stderrEncoding: latin1,
); );
if (result.exitCode == 0 || allowHeapCorruptionOnWindows(result.exitCode, _platform)) { if (result.exitCode == 0 || _allowHeapCorruptionOnWindows(result.exitCode, _platform)) {
_properties = parseAdbDeviceProperties(result.stdout as String); _properties = parseAdbDeviceProperties(result.stdout as String);
} else { } else {
_logger.printError('Error ${result.exitCode} retrieving device properties for $name:'); _logger.printError('Error ${result.exitCode} retrieving device properties for $name:');
...@@ -132,9 +131,9 @@ class AndroidDevice extends Device { ...@@ -132,9 +131,9 @@ class AndroidDevice extends Device {
if (_isLocalEmulator == null) { if (_isLocalEmulator == null) {
final String hardware = await _getProperty('ro.hardware'); final String hardware = await _getProperty('ro.hardware');
_logger.printTrace('ro.hardware = $hardware'); _logger.printTrace('ro.hardware = $hardware');
if (_kKnownHardware.containsKey(hardware)) { if (kKnownHardware.containsKey(hardware)) {
// Look for known hardware models. // Look for known hardware models.
_isLocalEmulator = _kKnownHardware[hardware] == _HardwareType.emulator; _isLocalEmulator = kKnownHardware[hardware] == HardwareType.emulator;
} else { } else {
// Fall back to a best-effort heuristic-based approach. // Fall back to a best-effort heuristic-based approach.
final String characteristics = await _getProperty('ro.build.characteristics'); final String characteristics = await _getProperty('ro.build.characteristics');
...@@ -242,8 +241,7 @@ class AndroidDevice extends Device { ...@@ -242,8 +241,7 @@ class AndroidDevice extends Device {
} }
@override @override
Future<String> get sdkNameAndVersion async => Future<String> get sdkNameAndVersion async => 'Android ${await _sdkVersion} (API ${await apiVersion})';
'Android ${await _sdkVersion} (API ${await apiVersion})';
Future<String> get _sdkVersion => _getProperty('ro.build.version.release'); Future<String> get _sdkVersion => _getProperty('ro.build.version.release');
...@@ -258,22 +256,6 @@ class AndroidDevice extends Device { ...@@ -258,22 +256,6 @@ class AndroidDevice extends Device {
return <String>[_androidSdk.adbPath, '-s', id, ...args]; return <String>[_androidSdk.adbPath, '-s', id, ...args];
} }
String runAdbCheckedSync(
List<String> params, {
String workingDirectory,
bool allowReentrantFlutter = false,
Map<String, String> environment,
}) {
return _processUtils.runSync(
adbCommandForDevice(params),
throwOnError: true,
workingDirectory: workingDirectory,
allowReentrantFlutter: allowReentrantFlutter,
environment: environment,
allowedFailures: (int value) => allowHeapCorruptionOnWindows(value, _platform),
).stdout.trim();
}
Future<RunResult> runAdbCheckedAsync( Future<RunResult> runAdbCheckedAsync(
List<String> params, { List<String> params, {
String workingDirectory, String workingDirectory,
...@@ -284,7 +266,7 @@ class AndroidDevice extends Device { ...@@ -284,7 +266,7 @@ class AndroidDevice extends Device {
throwOnError: true, throwOnError: true,
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
allowReentrantFlutter: allowReentrantFlutter, allowReentrantFlutter: allowReentrantFlutter,
allowedFailures: (int value) => allowHeapCorruptionOnWindows(value, _platform), allowedFailures: (int value) => _allowHeapCorruptionOnWindows(value, _platform),
); );
} }
...@@ -738,7 +720,7 @@ class AndroidDevice extends Device { ...@@ -738,7 +720,7 @@ class AndroidDevice extends Device {
app.id, app.id,
]); ]);
return _processUtils.stream(command).then<bool>( return _processUtils.stream(command).then<bool>(
(int exitCode) => exitCode == 0 || allowHeapCorruptionOnWindows(exitCode, _platform)); (int exitCode) => exitCode == 0 || _allowHeapCorruptionOnWindows(exitCode, _platform));
} }
@override @override
...@@ -794,17 +776,18 @@ class AndroidDevice extends Device { ...@@ -794,17 +776,18 @@ class AndroidDevice extends Device {
/// Return the most recent timestamp in the Android log or [null] if there is /// Return the most recent timestamp in the Android log or [null] if there is
/// no available timestamp. The format can be passed to logcat's -T option. /// no available timestamp. The format can be passed to logcat's -T option.
String get lastLogcatTimestamp { @visibleForTesting
String output; Future<String> lastLogcatTimestamp() async {
RunResult output;
try { try {
output = runAdbCheckedSync(<String>[ output = await runAdbCheckedAsync(<String>[
'shell', '-x', 'logcat', '-v', 'time', '-t', '1' 'shell', '-x', 'logcat', '-v', 'time', '-t', '1'
]); ]);
} on Exception catch (error) { } on Exception catch (error) {
_logger.printError('Failed to extract the most recent timestamp from the Android log: $error.'); _logger.printError('Failed to extract the most recent timestamp from the Android log: $error.');
return null; return null;
} }
final Match timeMatch = _timeRegExp.firstMatch(output); final Match timeMatch = _timeRegExp.firstMatch(output.stdout);
return timeMatch?.group(0); return timeMatch?.group(0);
} }
...@@ -1012,11 +995,9 @@ class AdbLogReader extends DeviceLogReader { ...@@ -1012,11 +995,9 @@ class AdbLogReader extends DeviceLogReader {
/// Create a new [AdbLogReader] from an [AndroidDevice] instance. /// Create a new [AdbLogReader] from an [AndroidDevice] instance.
static Future<AdbLogReader> createLogReader( static Future<AdbLogReader> createLogReader(
AndroidDevice device, AndroidDevice device,
ProcessManager processManager, ProcessManager processManager, {
{
bool includePastLogs = false, bool includePastLogs = false,
} }) async {
) async {
// logcat -T is not supported on Android releases before Lollipop. // logcat -T is not supported on Android releases before Lollipop.
const int kLollipopVersionCode = 21; const int kLollipopVersionCode = 21;
final int apiVersion = (String v) { final int apiVersion = (String v) {
...@@ -1034,17 +1015,20 @@ class AdbLogReader extends DeviceLogReader { ...@@ -1034,17 +1015,20 @@ class AdbLogReader extends DeviceLogReader {
'logcat', 'logcat',
'-v', '-v',
'time', 'time',
// If we include logs from the past, filter for 'flutter' logs only. ];
if (includePastLogs) ...<String>[
'-s', // If past logs are included then filter for 'flutter' logs only.
'flutter', if (includePastLogs) {
] else if (apiVersion != null && apiVersion >= kLollipopVersionCode) ...<String>[ args.addAll(<String>['-s', 'flutter']);
} else if (apiVersion != null && apiVersion >= kLollipopVersionCode) {
// Otherwise, filter for logs appearing past the present. // Otherwise, filter for logs appearing past the present.
// '-T 0` means the timestamp of the logcat command invocation. // '-T 0` means the timestamp of the logcat command invocation.
final String lastLogcatTimestamp = await device.lastLogcatTimestamp();
args.addAll(<String>[
'-T', '-T',
if (device.lastLogcatTimestamp != null) '\'${device.lastLogcatTimestamp}\'' else '0', if (lastLogcatTimestamp != null) '\'$lastLogcatTimestamp\'' else '0',
], ]);
]; }
final Process process = await processManager.start(device.adbCommandForDevice(args)); final Process process = await processManager.start(device.adbCommandForDevice(args));
return AdbLogReader._(process, device.name); return AdbLogReader._(process, device.name);
} }
...@@ -1340,3 +1324,10 @@ class AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -1340,3 +1324,10 @@ class AndroidDevicePortForwarder extends DevicePortForwarder {
} }
} }
} }
// In platform tools 29.0.0 adb.exe seems to be ending with this heap
// corruption error code on seemingly successful termination. Ignore
// this error on windows.
bool _allowHeapCorruptionOnWindows(int exitCode, Platform platform) {
return exitCode == -1073740940 && platform.isWindows;
}
...@@ -11,22 +11,29 @@ import '../base/io.dart'; ...@@ -11,22 +11,29 @@ import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/user_messages.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart' as globals;
import 'adb.dart'; import 'adb.dart';
import 'android_device.dart'; import 'android_device.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
import 'android_workflow.dart' hide androidWorkflow; import 'android_workflow.dart' hide androidWorkflow;
/// Device discovery for Android physical devices and emulators. /// Device discovery for Android physical devices and emulators.
///
/// This class primarily delegates to the `adb` command line tool provided by
/// the Android SDK to discover instances of connected android devices.
///
/// See also:
/// * [AndroidDevice], the type of discovered device.
class AndroidDevices extends PollingDeviceDiscovery { class AndroidDevices extends PollingDeviceDiscovery {
AndroidDevices({ AndroidDevices({
@required AndroidWorkflow androidWorkflow, @required AndroidWorkflow androidWorkflow,
@required ProcessManager processManager, @required ProcessManager processManager,
@required Logger logger, @required Logger logger,
@required AndroidSdk androidSdk, @required AndroidSdk androidSdk,
FileSystem fileSystem, // TODO(jonahwilliams): remove after rolling into google3 @required FileSystem fileSystem,
Platform platform, @required Platform platform,
@required UserMessages userMessages,
}) : _androidWorkflow = androidWorkflow, }) : _androidWorkflow = androidWorkflow,
_androidSdk = androidSdk, _androidSdk = androidSdk,
_processUtils = ProcessUtils( _processUtils = ProcessUtils(
...@@ -35,8 +42,9 @@ class AndroidDevices extends PollingDeviceDiscovery { ...@@ -35,8 +42,9 @@ class AndroidDevices extends PollingDeviceDiscovery {
), ),
_processManager = processManager, _processManager = processManager,
_logger = logger, _logger = logger,
_fileSystem = fileSystem ?? globals.fs, _fileSystem = fileSystem,
_platform = platform ?? globals.platform, _platform = platform,
_userMessages = userMessages,
super('Android devices'); super('Android devices');
final AndroidWorkflow _androidWorkflow; final AndroidWorkflow _androidWorkflow;
...@@ -46,6 +54,7 @@ class AndroidDevices extends PollingDeviceDiscovery { ...@@ -46,6 +54,7 @@ class AndroidDevices extends PollingDeviceDiscovery {
final Logger _logger; final Logger _logger;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final Platform _platform; final Platform _platform;
final UserMessages _userMessages;
@override @override
bool get supportsPlatform => _androidWorkflow.appliesToHostPlatform; bool get supportsPlatform => _androidWorkflow.appliesToHostPlatform;
...@@ -60,27 +69,19 @@ class AndroidDevices extends PollingDeviceDiscovery { ...@@ -60,27 +69,19 @@ class AndroidDevices extends PollingDeviceDiscovery {
} }
String text; String text;
try { try {
text = (await _processUtils.run( text = (await _processUtils.run(<String>[_androidSdk.adbPath, 'devices', '-l'],
<String>[_androidSdk.adbPath, 'devices', '-l'],
throwOnError: true, throwOnError: true,
)).stdout.trim(); )).stdout.trim();
} on ArgumentError catch (exception) {
throwToolExit('Unable to find "adb", check your Android SDK installation and '
'$kAndroidSdkRoot environment variable: ${exception.message}');
} on ProcessException catch (exception) { } on ProcessException catch (exception) {
throwToolExit('Unable to run "adb", check your Android SDK installation and ' throwToolExit(
'$kAndroidSdkRoot environment variable: ${exception.executable}'); 'Unable to run "adb", check your Android SDK installation and '
'$kAndroidSdkRoot environment variable: ${exception.executable}',
);
} }
final List<AndroidDevice> devices = <AndroidDevice>[]; final List<AndroidDevice> devices = <AndroidDevice>[];
parseADBDeviceOutput( _parseADBDeviceOutput(
text, text,
devices: devices, devices: devices,
timeoutConfiguration: timeoutConfiguration,
processManager: _processManager,
logger: _logger,
fileSystem: _fileSystem,
androidSdk: _androidSdk,
platform: _platform,
); );
return devices; return devices;
} }
...@@ -94,22 +95,14 @@ class AndroidDevices extends PollingDeviceDiscovery { ...@@ -94,22 +95,14 @@ class AndroidDevices extends PollingDeviceDiscovery {
final RunResult result = await _processUtils.run(<String>[_androidSdk.adbPath, 'devices', '-l']); final RunResult result = await _processUtils.run(<String>[_androidSdk.adbPath, 'devices', '-l']);
if (result.exitCode != 0) { if (result.exitCode != 0) {
return <String>[]; return <String>[];
} else { }
final String text = result.stdout;
final List<String> diagnostics = <String>[]; final List<String> diagnostics = <String>[];
parseADBDeviceOutput( _parseADBDeviceOutput(
text, result.stdout,
diagnostics: diagnostics, diagnostics: diagnostics,
timeoutConfiguration: timeoutConfiguration,
processManager: _processManager,
logger: _logger,
fileSystem: _fileSystem,
androidSdk: _androidSdk,
platform: _platform,
); );
return diagnostics; return diagnostics;
} }
}
// 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper // 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper
static final RegExp _kDeviceRegex = RegExp(r'^(\S+)\s+(\S+)(.*)'); static final RegExp _kDeviceRegex = RegExp(r'^(\S+)\s+(\S+)(.*)');
...@@ -117,17 +110,10 @@ class AndroidDevices extends PollingDeviceDiscovery { ...@@ -117,17 +110,10 @@ class AndroidDevices extends PollingDeviceDiscovery {
/// Parse the given `adb devices` output in [text], and fill out the given list /// Parse the given `adb devices` output in [text], and fill out the given list
/// of devices and possible device issue diagnostics. Either argument can be null, /// of devices and possible device issue diagnostics. Either argument can be null,
/// in which case information for that parameter won't be populated. /// in which case information for that parameter won't be populated.
@visibleForTesting void _parseADBDeviceOutput(
static void parseADBDeviceOutput(
String text, { String text, {
List<AndroidDevice> devices, List<AndroidDevice> devices,
List<String> diagnostics, List<String> diagnostics,
@required AndroidSdk androidSdk,
@required FileSystem fileSystem,
@required Logger logger,
@required Platform platform,
@required ProcessManager processManager,
@required TimeoutConfiguration timeoutConfiguration,
}) { }) {
// Check for error messages from adb // Check for error messages from adb
if (!text.contains('List of devices')) { if (!text.contains('List of devices')) {
...@@ -186,19 +172,19 @@ class AndroidDevices extends PollingDeviceDiscovery { ...@@ -186,19 +172,19 @@ class AndroidDevices extends PollingDeviceDiscovery {
productID: info['product'], productID: info['product'],
modelID: info['model'] ?? deviceID, modelID: info['model'] ?? deviceID,
deviceCodeName: info['device'], deviceCodeName: info['device'],
androidSdk: androidSdk, androidSdk: _androidSdk,
fileSystem: fileSystem, fileSystem: _fileSystem,
logger: logger, logger: _logger,
platform: platform, platform: _platform,
processManager: processManager, processManager: _processManager,
timeoutConfiguration: timeoutConfiguration, timeoutConfiguration: const TimeoutConfiguration(),
)); ));
} }
} else { } else {
diagnostics?.add( diagnostics?.add(
'Unexpected failure parsing device information from adb output:\n' 'Unexpected failure parsing device information from adb output:\n'
'$line\n' '$line\n'
'${globals.userMessages.flutterToolBugInstructions}'); '${_userMessages.flutterToolBugInstructions}');
} }
} }
} }
......
...@@ -70,6 +70,13 @@ class AndroidWorkflow implements Workflow { ...@@ -70,6 +70,13 @@ class AndroidWorkflow implements Workflow {
&& _androidSdk.emulatorPath != null; && _androidSdk.emulatorPath != null;
} }
/// A validator that checks if the Android SDK and Java SDK are available and
/// installed correctly.
///
/// Android development requires the Android SDK, and at least one Java SDK. While
/// newer Java compilers can be used to compile the Java application code, the SDK
/// tools themselves required JDK 1.8. This older JDK is normally bundled with
/// Android Studio.
class AndroidValidator extends DoctorValidator { class AndroidValidator extends DoctorValidator {
AndroidValidator({ AndroidValidator({
@required AndroidSdk androidSdk, @required AndroidSdk androidSdk,
...@@ -244,6 +251,8 @@ class AndroidValidator extends DoctorValidator { ...@@ -244,6 +251,8 @@ class AndroidValidator extends DoctorValidator {
} }
} }
/// A subvalidator that checks if the licenses within the detected Android
/// SDK have been accepted.
class AndroidLicenseValidator extends DoctorValidator { class AndroidLicenseValidator extends DoctorValidator {
AndroidLicenseValidator() : super('Android license subvalidator',); AndroidLicenseValidator() : super('Android license subvalidator',);
......
...@@ -144,6 +144,7 @@ Future<T> runInContext<T>( ...@@ -144,6 +144,7 @@ Future<T> runInContext<T>(
config: globals.config, config: globals.config,
fuchsiaWorkflow: fuchsiaWorkflow, fuchsiaWorkflow: fuchsiaWorkflow,
xcDevice: globals.xcdevice, xcDevice: globals.xcdevice,
userMessages: globals.userMessages,
windowsWorkflow: windowsWorkflow, windowsWorkflow: windowsWorkflow,
macOSWorkflow: MacOSWorkflow( macOSWorkflow: MacOSWorkflow(
platform: globals.platform, platform: globals.platform,
......
...@@ -342,6 +342,7 @@ class FlutterDeviceManager extends DeviceManager { ...@@ -342,6 +342,7 @@ class FlutterDeviceManager extends DeviceManager {
@required Config config, @required Config config,
@required Artifacts artifacts, @required Artifacts artifacts,
@required MacOSWorkflow macOSWorkflow, @required MacOSWorkflow macOSWorkflow,
@required UserMessages userMessages,
@required OperatingSystemUtils operatingSystemUtils, @required OperatingSystemUtils operatingSystemUtils,
@required WindowsWorkflow windowsWorkflow, @required WindowsWorkflow windowsWorkflow,
}) : deviceDiscoverers = <DeviceDiscovery>[ }) : deviceDiscoverers = <DeviceDiscovery>[
...@@ -350,6 +351,9 @@ class FlutterDeviceManager extends DeviceManager { ...@@ -350,6 +351,9 @@ class FlutterDeviceManager extends DeviceManager {
androidSdk: androidSdk, androidSdk: androidSdk,
androidWorkflow: androidWorkflow, androidWorkflow: androidWorkflow,
processManager: processManager, processManager: processManager,
fileSystem: fileSystem,
platform: platform,
userMessages: userMessages,
), ),
IOSDevices( IOSDevices(
platform: platform, platform: platform,
......
...@@ -185,7 +185,7 @@ MockAndroidDevice createMockDevice(int sdkLevel) { ...@@ -185,7 +185,7 @@ MockAndroidDevice createMockDevice(int sdkLevel) {
final MockAndroidDevice mockAndroidDevice = MockAndroidDevice(); final MockAndroidDevice mockAndroidDevice = MockAndroidDevice();
when(mockAndroidDevice.apiVersion) when(mockAndroidDevice.apiVersion)
.thenAnswer((Invocation invocation) async => sdkLevel.toString()); .thenAnswer((Invocation invocation) async => sdkLevel.toString());
when(mockAndroidDevice.lastLogcatTimestamp).thenReturn(kLastLogcatTimestamp); when(mockAndroidDevice.lastLogcatTimestamp()).thenAnswer((Invocation _) async => kLastLogcatTimestamp);
when(mockAndroidDevice.adbCommandForDevice(any)) when(mockAndroidDevice.adbCommandForDevice(any))
.thenAnswer((Invocation invocation) => <String>[ .thenAnswer((Invocation invocation) => <String>[
'adb', '-s', '1234', ...invocation.positionalArguments.first as List<String> 'adb', '-s', '1234', ...invocation.positionalArguments.first as List<String>
......
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/memory.dart'; 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_device_discovery.dart';
import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/android/android_workflow.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
...@@ -17,17 +17,27 @@ import '../../src/fake_process_manager.dart'; ...@@ -17,17 +17,27 @@ import '../../src/fake_process_manager.dart';
import '../../src/testbed.dart'; import '../../src/testbed.dart';
void main() { void main() {
AndroidWorkflow androidWorkflow;
setUp(() {
androidWorkflow = AndroidWorkflow(
androidSdk: FakeAndroidSdk(),
featureFlags: TestFeatureFlags(),
);
});
testWithoutContext('AndroidDevices returns empty device list and diagnostics on null adb', () async { testWithoutContext('AndroidDevices returns empty device list and diagnostics on null adb', () async {
final AndroidDevices androidDevices = AndroidDevices( final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(null), androidSdk: FakeAndroidSdk(null),
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow( androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(null), androidSdk: FakeAndroidSdk(null),
featureFlags: TestFeatureFlags(), featureFlags: TestFeatureFlags(),
), ),
processManager: FakeProcessManager.list(<FakeCommand>[]), processManager: FakeProcessManager.list(<FakeCommand>[]),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
platform: FakePlatform(), platform: FakePlatform(),
userMessages: UserMessages(),
); );
expect(await androidDevices.pollingGetDevices(), isEmpty); expect(await androidDevices.pollingGetDevices(), isEmpty);
...@@ -39,43 +49,19 @@ void main() { ...@@ -39,43 +49,19 @@ void main() {
androidSdk: null, androidSdk: null,
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow( androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(null), androidSdk: FakeAndroidSdk(null),
featureFlags: TestFeatureFlags(), featureFlags: TestFeatureFlags(),
), ),
processManager: FakeProcessManager.list(<FakeCommand>[]), processManager: FakeProcessManager.list(<FakeCommand>[]),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
platform: FakePlatform(), platform: FakePlatform(),
userMessages: UserMessages(),
); );
expect(await androidDevices.pollingGetDevices(), isEmpty); expect(await androidDevices.pollingGetDevices(), isEmpty);
expect(await androidDevices.getDiagnostics(), isEmpty); expect(await androidDevices.getDiagnostics(), isEmpty);
}); });
testWithoutContext('AndroidDevices throwsToolExit on missing adb path', () {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>['adb', 'devices', '-l'],
onRun: () {
throw ArgumentError('adb');
}
)
]);
final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(),
logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(),
featureFlags: TestFeatureFlags(),
),
processManager: processManager,
fileSystem: MemoryFileSystem.test(),
platform: FakePlatform(),
);
expect(androidDevices.pollingGetDevices(),
throwsToolExit(message: RegExp('Unable to find "adb"')));
});
testWithoutContext('AndroidDevices throwsToolExit on failing adb', () { testWithoutContext('AndroidDevices throwsToolExit on failing adb', () {
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand( const FakeCommand(
...@@ -84,15 +70,13 @@ void main() { ...@@ -84,15 +70,13 @@ void main() {
) )
]); ]);
final AndroidDevices androidDevices = AndroidDevices( final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(), androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow( androidWorkflow: androidWorkflow,
androidSdk: MockAndroidSdk(),
featureFlags: TestFeatureFlags(),
),
processManager: processManager, processManager: processManager,
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
platform: FakePlatform(), platform: FakePlatform(),
userMessages: UserMessages(),
); );
expect(androidDevices.pollingGetDevices(), expect(androidDevices.pollingGetDevices(),
...@@ -101,10 +85,10 @@ void main() { ...@@ -101,10 +85,10 @@ void main() {
testWithoutContext('AndroidDevices is disabled if feature is disabled', () { testWithoutContext('AndroidDevices is disabled if feature is disabled', () {
final AndroidDevices androidDevices = AndroidDevices( final AndroidDevices androidDevices = AndroidDevices(
androidSdk: MockAndroidSdk(), androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
androidWorkflow: AndroidWorkflow( androidWorkflow: AndroidWorkflow(
androidSdk: MockAndroidSdk(), androidSdk: FakeAndroidSdk(),
featureFlags: TestFeatureFlags( featureFlags: TestFeatureFlags(
isAndroidEnabled: false, isAndroidEnabled: false,
), ),
...@@ -112,98 +96,124 @@ void main() { ...@@ -112,98 +96,124 @@ void main() {
processManager: FakeProcessManager.any(), processManager: FakeProcessManager.any(),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
platform: FakePlatform(), platform: FakePlatform(),
userMessages: UserMessages(),
); );
expect(androidDevices.supportsPlatform, false); expect(androidDevices.supportsPlatform, false);
}); });
testWithoutContext('physical devices', () { testWithoutContext('AndroidDevices can parse output for physical devices', () async {
final List<AndroidDevice> devices = <AndroidDevice>[]; final AndroidDevices androidDevices = AndroidDevices(
AndroidDevices.parseADBDeviceOutput(''' userMessages: UserMessages(),
androidWorkflow: androidWorkflow,
androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['adb', 'devices', '-l'],
stdout: '''
List of devices attached List of devices attached
05a02bac device usb:336592896X product:razor model:Nexus_7 device:flo 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(), platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
); );
final List<Device> devices = await androidDevices.pollingGetDevices();
expect(devices, hasLength(1)); expect(devices, hasLength(1));
expect(devices.first.name, 'Nexus 7'); expect(devices.first.name, 'Nexus 7');
expect(devices.first.category, Category.mobile); expect(devices.first.category, Category.mobile);
}); });
testWithoutContext('emulators and short listings', () { testWithoutContext('AndroidDevices can parse output for emulators and short listings', () async {
final List<AndroidDevice> devices = <AndroidDevice>[]; final AndroidDevices androidDevices = AndroidDevices(
AndroidDevices.parseADBDeviceOutput(''' userMessages: UserMessages(),
androidWorkflow: androidWorkflow,
androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['adb', 'devices', '-l'],
stdout: '''
List of devices attached List of devices attached
localhost:36790 device localhost:36790 device
0149947A0D01500C device usb:340787200X 0149947A0D01500C device usb:340787200X
emulator-5612 host features:shell_2 emulator-5612 host features:shell_2
''', ''',
devices: devices, )
androidSdk: MockAndroidSdk(), ]),
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
timeoutConfiguration: const TimeoutConfiguration(),
platform: FakePlatform(), platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
); );
final List<Device> devices = await androidDevices.pollingGetDevices();
expect(devices, hasLength(3)); expect(devices, hasLength(3));
expect(devices.first.name, 'localhost:36790'); expect(devices[0].name, 'localhost:36790');
expect(devices[1].name, '0149947A0D01500C');
expect(devices[2].name, 'emulator-5612');
}); });
testWithoutContext('android n', () { testWithoutContext('AndroidDevices can parse output from android n', () async {
final List<AndroidDevice> devices = <AndroidDevice>[]; final AndroidDevices androidDevices = AndroidDevices(
AndroidDevices.parseADBDeviceOutput(''' userMessages: UserMessages(),
androidWorkflow: androidWorkflow,
androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['adb', 'devices', '-l'],
stdout: '''
List of devices attached List of devices attached
ZX1G22JJWR device usb:3-3 product:shamu model:Nexus_6 device:shamu features:cmd,shell_v2 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(), platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
); );
final List<Device> devices = await androidDevices.pollingGetDevices();
expect(devices, hasLength(1)); expect(devices, hasLength(1));
expect(devices.first.name, 'Nexus 6'); expect(devices.first.name, 'Nexus 6');
}); });
testWithoutContext('adb error message', () { testWithoutContext('AndroidDevices provides adb error message as diagnostics', () async {
final List<AndroidDevice> devices = <AndroidDevice>[]; final AndroidDevices androidDevices = AndroidDevices(
final List<String> diagnostics = <String>[]; userMessages: UserMessages(),
AndroidDevices.parseADBDeviceOutput(''' androidWorkflow: androidWorkflow,
androidSdk: FakeAndroidSdk(),
logger: BufferLogger.test(),
processManager: FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['adb', 'devices', '-l'],
stdout: '''
It appears you do not have 'Android SDK Platform-tools' installed. It appears you do not have 'Android SDK Platform-tools' installed.
Use the 'android' tool to install them: Use the 'android' tool to install them:
android update sdk --no-ui --filter 'platform-tools' android update sdk --no-ui --filter 'platform-tools'
''', devices: devices, ''',
diagnostics: diagnostics, )
timeoutConfiguration: const TimeoutConfiguration(), ]),
processManager: FakeProcessManager.any(),
platform: FakePlatform(), platform: FakePlatform(),
logger: BufferLogger.test(),
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
androidSdk: MockAndroidSdk(),
); );
expect(devices, isEmpty); final List<String> diagnostics = await androidDevices.getDiagnostics();
expect(diagnostics, hasLength(1)); expect(diagnostics, hasLength(1));
expect(diagnostics.first, contains('you do not have')); expect(diagnostics.first, contains('you do not have'));
}); });
} }
class MockAndroidSdk extends Mock implements AndroidSdk { class FakeAndroidSdk extends Fake implements AndroidSdk {
MockAndroidSdk([this.adbPath = 'adb']); FakeAndroidSdk([this.adbPath = 'adb']);
@override @override
final String adbPath; final String adbPath;
......
...@@ -131,23 +131,7 @@ void main() { ...@@ -131,23 +131,7 @@ void main() {
}); });
testWithoutContext('AndroidDevice can detect local emulator for known types', () async { testWithoutContext('AndroidDevice can detect local emulator for known types', () async {
final Set<String> knownPhyiscal = <String>{ for (final String hardware in kKnownHardware.keys) {
'qcom',
'samsungexynos7420',
'samsungexynos7580',
'samsungexynos7870',
'samsungexynos7880',
'samsungexynos8890',
'samsungexynos8895',
'samsungexynos9810',
'samsungexynos7570',
};
final Set<String> knownEmulator = <String>{
'goldfish',
'ranchu',
};
for (final String hardware in knownPhyiscal.followedBy(knownEmulator)) {
final AndroidDevice device = setUpAndroidDevice( final AndroidDevice device = setUpAndroidDevice(
processManager: FakeProcessManager.list(<FakeCommand>[ processManager: FakeProcessManager.list(<FakeCommand>[
FakeCommand( FakeCommand(
...@@ -160,7 +144,7 @@ void main() { ...@@ -160,7 +144,7 @@ void main() {
]) ])
); );
expect(await device.isLocalEmulator, knownEmulator.contains(hardware)); expect(await device.isLocalEmulator, kKnownHardware[hardware] == HardwareType.emulator);
} }
}); });
...@@ -358,7 +342,7 @@ flutter: ...@@ -358,7 +342,7 @@ flutter:
]) ])
); );
expect(device.lastLogcatTimestamp, isNull); expect(await device.lastLogcatTimestamp(), isNull);
}); });
testWithoutContext('AndroidDevice AdbLogReaders for past+future and future logs are not the same', () async { testWithoutContext('AndroidDevice AdbLogReaders for past+future and future logs are not the same', () async {
...@@ -657,7 +641,7 @@ const String kAdbShellGetprop = ''' ...@@ -657,7 +641,7 @@ const String kAdbShellGetprop = '''
/// A mock Android Console that presents a connection banner and responds to /// A mock Android Console that presents a connection banner and responds to
/// "avd name" requests with the supplied name. /// "avd name" requests with the supplied name.
class MockWorkingAndroidConsoleSocket extends Mock implements Socket { class MockWorkingAndroidConsoleSocket extends Fake implements Socket {
MockWorkingAndroidConsoleSocket(this.avdName) { MockWorkingAndroidConsoleSocket(this.avdName) {
_controller.add('Android Console: Welcome!\n'); _controller.add('Android Console: Welcome!\n');
// Include OK in the same packet here. In the response to "avd name" // Include OK in the same packet here. In the response to "avd name"
...@@ -683,10 +667,13 @@ class MockWorkingAndroidConsoleSocket extends Mock implements Socket { ...@@ -683,10 +667,13 @@ class MockWorkingAndroidConsoleSocket extends Mock implements Socket {
throw 'Unexpected command $text'; throw 'Unexpected command $text';
} }
} }
@override
void destroy() { }
} }
/// An Android console socket that drops all input and returns no output. /// An Android console socket that drops all input and returns no output.
class MockUnresponsiveAndroidConsoleSocket extends Mock implements Socket { class MockUnresponsiveAndroidConsoleSocket extends Fake implements Socket {
final StreamController<String> _controller = StreamController<String>(); final StreamController<String> _controller = StreamController<String>();
@override @override
...@@ -694,10 +681,13 @@ class MockUnresponsiveAndroidConsoleSocket extends Mock implements Socket { ...@@ -694,10 +681,13 @@ class MockUnresponsiveAndroidConsoleSocket extends Mock implements Socket {
@override @override
void add(List<int> data) {} void add(List<int> data) {}
@override
void destroy() { }
} }
/// An Android console socket that drops all input and returns no output. /// An Android console socket that drops all input and returns no output.
class MockDisconnectingAndroidConsoleSocket extends Mock implements Socket { class MockDisconnectingAndroidConsoleSocket extends Fake implements Socket {
MockDisconnectingAndroidConsoleSocket() { MockDisconnectingAndroidConsoleSocket() {
_controller.add('Android Console: Welcome!\n'); _controller.add('Android Console: Welcome!\n');
// Include OK in the same packet here. In the response to "avd name" // Include OK in the same packet here. In the response to "avd name"
...@@ -714,4 +704,7 @@ class MockDisconnectingAndroidConsoleSocket extends Mock implements Socket { ...@@ -714,4 +704,7 @@ class MockDisconnectingAndroidConsoleSocket extends Mock implements Socket {
void add(List<int> data) { void add(List<int> data) {
_controller.close(); _controller.close();
} }
@override
void destroy() { }
} }
...@@ -814,11 +814,11 @@ Stream<String> transformToLines(Stream<List<int>> byteStream) { ...@@ -814,11 +814,11 @@ Stream<String> transformToLines(Stream<List<int>> byteStream) {
} }
Map<String, dynamic> parseFlutterResponse(String line) { Map<String, dynamic> parseFlutterResponse(String line) {
if (line.startsWith('[') && line.endsWith(']')) { if (line.startsWith('[') && line.endsWith(']') && line.length > 2) {
try { try {
final Map<String, dynamic> response = castStringKeyedMap(json.decode(line)[0]); final Map<String, dynamic> response = castStringKeyedMap(json.decode(line)[0]);
return response; return response;
} on Exception { } on FormatException {
// Not valid JSON, so likely some other output that was surrounded by [brackets] // Not valid JSON, so likely some other output that was surrounded by [brackets]
return null; return null;
} }
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:path/path.dart' as path;
/// Count the number of libraries that import globals.dart in lib and test.
///
/// This must be run from the flutter_tools project root directory.
void main() {
final Directory sources = Directory(path.join(Directory.current.path, 'lib'));
final Directory tests = Directory(path.join(Directory.current.path, 'test'));
final int sourceGlobals = countGlobalImports(sources);
final int testGlobals = countGlobalImports(tests);
print('lib/ contains $sourceGlobals libraries with global usage');
print('test/ contains $testGlobals libraries with global usage');
}
final RegExp globalImport = RegExp('import.*globals.dart\' as globals;');
int countGlobalImports(Directory directory) {
int count = 0;
for (final FileSystemEntity file in directory.listSync(recursive: true)) {
if (!file.path.endsWith('.dart') || file is! File) {
continue;
}
final bool hasImport = (file as File).readAsLinesSync().any((String line) {
return globalImport.hasMatch(line);
});
if (hasImport) {
count += 1;
}
}
return count;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment