diff --git a/dev/devicelab/README.md b/dev/devicelab/README.md index 3bf02a086f..4e0fdde167 100644 --- a/dev/devicelab/README.md +++ b/dev/devicelab/README.md @@ -60,6 +60,10 @@ Running the devicelab will do things to your environment. Notably, it will start and stop Gradle, for instance. +### Running tests in `test/...` + +`dart test test/{NAME_OF_TEST}` + ### Running specific tests To run a test, use option `-t` (`--task`): diff --git a/dev/devicelab/lib/framework/devices.dart b/dev/devicelab/lib/framework/devices.dart index 03ec9efb90..6992765576 100644 --- a/dev/devicelab/lib/framework/devices.dart +++ b/dev/devicelab/lib/framework/devices.dart @@ -706,6 +706,7 @@ class AndroidDevice extends Device { List arguments, { Map? environment, bool silent = false, + bool canFail = false, // as in, whether failures are ok. False means that they are fatal. }) { return eval( adbPath, @@ -713,6 +714,7 @@ class AndroidDevice extends Device { environment: environment, printStdout: !silent, printStderr: !silent, + canFail: canFail, ); } @@ -735,7 +737,7 @@ class AndroidDevice extends Device { @override Future startLoggingToSink(IOSink sink, {bool clear = true}) async { if (clear) { - await adb(['logcat', '--clear'], silent: true); + await adb(['logcat', '--clear'], silent: true, canFail: true); } _loggingProcess = await startProcess( adbPath, @@ -770,7 +772,7 @@ class AndroidDevice extends Device { @override Future clearLogs() { - return adb(['logcat', '-c']); + return adb(['logcat', '-c'], canFail: true); } @override diff --git a/dev/devicelab/test/adb_test.dart b/dev/devicelab/test/adb_test.dart index e9d0033f08..d65b66c958 100644 --- a/dev/devicelab/test/adb_test.dart +++ b/dev/devicelab/test/adb_test.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + import 'package:collection/collection.dart' show ListEquality, MapEquality; import 'package:flutter_devicelab/framework/devices.dart'; @@ -18,8 +23,7 @@ void main() { device = FakeDevice(deviceId: 'fakeDeviceId'); }); - tearDown(() { - }); + tearDown(() {}); group('cpu check', () { test('arm64', () async { @@ -119,12 +123,80 @@ void main() { group('adb', () { test('tap', () async { + FakeDevice.resetLog(); await device.tap(100, 200); expectLog([ - cmd(command: 'getprop', arguments: ['ro.bootimage.build.fingerprint', ';', 'getprop', 'ro.build.version.release', ';', 'getprop', 'ro.build.version.sdk']), cmd(command: 'input', arguments: ['tap', '100', '200']), ]); }); + + test('awaitDevice', () async { + FakeDevice.resetLog(); + // The expected value from `adb shell getprop sys.boot_completed` + FakeDevice.output = '1'; + await device.awaitDevice(); + expectLog([ + cmd(command: 'adb', environment: { + FakeDevice.canFailKey: 'false' + }, arguments: [ + '-s', + device.deviceId, + 'wait-for-device', + ]), + cmd(command: 'adb', environment: { + FakeDevice.canFailKey: 'false', + }, arguments: [ + '-s', + device.deviceId, + 'shell', + 'getprop sys.boot_completed', + ]) + ]); + }); + + test('reboot', () async { + FakeDevice.resetLog(); + await device.reboot(); + expectLog([ + cmd(command: 'adb', environment: { + FakeDevice.canFailKey: 'false' + }, arguments: [ + '-s', + device.deviceId, + 'reboot', + ]), + ]); + }); + + test('clearLog', () async { + FakeDevice.resetLog(); + await device.clearLogs(); + expectLog([ + cmd(command: 'adb', environment: { + FakeDevice.canFailKey: 'true' + }, arguments: [ + '-s', + device.deviceId, + 'logcat', + '-c', + ]), + ]); + }); + + test('startLoggingToSink calls adb', () async { + FakeDevice.resetLog(); + await device.startLoggingToSink(IOSink(_MemoryIOSink())); + expectLog([ + cmd(command: 'adb', environment: { + FakeDevice.canFailKey: 'true' + }, arguments: [ + '-s', + device.deviceId, + 'logcat', + '--clear', + ]), + ]); + }); }); }); } @@ -181,6 +253,8 @@ class CommandArgs { class FakeDevice extends AndroidDevice { FakeDevice({required super.deviceId}); + static const String canFailKey = 'canFail'; + static String output = ''; static List commandLog = []; @@ -213,6 +287,21 @@ class FakeDevice extends AndroidDevice { '''; } + @override + Future adb(List arguments, + {Map? environment, + bool silent = false, + bool canFail = false}) async { + environment ??= {}; + commandLog.add(CommandArgs( + command: 'adb', + // ignore: prefer_spread_collections + arguments: ['-s', deviceId]..addAll(arguments), + environment: environment..putIfAbsent('canFail', () => '$canFail'), + )); + return output; + } + @override Future shellEval(String command, List arguments, { Map? environment, bool silent = false }) async { commandLog.add(CommandArgs( @@ -232,3 +321,65 @@ class FakeDevice extends AndroidDevice { )); } } + +/// An IOSink that collects whatever is written to it. +/// Inspired by packages/flutter_tools/lib/src/base/net.dart +class _MemoryIOSink implements IOSink { + @override + Encoding encoding = utf8; + + final BytesBuilder writes = BytesBuilder(copy: false); + + @override + void add(List data) { + writes.add(data); + } + + @override + Future addStream(Stream> stream) { + final Completer completer = Completer(); + stream.listen(add).onDone(completer.complete); + return completer.future; + } + + @override + void writeCharCode(int charCode) { + add([charCode]); + } + + @override + void write(Object? obj) { + add(encoding.encode('$obj')); + } + + @override + void writeln([Object? obj = '']) { + add(encoding.encode('$obj\n')); + } + + @override + void writeAll(Iterable objects, [String separator = '']) { + bool addSeparator = false; + for (final dynamic object in objects) { + if (addSeparator) { + write(separator); + } + write(object); + addSeparator = true; + } + } + + @override + void addError(dynamic error, [StackTrace? stackTrace]) { + throw UnimplementedError(); + } + + @override + Future get done => close(); + + @override + Future close() async {} + + @override + Future flush() async {} +}