Chromium Code Reviews| Index: tools/testing/dart/android.dart |
| diff --git a/tools/testing/dart/android.dart b/tools/testing/dart/android.dart |
| index 1d390715733707bd716b615c676b5b17a7e6d3ee..9bf2926cdc8a82929cecf6ddd8e85c63e2b93c6a 100644 |
| --- a/tools/testing/dart/android.dart |
| +++ b/tools/testing/dart/android.dart |
| @@ -7,30 +7,40 @@ library android; |
| import "dart:async"; |
| import "dart:convert" show LineSplitter, UTF8; |
| import "dart:core"; |
| +import "dart:collection"; |
| import "dart:io"; |
| import "path.dart"; |
| import "utils.dart"; |
| -Future _executeCommand(String executable, List<String> args, |
| - [String stdin = ""]) { |
| - return _executeCommandRaw(executable, args, stdin).then((results) => null); |
| -} |
| - |
| -Future _executeCommandGetOutput(String executable, List<String> args, |
| - [String stdin = ""]) { |
| - return _executeCommandRaw(executable, args, stdin).then((output) => output); |
| +class AdbCommandResult { |
| + final String command; |
| + final String stdout; |
| + final String stderr; |
| + final int exitCode; |
| + |
| + AdbCommandResult(this.command, this.stdout, this.stderr, this.exitCode); |
| + |
| + void throwIfFailed() { |
| + if (exitCode != 0) { |
| + var error = "Running: $command failed:" |
| + "stdout: \n $stdout" |
| + "stderr: \n $stderr" |
| + "exitCode: \n $exitCode"; |
| + throw new Exception(error); |
| + } |
| + } |
| } |
| /** |
| - * [_executeCommandRaw] will write [stdin] to the standard input of the created |
| + * [_executeCommand] will write [stdin] to the standard input of the created |
| * process and will return a tuple (stdout, stderr). |
| * |
| * If the exit code of the process was nonzero it will complete with an error. |
| * If starting the process failed, it will complete with an error as well. |
| */ |
| -Future _executeCommandRaw(String executable, List<String> args, |
| - [String stdin = ""]) { |
| +Future<AdbCommandResult> _executeCommand( |
| + String executable, List<String> args, [String stdin = ""]) { |
| Future<String> getOutput(Stream<List<int>> stream) { |
| return stream |
| .transform(UTF8.decoder) |
| @@ -38,31 +48,19 @@ Future _executeCommandRaw(String executable, List<String> args, |
| .then((data) => data.join("")); |
| } |
| - DebugLogger.info("Running: '\$ $executable ${args.join(' ')}'"); |
| - return Process.start(executable, args).then((Process process) { |
| + return Process.start(executable, args).then((Process process) async { |
| if (stdin != null && stdin != '') { |
| process.stdin.write(stdin); |
| } |
| process.stdin.close(); |
| - var futures = [ |
| - getOutput(process.stdout), |
| - getOutput(process.stderr), |
| - process.exitCode |
| - ]; |
| - return Future.wait(futures).then((results) { |
| - bool success = results[2] == 0; |
| - if (!success) { |
| - var error = "Running: '\$ $executable ${args.join(' ')}' failed:" |
| - "stdout: \n ${results[0]}" |
| - "stderr: \n ${results[1]}" |
| - "exitCode: \n ${results[2]}"; |
| - throw new Exception(error); |
| - } else { |
| - DebugLogger.info("Success: $executable finished"); |
| - } |
| - return results[0]; |
| - }); |
| + var results = await Future.wait([ |
| + getOutput(process.stdout), |
| + getOutput(process.stderr), |
| + process.exitCode |
| + ]); |
| + String command = '$executable ${args.join(' ')}'; |
|
Bill Hesse
2016/04/27 13:54:40
It may be legal to have ' inside ' because it is i
kustermann
2016/05/02 10:40:23
Done.
|
| + return new AdbCommandResult(command, results[0], results[1], results[2]); |
| }); |
| } |
| @@ -152,7 +150,7 @@ class AndroidEmulator { |
| * Helper class to create avd device configurations. |
| */ |
| class AndroidHelper { |
| - static Future createAvd(String name, String target) { |
| + static Future createAvd(String name, String target) async { |
| var args = [ |
| '--silent', |
| 'create', |
| @@ -166,7 +164,8 @@ class AndroidHelper { |
| 'armeabi-v7a' |
| ]; |
| // We're adding newlines to stdin to simulate <enter>. |
| - return _executeCommand("android", args, "\n\n\n\n"); |
| + var result = await _executeCommand("android", args, "\n\n\n\n"); |
| + result.throwIfFailed(); |
| } |
| } |
| @@ -192,25 +191,15 @@ class AdbDevice { |
| * Polls the 'sys.boot_completed' property. Returns as soon as the property is |
| * 1. |
| */ |
| - Future waitForBootCompleted() { |
| - var timeout = const Duration(seconds: 2); |
| - var completer = new Completer(); |
| - |
| - checkUntilBooted() { |
| - _adbCommandGetOutput(['shell', 'getprop', 'sys.boot_completed']) |
| - .then((String stdout) { |
| - stdout = stdout.trim(); |
| - if (stdout == '1') { |
| - completer.complete(); |
| - } else { |
| - new Timer(timeout, checkUntilBooted); |
| - } |
| - }).catchError((error) { |
| - new Timer(timeout, checkUntilBooted); |
| - }); |
| + Future waitForBootCompleted() async { |
| + while (true) { |
| + try { |
| + AdbCommandResult result = |
| + await _adbCommand(['shell', 'getprop', 'sys.boot_completed']); |
| + if (result.stdout.trim() == '1') return; |
| + } catch (_) { } |
| + await new Future.delayed(const Duration(seconds: 2)); |
| } |
| - checkUntilBooted(); |
| - return completer.future; |
| } |
| /** |
| @@ -299,22 +288,48 @@ class AdbDevice { |
| return _adbCommand(arguments); |
| } |
| - Future _adbCommand(List<String> adbArgs) { |
| - if (_deviceId != null) { |
| - var extendedAdbArgs = ['-s', _deviceId]; |
| - extendedAdbArgs.addAll(adbArgs); |
| - adbArgs = extendedAdbArgs; |
| + Future<AdbCommandResult> runAdbCommand(List<String> adbArgs) { |
| + return _executeCommand("adb", _deviceSpecificArgs(adbArgs)); |
| + } |
| + |
| + Future<AdbCommandResult> runAdbShellCommand(List<String> shellArgs) async { |
| + const MARKER = 'AdbShellExitCode: '; |
| + |
| + // The exitcode of 'adb shell ...' can be 0 even though the command failed |
| + // with a non-zero exit code. We therefore explicitly print it to stdout and |
| + // search for it. |
| + |
| + var args = ['shell', |
| + "${shellArgs.join(' ')} ; echo $MARKER \$?"]; |
| + AdbCommandResult result = await _executeCommand( |
| + "adb", _deviceSpecificArgs(args)); |
| + int exitCode = result.exitCode; |
| + var lines = result |
| + .stdout.split('\n') |
| + .where((line) => line.trim().length > 0) |
| + .toList(); |
| + if (lines.length > 0) { |
| + int index = lines.last.indexOf(MARKER); |
| + assert(index >= 0); |
| + exitCode = int.parse(lines.last.substring(index + MARKER.length).trim()); |
| } |
| - return _executeCommand("adb", adbArgs); |
| + return new AdbCommandResult( |
| + result.command, result.stdout, result.stderr, exitCode); |
| + } |
| + |
| + Future<AdbCommandResult> _adbCommand(List<String> adbArgs) async { |
| + var result = await _executeCommand("adb", _deviceSpecificArgs(adbArgs)); |
| + result.throwIfFailed(); |
| + return result; |
| } |
| - Future<String> _adbCommandGetOutput(List<String> adbArgs) { |
| + List<String> _deviceSpecificArgs(List<String> adbArgs) { |
| if (_deviceId != null) { |
| var extendedAdbArgs = ['-s', _deviceId]; |
| extendedAdbArgs.addAll(adbArgs); |
| adbArgs = extendedAdbArgs; |
| } |
| - return _executeCommandGetOutput("adb", adbArgs); |
| + return adbArgs; |
| } |
| } |
| @@ -350,3 +365,45 @@ class Intent { |
| Intent(this.action, this.package, this.activity, [this.dataUri]); |
| } |
| + |
| +/** |
| + * Discovers all available devices and supports acquire/release. |
| + */ |
| +class AdbDevicePool { |
| + final Queue<AdbDevice> _idleDevices = new Queue<AdbDevice>(); |
| + final Queue<Completer> _waiter = new Queue<Completer>(); |
| + |
| + AdbDevicePool(List<AdbDevice> idleDevices) { |
| + _idleDevices.addAll(idleDevices); |
| + } |
| + |
| + static Future<AdbDevicePool> create() async { |
| + var names = await AdbHelper.listDevices(); |
| + var devices = names.map((id) => new AdbDevice(id)).toList(); |
| + if (devices.length == 0) { |
| + throw new Exception( |
| + 'No android devices found. ' |
| + 'Please make sure "adb devices" shows your device!'); |
| + } |
| + return new AdbDevicePool(devices); |
| + } |
| + |
| + Future<AdbDevice> acquireDevice() async { |
| + if (_idleDevices.length > 0) { |
| + return _idleDevices.removeFirst(); |
| + } else { |
| + var completer = new Completer(); |
| + _waiter.add(completer); |
| + return completer.future; |
| + } |
| + } |
| + |
| + void releaseDevice(AdbDevice device) { |
| + if (_waiter.length > 0) { |
| + Completer completer = _waiter.removeFirst(); |
| + completer.complete(device); |
| + } else { |
| + _idleDevices.add(device); |
| + } |
| + } |
| +} |