Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(150)

Unified Diff: tools/testing/dart/android.dart

Issue 15567002: Added support for running dart2js tests on android devices (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/testing/dart/android.dart
diff --git a/tools/testing/dart/android.dart b/tools/testing/dart/android.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c28cdfc77b29748cc65dc6a16a6a578193d4cdeb
--- /dev/null
+++ b/tools/testing/dart/android.dart
@@ -0,0 +1,323 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library android;
+
+import "dart:io";
+import "dart:async";
+import "dart:core";
+import "dart:utf";
+
+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((results) => results[0]);
+}
+
+/**
+ * [_executeCommandRaw] 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<String> getOutput(Stream<List<int>> stream) {
+ return stream.transform(new StringDecoder())
+ .reduce(new StringBuffer(), (buf, data) {
+ buf.write(data);
ricow1 2013/05/21 18:30:43 indentation seems off here, hard to follow flow
kustermann 2013/05/22 09:30:58 Done.
+ return buf;
+ }).then((buf) => buf.toString());
+ }
+
+ DebugLogger.info("Running: '\$ $executable ${args.join(' ')}'");
+ return Process.start(executable, args).then((Process process) {
+ 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]}
ricow1 2013/05/21 18:30:43 this may look sort of strange due to the whitespac
kustermann 2013/05/22 09:30:58 Done.
+ stderr: \n ${results[1]}
+ exitCode: \n ${results[2]}
+ """;
+ throw new Exception(error);
+ }
+ return [results[0], results[1]];
ricow1 2013/05/21 18:30:43 how about a ProcessResult like class for returning
kustermann 2013/05/22 09:30:58 I think there is no public ProcessResult construct
ricow1 2013/05/22 13:13:36 That is why I wrote ProcessResult _like_ class :-)
+ });
+ });
+}
+
+/**
+ * Helper class to loop through all adb ports.
+ *
+ * The ports come in pairs:
+ * - even number: console connection
+ * - odd number: adb connection
+ * Note that this code doesn't check if the ports are used.
+ */
+class AdbServerPortPool {
+ static int MIN_PORT = 5554;
+ static int MAX_PORT = 5584;
+
+ static int _nextPort = MIN_PORT;
+
+ static int next() {
+ var port = _nextPort;
+ if (port > MAX_PORT) {
+ throw new Exception("All ports are used.");
+ }
+ _nextPort += 2;
+ return port;
+ }
+}
+
+/**
+ * Represents the interface to the emulator.
+ * New emulators can be launched by calling the static [launchNewEmulator]
+ * method.
+ */
+class AndroidEmulator {
+ int _port;
+ Process _emulatorProcess;
+ AdbDevice _adbDevice;
+
+ int get port => _port;
+
+ AdbDevice get adbDevice => _adbDevice;
+
+ static Future<AndroidEmulator> launchNewEmulator(String avdName) {
+ var portNumber = AdbServerPortPool.next();
+ var args = ['-avd', '$avdName', '-port', "$portNumber" /*, '-gpu', 'on'*/];
+ return Process.start("emulator64-arm", args).then((Process process) {
+ var adbDevice = new AdbDevice('emulator-$portNumber');
+ return new AndroidEmulator(portNumber, adbDevice, process);
+ });
+ }
+
+ AndroidEmulator(this._port, this._adbDevice, this._emulatorProcess) {
ricow1 2013/05/21 18:30:43 since you are using private methods and instance v
kustermann 2013/05/22 09:30:58 Done.
+ Stream<String> getLines(Stream s) {
+ return s.transform(new StringDecoder()).transform(new LineTransformer());
+ }
+
+ getLines(_emulatorProcess.stdout).listen((line) {
+ log("stdout: ${line.trim()}");
+ });
+ getLines(_emulatorProcess.stderr).listen((line) {
+ log("stderr: ${line.trim()}");
+ });
+ _emulatorProcess.exitCode.then((exitCode) {
+ log("emulator exited with exitCode: $exitCode.");
+ });
+ }
+
+ Future<bool> kill() {
+ var completer = new Completer();
+ if (_emulatorProcess.kill()) {
+ _emulatorProcess.exitCode.then((exitCode) {
+ completer.complete(true);
+ });
+ } else {
+ log("Sending kill signal to emulator process failed");
+ completer.complete(false);
+ }
+ return completer.future;
+ }
+
+ void log(String msg) {
+ DebugLogger.info("AndroidEmulator(${_adbDevice.deviceId}): $msg");
+ }
+}
+
+/**
+ * Helper class to create avd device configurations.
+ */
+class AndroidHelper {
+ static Future createAvd(String name, String target) {
+ var args = ['--silent', 'create', 'avd', '--name', '$name',
+ '--target', '$target', '--force', '--abi', 'armeabi-v7a'];
+ return _executeCommand("android", args, "\n\n\n\n");
ricow1 2013/05/21 18:30:43 add a comment stating why we add 4 new lines to st
kustermann 2013/05/22 09:30:58 Done. I don't remember if we actually need 4 of th
+ }
+}
+
+/**
+ * Used for communicating with an emulator or with a real device.
+ */
+class AdbDevice {
+ static const _adbServerStartupTime = const Duration(seconds: 3);
+ String _deviceId;
+
+ String get deviceId => _deviceId;
+
+ AdbDevice(this._deviceId);
+
+ /**
+ * Blocks execution until the device is online
+ */
+ Future waitForDevice() {
+ return _adbCommand(['wait-for-device']);
+ }
+
+ /**
+ * 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);
+ });
+ }
+ checkUntilBooted();
+ return completer.future;
+ }
+
+ /**
+ * Put adb in root mode.
+ */
+ Future adbRoot() {
+ var adbRootCompleter = new Completer();
+ return _adbCommand(['root']).then((_) {
+ new Timer(_adbServerStartupTime, () => adbRootCompleter.complete(true));
+ }).catchError((error) => adbRootCompleter.completeError(error));
+ return adbRootCompleter.future;
+ }
+
+ /**
+ * Download data form the device.
+ */
+ Future pullData(Path remote, Path local) {
+ return _adbCommand(['pull', '$remote', '$local']);
+ }
+
+ /**
+ * Upload data to the device.
+ */
+ Future pushData(Path local, Path remote) {
+ return _adbCommand(['push', '$local', '$remote']);
+ }
+
+ /**
+ * Change permission of directory recursively.
+ */
+ Future chmod(String mode, Path directory) {
+ var arguments = ['shell', 'chmod', '-R', mode, '$directory'];
+ return _adbCommand(arguments);
+ }
+
+ /**
+ * Install an application on the device.
+ */
+ Future installApk(Path filename) {
+ return _adbCommand(
+ ['install', '-i', 'com.google.android.feedback', '-r', '$filename']);
+ }
+
+ /**
+ * Start the given intent on the device.
+ */
+ Future startActivity(Intent intent) {
+ var arguments = ['shell', 'am', 'start', '-W',
+ '-a', intent.action,
+ '-n', "${intent.package}/${intent.activity}"];
+ if (intent.dataUri != null) {
+ arguments.addAll(['-d', intent.dataUri]);
+ }
+ return _adbCommand(arguments);
+ }
+
+ /**
+ * Force to stop everything associated with [package].
+ */
+ Future forceStop(String package) {
+ var arguments = ['shell', 'am', 'force-stop', package];
+ return _adbCommand(arguments);
+ }
+
+ /**
+ * Kill all background processes.
+ */
+ Future killAll() {
+ var arguments = ['shell', 'am', 'kill-all'];
+ return _adbCommand(arguments);
+ }
+
+ Future _adbCommand(List<String> adbArgs) {
+ if (_deviceId != null) {
+ var extendedAdbArgs = ['-s', _deviceId];
+ extendedAdbArgs.addAll(adbArgs);
+ adbArgs = extendedAdbArgs;
+ }
+ return _executeCommand("adb", adbArgs);
+ }
+
+ Future<String> _adbCommandGetOutput(List<String> adbArgs) {
+ if (_deviceId != null) {
+ var extendedAdbArgs = ['-s', _deviceId];
+ extendedAdbArgs.addAll(adbArgs);
+ adbArgs = extendedAdbArgs;
+ }
+ return _executeCommandGetOutput("adb", adbArgs);
+ }
+}
+
+/**
+ * Helper to list all adb devicess available.
+ */
+class AdbHelper {
+ static RegExp _deviceLineRegexp =
+ new RegExp(r'^([a-zA-Z0-9_-]+)[ \t]+device$', multiLine: true);
+
+ static Future<List<String>> listDevices() {
+ return Process.run('adb', ['devices']).then((ProcessResult result) {
+ if (result.exitCode != 0) {
+ throw new Exception("Could not list devices [stdout: ${result.stdout},"
+ "stderr: ${result.stderr}]");
+ }
+ return _deviceLineRegexp.allMatches(result.stdout).map((Match m) => m.group(1)).toList();
ricow1 2013/05/21 18:30:43 long line
kustermann 2013/05/22 09:30:58 Done.
+ });
+ }
+}
+
+/**
+ * Represents an android intent.
+ */
+class Intent {
+ String action;
+ String package;
+ String activity;
+ String dataUri;
+
+ Intent(this.action, this.package, this.activity, [this.dataUri]);
+}
+

Powered by Google App Engine
This is Rietveld 408576698