From 1eb7cd2c735f523a32960f631a764fcf5e720e48 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Mon, 24 Jun 2024 15:13:24 -0400 Subject: [PATCH] allow adb to set canfail then use canFail=true for clearing logs (#150517) Fixes https://github.com/flutter/flutter/issues/150093 New tests added to cover that we at least pass the arguments we expect to adb. The test for #150093 is not ideal in that it does not verify the behavior of a failed process but instead ensures we set the parameter that contains the behavior we want. devicelab code and tests are not setup to enable fake process or fake output from stdin/stderr and hang if adb or no hardware are present. --- dev/devicelab/README.md | 4 + dev/devicelab/lib/framework/devices.dart | 6 +- dev/devicelab/test/adb_test.dart | 157 ++++++++++++++++++++++- 3 files changed, 162 insertions(+), 5 deletions(-) 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 {} +}