Unverified Commit ab68721a authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add Android device build/OS/API Level information to logs. (#58747)

This adds some information about the chosen device to the output log so that we can tell what type of device/OS Level/API Level the test is running on when doing a postmortem on a failed test.
parent 5722adf1
...@@ -12,6 +12,14 @@ import 'package:path/path.dart' as path; ...@@ -12,6 +12,14 @@ import 'package:path/path.dart' as path;
import 'utils.dart'; import 'utils.dart';
class DeviceException implements Exception {
const DeviceException(this.message);
final String message;
@override
String toString() => message == null ? '$DeviceException' : '$DeviceException: $message';
}
/// Gets the artifact path relative to the current directory. /// Gets the artifact path relative to the current directory.
String getArtifactPath() { String getArtifactPath() {
...@@ -43,7 +51,7 @@ abstract class DeviceDiscovery { ...@@ -43,7 +51,7 @@ abstract class DeviceDiscovery {
case DeviceOperatingSystem.fuchsia: case DeviceOperatingSystem.fuchsia:
return FuchsiaDeviceDiscovery(); return FuchsiaDeviceDiscovery();
default: default:
throw StateError('Unsupported device operating system: {config.deviceOperatingSystem}'); throw const DeviceException('Unsupported device operating system: {config.deviceOperatingSystem}');
} }
} }
...@@ -73,6 +81,9 @@ abstract class DeviceDiscovery { ...@@ -73,6 +81,9 @@ abstract class DeviceDiscovery {
/// A proxy for one specific device. /// A proxy for one specific device.
abstract class Device { abstract class Device {
// Const constructor so subclasses may be const.
const Device();
/// A unique device identifier. /// A unique device identifier.
String get deviceId; String get deviceId;
...@@ -110,6 +121,11 @@ abstract class Device { ...@@ -110,6 +121,11 @@ abstract class Device {
/// Stop a process. /// Stop a process.
Future<void> stop(String packageName); Future<void> stop(String packageName);
@override
String toString() {
return 'device: $deviceId';
}
} }
class AndroidDeviceDiscovery implements DeviceDiscovery { class AndroidDeviceDiscovery implements DeviceDiscovery {
...@@ -146,10 +162,11 @@ class AndroidDeviceDiscovery implements DeviceDiscovery { ...@@ -146,10 +162,11 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
.toList(); .toList();
if (allDevices.isEmpty) if (allDevices.isEmpty)
throw 'No Android devices detected'; throw const DeviceException('No Android devices detected');
// TODO(yjbanov): filter out and warn about those with low battery level // TODO(yjbanov): filter out and warn about those with low battery level
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)]; _workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
print('Device chosen: $_workingDevice');
} }
@override @override
...@@ -175,7 +192,7 @@ class AndroidDeviceDiscovery implements DeviceDiscovery { ...@@ -175,7 +192,7 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
results.add(deviceID); results.add(deviceID);
} }
} else { } else {
throw 'Failed to parse device from adb output: "$line"'; throw FormatException('Failed to parse device from adb output: "$line"');
} }
} }
...@@ -192,7 +209,7 @@ class AndroidDeviceDiscovery implements DeviceDiscovery { ...@@ -192,7 +209,7 @@ class AndroidDeviceDiscovery implements DeviceDiscovery {
// TODO(yjbanov): check battery level // TODO(yjbanov): check battery level
await device._getWakefulness(); await device._getWakefulness();
results['android-device-$deviceId'] = HealthCheckResult.success(); results['android-device-$deviceId'] = HealthCheckResult.success();
} catch (e, s) { } on Exception catch (e, s) {
results['android-device-$deviceId'] = HealthCheckResult.error(e, s); results['android-device-$deviceId'] = HealthCheckResult.error(e, s);
} }
} }
...@@ -246,9 +263,10 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery { ...@@ -246,9 +263,10 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
.toList(); .toList();
if (allDevices.isEmpty) { if (allDevices.isEmpty) {
throw Exception('No Fuchsia devices detected'); throw const DeviceException('No Fuchsia devices detected');
} }
_workingDevice = allDevices.first; _workingDevice = allDevices.first;
print('Device chosen: $_workingDevice');
} }
@override @override
...@@ -285,7 +303,7 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery { ...@@ -285,7 +303,7 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
} else { } else {
results['fuchsia-device-$deviceId'] = HealthCheckResult.failure('Cannot resolve device $deviceId'); results['fuchsia-device-$deviceId'] = HealthCheckResult.failure('Cannot resolve device $deviceId');
} }
} catch (error, stacktrace) { } on Exception catch (error, stacktrace) {
results['fuchsia-device-$deviceId'] = HealthCheckResult.error(error, stacktrace); results['fuchsia-device-$deviceId'] = HealthCheckResult.error(error, stacktrace);
} }
} }
...@@ -296,11 +314,14 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery { ...@@ -296,11 +314,14 @@ class FuchsiaDeviceDiscovery implements DeviceDiscovery {
Future<void> performPreflightTasks() async {} Future<void> performPreflightTasks() async {}
} }
class AndroidDevice implements Device { class AndroidDevice extends Device {
AndroidDevice({@required this.deviceId}); AndroidDevice({@required this.deviceId}) {
_updateDeviceInfo();
}
@override @override
final String deviceId; final String deviceId;
String deviceInfo = '';
/// Whether the device is awake. /// Whether the device is awake.
@override @override
...@@ -358,19 +379,53 @@ class AndroidDevice implements Device { ...@@ -358,19 +379,53 @@ class AndroidDevice implements Device {
return wakefulness; return wakefulness;
} }
Future<void> _updateDeviceInfo() async {
String info;
try {
info = await shellEval(
'getprop',
<String>[
'ro.bootimage.build.fingerprint', ';',
'getprop', 'ro.build.version.release', ';',
'getprop', 'ro.build.version.sdk',
],
silent: true,
);
} on IOException {
info = '';
}
final List<String> list = info.split('\n');
if (list.length == 3) {
deviceInfo = 'fingerprint: ${list[0]} os: ${list[1]} api-level: ${list[2]}';
} else {
deviceInfo = '';
}
}
/// Executes [command] on `adb shell` and returns its exit code. /// Executes [command] on `adb shell` and returns its exit code.
Future<void> shellExec(String command, List<String> arguments, { Map<String, String> environment }) async { Future<void> shellExec(String command, List<String> arguments, { Map<String, String> environment, bool silent = false }) async {
await adb(<String>['shell', command, ...arguments], environment: environment); await adb(<String>['shell', command, ...arguments], environment: environment, silent: silent);
} }
/// Executes [command] on `adb shell` and returns its standard output as a [String]. /// Executes [command] on `adb shell` and returns its standard output as a [String].
Future<String> shellEval(String command, List<String> arguments, { Map<String, String> environment }) { Future<String> shellEval(String command, List<String> arguments, { Map<String, String> environment, bool silent = false }) {
return adb(<String>['shell', command, ...arguments], environment: environment); return adb(<String>['shell', command, ...arguments], environment: environment, silent: silent);
} }
/// Runs `adb` with the given [arguments], selecting this device. /// Runs `adb` with the given [arguments], selecting this device.
Future<String> adb(List<String> arguments, { Map<String, String> environment }) { Future<String> adb(
return eval(adbPath, <String>['-s', deviceId, ...arguments], environment: environment, canFail: false); List<String> arguments, {
Map<String, String> environment,
bool silent = false,
}) {
return eval(
adbPath,
<String>['-s', deviceId, ...arguments],
environment: environment,
canFail: false,
printStdout: !silent,
printStderr: !silent,
);
} }
@override @override
...@@ -453,6 +508,11 @@ class AndroidDevice implements Device { ...@@ -453,6 +508,11 @@ class AndroidDevice implements Device {
Future<void> stop(String packageName) async { Future<void> stop(String packageName) async {
return shellExec('am', <String>['force-stop', packageName]); return shellExec('am', <String>['force-stop', packageName]);
} }
@override
String toString() {
return '$deviceId $deviceInfo';
}
} }
class IosDeviceDiscovery implements DeviceDiscovery { class IosDeviceDiscovery implements DeviceDiscovery {
...@@ -484,10 +544,11 @@ class IosDeviceDiscovery implements DeviceDiscovery { ...@@ -484,10 +544,11 @@ class IosDeviceDiscovery implements DeviceDiscovery {
.toList(); .toList();
if (allDevices.isEmpty) if (allDevices.isEmpty)
throw 'No iOS devices detected'; throw const DeviceException('No iOS devices detected');
// TODO(yjbanov): filter out and warn about those with low battery level // TODO(yjbanov): filter out and warn about those with low battery level
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)]; _workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
print('Device chosen: $_workingDevice');
} }
// Returns a colon-separated environment variable that contains the paths // Returns a colon-separated environment variable that contains the paths
...@@ -511,7 +572,7 @@ class IosDeviceDiscovery implements DeviceDiscovery { ...@@ -511,7 +572,7 @@ class IosDeviceDiscovery implements DeviceDiscovery {
.where((String line) => line.isNotEmpty) .where((String line) => line.isNotEmpty)
.toList(); .toList();
if (iosDeviceIDs.isEmpty) if (iosDeviceIDs.isEmpty)
throw 'No connected iOS devices found.'; throw const DeviceException('No connected iOS devices found.');
return iosDeviceIDs; return iosDeviceIDs;
} }
...@@ -532,7 +593,7 @@ class IosDeviceDiscovery implements DeviceDiscovery { ...@@ -532,7 +593,7 @@ class IosDeviceDiscovery implements DeviceDiscovery {
} }
/// iOS device. /// iOS device.
class IosDevice implements Device { class IosDevice extends Device {
const IosDevice({ @required this.deviceId }); const IosDevice({ @required this.deviceId });
@override @override
...@@ -563,17 +624,17 @@ class IosDevice implements Device { ...@@ -563,17 +624,17 @@ class IosDevice implements Device {
@override @override
Future<void> tap(int x, int y) async { Future<void> tap(int x, int y) async {
throw 'Not implemented'; throw UnimplementedError();
} }
@override @override
Future<Map<String, dynamic>> getMemoryStats(String packageName) async { Future<Map<String, dynamic>> getMemoryStats(String packageName) async {
throw 'Not implemented'; throw UnimplementedError();
} }
@override @override
Stream<String> get logcat { Stream<String> get logcat {
throw 'Not implemented'; throw UnimplementedError();
} }
@override @override
...@@ -581,7 +642,7 @@ class IosDevice implements Device { ...@@ -581,7 +642,7 @@ class IosDevice implements Device {
} }
/// Fuchsia device. /// Fuchsia device.
class FuchsiaDevice implements Device { class FuchsiaDevice extends Device {
const FuchsiaDevice({ @required this.deviceId }); const FuchsiaDevice({ @required this.deviceId });
@override @override
...@@ -614,12 +675,12 @@ class FuchsiaDevice implements Device { ...@@ -614,12 +675,12 @@ class FuchsiaDevice implements Device {
@override @override
Future<Map<String, dynamic>> getMemoryStats(String packageName) async { Future<Map<String, dynamic>> getMemoryStats(String packageName) async {
throw 'Not implemented'; throw UnimplementedError();
} }
@override @override
Stream<String> get logcat { Stream<String> get logcat {
throw 'Not implemented'; throw UnimplementedError();
} }
} }
...@@ -627,15 +688,18 @@ class FuchsiaDevice implements Device { ...@@ -627,15 +688,18 @@ class FuchsiaDevice implements Device {
String get adbPath { String get adbPath {
final String androidHome = Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT']; final String androidHome = Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT'];
if (androidHome == null) if (androidHome == null) {
throw 'The ANDROID_SDK_ROOT and ANDROID_HOME environment variables are ' throw const DeviceException(
'missing. At least one of these variables must point to the Android ' 'The ANDROID_SDK_ROOT and ANDROID_HOME environment variables are '
'SDK directory containing platform-tools.'; 'missing. At least one of these variables must point to the Android '
'SDK directory containing platform-tools.'
);
}
final String adbPath = path.join(androidHome, 'platform-tools/adb'); final String adbPath = path.join(androidHome, 'platform-tools/adb');
if (!canRun(adbPath)) if (!canRun(adbPath))
throw 'adb not found at: $adbPath'; throw DeviceException('adb not found at: $adbPath');
return path.absolute(adbPath); return path.absolute(adbPath);
} }
...@@ -475,11 +475,11 @@ void cd(dynamic directory) { ...@@ -475,11 +475,11 @@ void cd(dynamic directory) {
cwd = directory.path; cwd = directory.path;
d = directory; d = directory;
} else { } else {
throw 'Unsupported type ${directory.runtimeType} of $directory'; throw FileSystemException('Unsupported directory type ${directory.runtimeType}', directory.toString());
} }
if (!d.existsSync()) if (!d.existsSync())
throw 'Cannot cd into directory that does not exist: $directory'; throw FileSystemException('Cannot cd into directory that does not exist', d.toString());
} }
Directory get flutterDirectory => Directory.current.parent.parent; Directory get flutterDirectory => Directory.current.parent.parent;
......
...@@ -42,6 +42,7 @@ void main() { ...@@ -42,6 +42,7 @@ void main() {
test('sends power event', () async { test('sends power event', () async {
await device.togglePower(); await device.togglePower();
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'input', arguments: <String>['keyevent', '26']), cmd(command: 'input', arguments: <String>['keyevent', '26']),
]); ]);
}); });
...@@ -52,6 +53,7 @@ void main() { ...@@ -52,6 +53,7 @@ void main() {
FakeDevice.pretendAwake(); FakeDevice.pretendAwake();
await device.wakeUp(); await device.wakeUp();
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'dumpsys', arguments: <String>['power']), cmd(command: 'dumpsys', arguments: <String>['power']),
]); ]);
}); });
...@@ -60,6 +62,7 @@ void main() { ...@@ -60,6 +62,7 @@ void main() {
FakeDevice.pretendAsleep(); FakeDevice.pretendAsleep();
await device.wakeUp(); await device.wakeUp();
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'dumpsys', arguments: <String>['power']), cmd(command: 'dumpsys', arguments: <String>['power']),
cmd(command: 'input', arguments: <String>['keyevent', '26']), cmd(command: 'input', arguments: <String>['keyevent', '26']),
]); ]);
...@@ -71,6 +74,7 @@ void main() { ...@@ -71,6 +74,7 @@ void main() {
FakeDevice.pretendAsleep(); FakeDevice.pretendAsleep();
await device.sendToSleep(); await device.sendToSleep();
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'dumpsys', arguments: <String>['power']), cmd(command: 'dumpsys', arguments: <String>['power']),
]); ]);
}); });
...@@ -79,6 +83,7 @@ void main() { ...@@ -79,6 +83,7 @@ void main() {
FakeDevice.pretendAwake(); FakeDevice.pretendAwake();
await device.sendToSleep(); await device.sendToSleep();
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'dumpsys', arguments: <String>['power']), cmd(command: 'dumpsys', arguments: <String>['power']),
cmd(command: 'input', arguments: <String>['keyevent', '26']), cmd(command: 'input', arguments: <String>['keyevent', '26']),
]); ]);
...@@ -90,6 +95,7 @@ void main() { ...@@ -90,6 +95,7 @@ void main() {
FakeDevice.pretendAwake(); FakeDevice.pretendAwake();
await device.unlock(); await device.unlock();
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'dumpsys', arguments: <String>['power']), cmd(command: 'dumpsys', arguments: <String>['power']),
cmd(command: 'input', arguments: <String>['keyevent', '82']), cmd(command: 'input', arguments: <String>['keyevent', '82']),
]); ]);
...@@ -100,6 +106,7 @@ void main() { ...@@ -100,6 +106,7 @@ void main() {
test('tap', () async { test('tap', () async {
await device.tap(100, 200); await device.tap(100, 200);
expectLog(<CommandArgs>[ expectLog(<CommandArgs>[
cmd(command: 'getprop', arguments: <String>['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk'], environment: null),
cmd(command: 'input', arguments: <String>['tap', '100', '200']), cmd(command: 'input', arguments: <String>['tap', '100', '200']),
]); ]);
}); });
...@@ -183,7 +190,7 @@ class FakeDevice extends AndroidDevice { ...@@ -183,7 +190,7 @@ class FakeDevice extends AndroidDevice {
} }
@override @override
Future<String> shellEval(String command, List<String> arguments, { Map<String, String> environment }) async { Future<String> shellEval(String command, List<String> arguments, { Map<String, String> environment, bool silent = false }) async {
commandLog.add(CommandArgs( commandLog.add(CommandArgs(
command: command, command: command,
arguments: arguments, arguments: arguments,
...@@ -193,7 +200,7 @@ class FakeDevice extends AndroidDevice { ...@@ -193,7 +200,7 @@ class FakeDevice extends AndroidDevice {
} }
@override @override
Future<void> shellExec(String command, List<String> arguments, { Map<String, String> environment }) async { Future<void> shellExec(String command, List<String> arguments, { Map<String, String> environment, bool silent = false }) async {
commandLog.add(CommandArgs( commandLog.add(CommandArgs(
command: command, command: command,
arguments: arguments, arguments: arguments,
......
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