Index: tools/testing/dart/android.dart |
diff --git a/tools/testing/dart/android.dart b/tools/testing/dart/android.dart |
index 1d390715733707bd716b615c676b5b17a7e6d3ee..e6ab95e27258e0afe4f5c43ba3da162b9de0dd2b 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(' ')}"; |
+ 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); |
+ } |
+ } |
+} |