| /* |
| * 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.cts.tradefed.testtype; |
| |
| import com.android.ddmlib.AdbCommandRejectedException; |
| import com.android.ddmlib.IShellEnabledDevice; |
| import com.android.ddmlib.Log; |
| import com.android.ddmlib.ShellCommandUnresponsiveException; |
| import com.android.ddmlib.TimeoutException; |
| import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; |
| import com.android.ddmlib.testrunner.ITestRunListener; |
| import com.android.ddmlib.testrunner.InstrumentationResultParser; |
| |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Hashtable; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.TimeUnit; |
| |
| public class PrintTestRemoteTestRunner implements IRemoteAndroidTestRunner { |
| |
| private final String mPackageName; |
| private final String mRunnerName; |
| private IShellEnabledDevice mRemoteDevice; |
| // default to no timeout |
| private long mMaxTimeToOutputResponse = 0; |
| private TimeUnit mMaxTimeUnits = TimeUnit.MILLISECONDS; |
| private String mRunName = null; |
| |
| /** map of name-value instrumentation argument pairs */ |
| private Map<String, String> mArgMap; |
| private InstrumentationResultParser mParser; |
| |
| private static final String LOG_TAG = "RemoteAndroidTest"; |
| private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner"; |
| |
| private static final char CLASS_SEPARATOR = ','; |
| private static final char METHOD_SEPARATOR = '#'; |
| private static final char RUNNER_SEPARATOR = '/'; |
| |
| // defined instrumentation argument names |
| private static final String CLASS_ARG_NAME = "class"; |
| private static final String LOG_ARG_NAME = "log"; |
| private static final String DEBUG_ARG_NAME = "debug"; |
| private static final String COVERAGE_ARG_NAME = "coverage"; |
| private static final String PACKAGE_ARG_NAME = "package"; |
| private static final String SIZE_ARG_NAME = "size"; |
| |
| // This command starts a shell Java program (installed by this class) |
| // in the folder owned by the shell user. This app creates a proxy |
| // which does privileged operations such as wiping a package's user |
| // data and then starts the tests passing the proxy. This enables |
| // the tests to clear the print spooler data. |
| private static final String INSTRUMENTATION_COMMAND = |
| "chmod 755 /data/local/tmp/print-instrument && " |
| + "/data/local/tmp/print-instrument instrument -w -r %1$s %2$s"; |
| |
| /** |
| * Creates a remote Android test runner. |
| * |
| * @param packageName the Android application package that contains the |
| * tests to run |
| * @param runnerName the instrumentation test runner to execute. If null, |
| * will use default runner |
| * @param remoteDevice the Android device to execute tests on |
| */ |
| public PrintTestRemoteTestRunner(String packageName, String runnerName, |
| IShellEnabledDevice remoteDevice) { |
| |
| mPackageName = packageName; |
| mRunnerName = runnerName; |
| mRemoteDevice = remoteDevice; |
| mArgMap = new Hashtable<String, String>(); |
| } |
| |
| /** |
| * Alternate constructor. Uses default instrumentation runner. |
| * |
| * @param packageName the Android application package that contains the |
| * tests to run |
| * @param remoteDevice the Android device to execute tests on |
| */ |
| public PrintTestRemoteTestRunner(String packageName, IShellEnabledDevice remoteDevice) { |
| this(packageName, null, remoteDevice); |
| } |
| |
| @Override |
| public String getPackageName() { |
| return mPackageName; |
| } |
| |
| @Override |
| public String getRunnerName() { |
| if (mRunnerName == null) { |
| return DEFAULT_RUNNER_NAME; |
| } |
| return mRunnerName; |
| } |
| |
| /** |
| * Returns the complete instrumentation component path. |
| */ |
| private String getRunnerPath() { |
| return getPackageName() + RUNNER_SEPARATOR + getRunnerName(); |
| } |
| |
| @Override |
| public void setClassName(String className) { |
| addInstrumentationArg(CLASS_ARG_NAME, className); |
| } |
| |
| @Override |
| public void setClassNames(String[] classNames) { |
| StringBuilder classArgBuilder = new StringBuilder(); |
| |
| for (int i = 0; i < classNames.length; i++) { |
| if (i != 0) { |
| classArgBuilder.append(CLASS_SEPARATOR); |
| } |
| classArgBuilder.append(classNames[i]); |
| } |
| setClassName(classArgBuilder.toString()); |
| } |
| |
| @Override |
| public void setMethodName(String className, String testName) { |
| setClassName(className + METHOD_SEPARATOR + testName); |
| } |
| |
| @Override |
| public void setTestPackageName(String packageName) { |
| addInstrumentationArg(PACKAGE_ARG_NAME, packageName); |
| } |
| |
| @Override |
| public void addInstrumentationArg(String name, String value) { |
| if (name == null || value == null) { |
| throw new IllegalArgumentException("name or value arguments cannot be null"); |
| } |
| mArgMap.put(name, value); |
| } |
| |
| @Override |
| public void removeInstrumentationArg(String name) { |
| if (name == null) { |
| throw new IllegalArgumentException("name argument cannot be null"); |
| } |
| mArgMap.remove(name); |
| } |
| |
| @Override |
| public void addBooleanArg(String name, boolean value) { |
| addInstrumentationArg(name, Boolean.toString(value)); |
| } |
| |
| @Override |
| public void setLogOnly(boolean logOnly) { |
| addBooleanArg(LOG_ARG_NAME, logOnly); |
| } |
| |
| @Override |
| public void setDebug(boolean debug) { |
| addBooleanArg(DEBUG_ARG_NAME, debug); |
| } |
| |
| @Override |
| public void setCoverage(boolean coverage) { |
| addBooleanArg(COVERAGE_ARG_NAME, coverage); |
| } |
| |
| @Override |
| public void setTestSize(TestSize size) { |
| addInstrumentationArg(SIZE_ARG_NAME, ""/*size.getRunnerValue()*/); |
| } |
| |
| @Override |
| public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) { |
| setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS); |
| } |
| |
| @Override |
| public void setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits) { |
| mMaxTimeToOutputResponse = maxTimeToOutputResponse; |
| mMaxTimeUnits = maxTimeUnits; |
| } |
| |
| @Override |
| public void setRunName(String runName) { |
| mRunName = runName; |
| } |
| |
| @Override |
| public void run(ITestRunListener... listeners) throws TimeoutException, |
| AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException { |
| run(Arrays.asList(listeners)); |
| } |
| |
| @Override |
| public void run(Collection<ITestRunListener> listeners) throws TimeoutException, |
| AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException { |
| final String runCaseCommandStr = String.format(INSTRUMENTATION_COMMAND, |
| getArgsCommand(), getRunnerPath()); |
| Log.i(LOG_TAG, |
| String.format("Running %1$s on %2$s", runCaseCommandStr, mRemoteDevice.getName())); |
| String runName = mRunName == null ? mPackageName : mRunName; |
| mParser = new InstrumentationResultParser(runName, listeners); |
| |
| try { |
| mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser, mMaxTimeToOutputResponse, |
| mMaxTimeUnits); |
| } catch (IOException e) { |
| Log.w(LOG_TAG, String.format("IOException %1$s when running tests %2$s on %3$s", |
| e.toString(), getPackageName(), mRemoteDevice.getName())); |
| // rely on parser to communicate results to listeners |
| mParser.handleTestRunFailed(e.toString()); |
| throw e; |
| } catch (ShellCommandUnresponsiveException e) { |
| Log.w(LOG_TAG, String.format( |
| "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s", |
| e.toString(), getPackageName(), mRemoteDevice.getName())); |
| mParser.handleTestRunFailed(String |
| .format("Failed to receive adb shell test output within %1$d ms. " |
| + "Test may have timed out, or adb connection to device became" |
| + "unresponsive", mMaxTimeToOutputResponse)); |
| throw e; |
| } catch (TimeoutException e) { |
| Log.w(LOG_TAG, String.format("TimeoutException when running tests %1$s on %2$s", |
| getPackageName(), mRemoteDevice.getName())); |
| mParser.handleTestRunFailed(e.toString()); |
| throw e; |
| } catch (AdbCommandRejectedException e) { |
| Log.w(LOG_TAG, String.format( |
| "AdbCommandRejectedException %1$s when running tests %2$s on %3$s", |
| e.toString(), getPackageName(), mRemoteDevice.getName())); |
| mParser.handleTestRunFailed(e.toString()); |
| throw e; |
| } |
| } |
| |
| @Override |
| public void cancel() { |
| if (mParser != null) { |
| mParser.cancel(); |
| } |
| } |
| |
| /** |
| * Returns the full instrumentation command line syntax for the provided |
| * instrumentation arguments. Returns an empty string if no arguments were |
| * specified. |
| */ |
| private String getArgsCommand() { |
| StringBuilder commandBuilder = new StringBuilder(); |
| for (Entry<String, String> argPair : mArgMap.entrySet()) { |
| final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(), |
| argPair.getValue()); |
| commandBuilder.append(argCmd); |
| } |
| return commandBuilder.toString(); |
| } |
| } |