diff options
Diffstat (limited to 'jack-tests/src/com/android/jack/test/runner/DeviceRunner.java')
-rw-r--r-- | jack-tests/src/com/android/jack/test/runner/DeviceRunner.java | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/jack-tests/src/com/android/jack/test/runner/DeviceRunner.java b/jack-tests/src/com/android/jack/test/runner/DeviceRunner.java new file mode 100644 index 0000000..dbb9203 --- /dev/null +++ b/jack-tests/src/com/android/jack/test/runner/DeviceRunner.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.test.runner; + +import com.google.common.base.Joiner; + +import com.android.ddmlib.AdbCommandRejectedException; +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.IShellOutputReceiver; +import com.android.ddmlib.ShellCommandUnresponsiveException; +import com.android.ddmlib.SyncException; +import com.android.ddmlib.TimeoutException; +import com.android.jack.test.toolchain.AbstractTestTools; +import com.android.jack.test.toolchain.TestConfigurationException; +import com.android.jack.test.util.ExecFileException; +import com.android.jack.test.util.ExecuteFile; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Nonnull; + +/** + * This runner is used to execute tests on a device. + */ +public abstract class DeviceRunner extends AbstractRuntimeRunner { + + @Nonnull + public static final File ROOT_DIR = new File("/system"); + @Nonnull + public static final File ANDROID_DATA_DIR = new File("/data"); + + private static final long ADB_CONNECTION_TIMEOUT = 5000; + private static final long ADB_WAIT_STEP = ADB_CONNECTION_TIMEOUT / 10; + + @Nonnull + private MyShellOuputReceiver shellOutput = new MyShellOuputReceiver(); + + private class MyShellOuputReceiver implements IShellOutputReceiver { + + @Override + public void addOutput(@Nonnull byte[] data, int offset, int length) { + outRedirectStream.println(new String(Arrays.copyOfRange(data, offset, offset + length))); + } + + @Override + public void flush() { + } + + @Override + public boolean isCancelled() { + return false; + } + } + + public DeviceRunner() { + super(ROOT_DIR); + try { + AndroidDebugBridge.init(/* clientSupport */ false); + } catch (IllegalStateException ex) { + // ADB was already initialized, we're fine, so just ignore. + } + } + + private class ShellOutputToStringReceiver implements IShellOutputReceiver { + + @Nonnull + StringBuffer out = new StringBuffer(); + + @Override + public void addOutput(@Nonnull byte[] data, int offset, int length) { + out.append(new String(Arrays.copyOfRange(data, offset, offset + length))); + } + + @Override + public void flush() { + } + + @Override + public boolean isCancelled() { + return false; + } + + @Nonnull + public String getOutput() { + return out.toString(); + } + } + + protected int runOnDevice(@Nonnull String[] options, @Nonnull String[] mainClasses, + @Nonnull File... classpathFiles) + throws RuntimeRunnerException { + + // Assumes adb is in PATH + AndroidDebugBridge adb = AndroidDebugBridge.createBridge("adb", false); + + long start = System.currentTimeMillis(); + + if (isVerbose) { + outRedirectStream.println("Initializing adb..."); + } + + while (!isAdbInitialized(adb)) { + long timeLeft = start + ADB_CONNECTION_TIMEOUT - System.currentTimeMillis(); + if (timeLeft <= 0) { + break; + } + try { + Thread.sleep(ADB_WAIT_STEP); + } catch (InterruptedException e) { + throw new RuntimeRunnerException(e); + } + } + if (!isAdbInitialized(adb)) { + throw new RuntimeRunnerException("adb is not initialized"); + } + + if (isVerbose) { + outRedirectStream.println("Done"); + } + + IDevice[] connectedDevices = adb.getDevices(); + + if (connectedDevices.length == 0) { + throw new RuntimeRunnerException("No device found"); + } + + int exitStatus = -1; + for (IDevice device : connectedDevices) { + + checkDeviceRuntime(device); + + if (isVerbose) { + outRedirectStream.println("Running on device: " + device.getName()); + } + + ensureAdbRoot(device); + + File testsRootDir = new File(device.getMountPoint(IDevice.MNT_DATA) + "/jack-tests"); + File[] desFilePaths = new File[classpathFiles.length]; + try { + if (isVerbose) { + outRedirectStream.println("adb shell -s " + device.getSerialNumber() + " mkdir " + + testsRootDir.getAbsolutePath()); + } + device.executeShellCommand("mkdir " + testsRootDir.getAbsolutePath(), shellOutput); + + if (isVerbose) { + outRedirectStream.println("adb -s " + device.getSerialNumber() + " push " + + System.getProperty("user.dir") + File.separator + "test-exit-status.sh " + + testsRootDir.getAbsolutePath() + "/test-exit-status.sh"); + } + device.pushFile(System.getProperty("user.dir") + File.separator + "test-exit-status.sh", + testsRootDir.getAbsolutePath() + "/test-exit-status.sh"); + + if (isVerbose) { + outRedirectStream.println("adb -s " + device.getSerialNumber() + " shell chmod 777 " + + testsRootDir.getAbsolutePath() + "/test-exit-status.sh"); + } + device.executeShellCommand( + "chmod 777 " + testsRootDir.getAbsolutePath() + "/test-exit-status.sh", shellOutput); + + int i = 0; + for (File f : classpathFiles) { + desFilePaths[i] = new File(testsRootDir, "f" + i + "_" + f.getName()); + + if (isVerbose) { + outRedirectStream.println("adb -s " + device.getSerialNumber() + " push " + + f.getAbsolutePath() + " " + desFilePaths[i].getAbsolutePath()); + } + device.pushFile(f.getAbsolutePath(), desFilePaths[i].getAbsolutePath()); + i++; + } + } catch (TimeoutException e) { + throw new RuntimeRunnerException(e); + } catch (AdbCommandRejectedException e) { + throw new RuntimeRunnerException(e); + } catch (ShellCommandUnresponsiveException e) { + throw new RuntimeRunnerException(e); + } catch (IOException e) { + throw new RuntimeRunnerException(e); + } catch (SyncException e) { + throw new RuntimeRunnerException(e); + } + + String args = Joiner.on(' ').join(buildCommandLine(options, mainClasses, desFilePaths)); + + try { + // Bug : exit code return by adb shell is wrong (always 0) + // https://code.google.com/p/android/issues/detail?id=3254 + // Use go team hack to work this around + // https://code.google.com/p/go/source/browse/misc/arm/a + + if (isVerbose) { + outRedirectStream.println("adb -s " + device.getSerialNumber() + " shell " + + testsRootDir.getAbsolutePath() + "/test-exit-status.sh " + args); + } + device.executeShellCommand( + testsRootDir.getAbsolutePath() + "/test-exit-status.sh " + args, + shellOutput); + + File exitStatusFile = AbstractTestTools.createTempFile("exitStatus", ""); + if (isVerbose) { + outRedirectStream.println("adb -s " + device.getSerialNumber() + " pull " + + testsRootDir.getAbsolutePath() + "/exitStatus " + exitStatusFile.getAbsolutePath()); + } + device.pullFile(testsRootDir.getAbsolutePath() + "/exitStatus", + exitStatusFile.getAbsolutePath()); + + BufferedReader br = new BufferedReader(new FileReader(exitStatusFile)); + try { + String readLine = br.readLine(); + if (readLine == null) { + throw new RuntimeRunnerException("Exit status not found"); + } + exitStatus = Integer.parseInt(readLine); + } finally { + br.close(); + } + + if (isVerbose) { + outRedirectStream.println("Exit status: " + exitStatus); + } + + for (File pushedFile : desFilePaths) { + if (isVerbose) { + outRedirectStream.println( + "adb -s " + device.getSerialNumber() + "rm " + pushedFile.getAbsolutePath()); + } + device.executeShellCommand("rm " + pushedFile.getAbsolutePath(), shellOutput); + } + + if (exitStatus != 0) { + errRedirectStream.println("Execution failed on device '" + device.getName() + "'"); + break; + } + + } catch (TimeoutException e) { + throw new RuntimeRunnerException(e); + } catch (AdbCommandRejectedException e) { + throw new RuntimeRunnerException(e); + } catch (ShellCommandUnresponsiveException e) { + throw new RuntimeRunnerException(e); + } catch (IOException e) { + throw new RuntimeRunnerException(e); + } catch (SyncException e) { + throw new RuntimeRunnerException(e); + } + } + + return exitStatus; + } + + @Nonnull + protected abstract List<String> buildCommandLine(@Nonnull String[] options, + @Nonnull String[] mainClasses, @Nonnull File... classpathFiles); + + private boolean isAdbInitialized(@Nonnull AndroidDebugBridge adb) { + return adb.isConnected() && adb.hasInitialDeviceList(); + } + + private void ensureAdbRoot(@Nonnull IDevice device) throws RuntimeRunnerException { + ShellOutputToStringReceiver outputToString = new ShellOutputToStringReceiver(); + try { + device.executeShellCommand("id", outputToString); + + if (!outputToString.getOutput().contains("uid=0(root)")) { + ExecuteFile ef; + + ef = new ExecuteFile("adb -s " + device.getSerialNumber() + " root"); + ef.setOut(System.out); + ef.setErr(System.err); + ef.setVerbose(isVerbose); + ef.run(); + + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + Thread.currentThread().interrupt(); + } + } + } catch (TimeoutException e1) { + throw new RuntimeRunnerException(e1); + } catch (AdbCommandRejectedException e1) { + throw new RuntimeRunnerException(e1); + } catch (ShellCommandUnresponsiveException e1) { + throw new RuntimeRunnerException(e1); + } catch (IOException e1) { + throw new RuntimeRunnerException(e1); + } catch (ExecFileException e) { + throw new RuntimeRunnerException("Error while executing 'adb root'", e); + } + } + + @Nonnull + protected abstract String getRuntimeName(); + + private void checkDeviceRuntime(@Nonnull IDevice device) throws RuntimeRunnerException { + ShellOutputToStringReceiver outputToString = new ShellOutputToStringReceiver(); + try { + device.executeShellCommand("dalvikvm -showversion", outputToString); + if (!outputToString.getOutput().contains(getRuntimeName())) { + throw new TestConfigurationException( + "The plugged device does not run the required runtime: '" + getRuntimeName() + "'"); + } + } catch (TimeoutException e) { + throw new RuntimeRunnerException(e); + } catch (AdbCommandRejectedException e) { + throw new RuntimeRunnerException(e); + } catch (ShellCommandUnresponsiveException e) { + throw new RuntimeRunnerException(e); + } catch (IOException e) { + throw new RuntimeRunnerException(e); + } + } + +} |