Home | History | Annotate | Download | only in testtype
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.cts.tradefed.testtype;
     18 
     19 import com.android.ddmlib.AdbCommandRejectedException;
     20 import com.android.ddmlib.IShellEnabledDevice;
     21 import com.android.ddmlib.Log;
     22 import com.android.ddmlib.ShellCommandUnresponsiveException;
     23 import com.android.ddmlib.TimeoutException;
     24 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
     25 import com.android.ddmlib.testrunner.ITestRunListener;
     26 import com.android.ddmlib.testrunner.InstrumentationResultParser;
     27 
     28 import java.io.IOException;
     29 import java.util.Arrays;
     30 import java.util.Collection;
     31 import java.util.Hashtable;
     32 import java.util.Map;
     33 import java.util.Map.Entry;
     34 import java.util.concurrent.TimeUnit;
     35 
     36 public class PrintTestRemoteTestRunner implements IRemoteAndroidTestRunner {
     37 
     38     private final String mPackageName;
     39     private final String mRunnerName;
     40     private IShellEnabledDevice mRemoteDevice;
     41     // default to no timeout
     42     private long mMaxTimeToOutputResponse = 0;
     43     private TimeUnit mMaxTimeUnits = TimeUnit.MILLISECONDS;
     44     private String mRunName = null;
     45 
     46     /** map of name-value instrumentation argument pairs */
     47     private Map<String, String> mArgMap;
     48     private InstrumentationResultParser mParser;
     49 
     50     private static final String LOG_TAG = "RemoteAndroidTest";
     51     private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
     52 
     53     private static final char CLASS_SEPARATOR = ',';
     54     private static final char METHOD_SEPARATOR = '#';
     55     private static final char RUNNER_SEPARATOR = '/';
     56 
     57     // defined instrumentation argument names
     58     private static final String CLASS_ARG_NAME = "class";
     59     private static final String LOG_ARG_NAME = "log";
     60     private static final String DEBUG_ARG_NAME = "debug";
     61     private static final String COVERAGE_ARG_NAME = "coverage";
     62     private static final String PACKAGE_ARG_NAME = "package";
     63     private static final String SIZE_ARG_NAME = "size";
     64 
     65     // This command starts a shell Java program (installed by this class)
     66     // in the folder owned by the shell user. This app creates a proxy
     67     // which does privileged operations such as wiping a package's user
     68     // data and then starts the tests passing the proxy. This enables
     69     // the tests to clear the print spooler data.
     70     private static final String INSTRUMENTATION_COMMAND =
     71             "chmod 755 /data/local/tmp/print-instrument && "
     72             + "/data/local/tmp/print-instrument instrument -w -r %1$s %2$s";
     73 
     74     /**
     75      * Creates a remote Android test runner.
     76      *
     77      * @param packageName the Android application package that contains the
     78      *            tests to run
     79      * @param runnerName the instrumentation test runner to execute. If null,
     80      *            will use default runner
     81      * @param remoteDevice the Android device to execute tests on
     82      */
     83     public PrintTestRemoteTestRunner(String packageName, String runnerName,
     84             IShellEnabledDevice remoteDevice) {
     85 
     86         mPackageName = packageName;
     87         mRunnerName = runnerName;
     88         mRemoteDevice = remoteDevice;
     89         mArgMap = new Hashtable<String, String>();
     90     }
     91 
     92     /**
     93      * Alternate constructor. Uses default instrumentation runner.
     94      *
     95      * @param packageName the Android application package that contains the
     96      *            tests to run
     97      * @param remoteDevice the Android device to execute tests on
     98      */
     99     public PrintTestRemoteTestRunner(String packageName, IShellEnabledDevice remoteDevice) {
    100         this(packageName, null, remoteDevice);
    101     }
    102 
    103     @Override
    104     public String getPackageName() {
    105         return mPackageName;
    106     }
    107 
    108     @Override
    109     public String getRunnerName() {
    110         if (mRunnerName == null) {
    111             return DEFAULT_RUNNER_NAME;
    112         }
    113         return mRunnerName;
    114     }
    115 
    116     /**
    117      * Returns the complete instrumentation component path.
    118      */
    119     private String getRunnerPath() {
    120         return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
    121     }
    122 
    123     @Override
    124     public void setClassName(String className) {
    125         addInstrumentationArg(CLASS_ARG_NAME, className);
    126     }
    127 
    128     @Override
    129     public void setClassNames(String[] classNames) {
    130         StringBuilder classArgBuilder = new StringBuilder();
    131 
    132         for (int i = 0; i < classNames.length; i++) {
    133             if (i != 0) {
    134                 classArgBuilder.append(CLASS_SEPARATOR);
    135             }
    136             classArgBuilder.append(classNames[i]);
    137         }
    138         setClassName(classArgBuilder.toString());
    139     }
    140 
    141     @Override
    142     public void setMethodName(String className, String testName) {
    143         setClassName(className + METHOD_SEPARATOR + testName);
    144     }
    145 
    146     @Override
    147     public void setTestPackageName(String packageName) {
    148         addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
    149     }
    150 
    151     @Override
    152     public void addInstrumentationArg(String name, String value) {
    153         if (name == null || value == null) {
    154             throw new IllegalArgumentException("name or value arguments cannot be null");
    155         }
    156         mArgMap.put(name, value);
    157     }
    158 
    159     @Override
    160     public void removeInstrumentationArg(String name) {
    161         if (name == null) {
    162             throw new IllegalArgumentException("name argument cannot be null");
    163         }
    164         mArgMap.remove(name);
    165     }
    166 
    167     @Override
    168     public void addBooleanArg(String name, boolean value) {
    169         addInstrumentationArg(name, Boolean.toString(value));
    170     }
    171 
    172     @Override
    173     public void setLogOnly(boolean logOnly) {
    174         addBooleanArg(LOG_ARG_NAME, logOnly);
    175     }
    176 
    177     @Override
    178     public void setDebug(boolean debug) {
    179         addBooleanArg(DEBUG_ARG_NAME, debug);
    180     }
    181 
    182     @Override
    183     public void setCoverage(boolean coverage) {
    184         addBooleanArg(COVERAGE_ARG_NAME, coverage);
    185     }
    186 
    187     @Override
    188     public void setTestSize(TestSize size) {
    189         addInstrumentationArg(SIZE_ARG_NAME, ""/*size.getRunnerValue()*/);
    190     }
    191 
    192     @Override
    193     public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) {
    194         setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
    195     }
    196 
    197     @Override
    198     public void setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits) {
    199         mMaxTimeToOutputResponse = maxTimeToOutputResponse;
    200         mMaxTimeUnits = maxTimeUnits;
    201     }
    202 
    203     @Override
    204     public void setRunName(String runName) {
    205         mRunName = runName;
    206     }
    207 
    208     @Override
    209     public void run(ITestRunListener... listeners) throws TimeoutException,
    210             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
    211         run(Arrays.asList(listeners));
    212     }
    213 
    214     @Override
    215     public void run(Collection<ITestRunListener> listeners) throws TimeoutException,
    216             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
    217         final String runCaseCommandStr = String.format(INSTRUMENTATION_COMMAND,
    218               getArgsCommand(), getRunnerPath());
    219         Log.i(LOG_TAG,
    220                 String.format("Running %1$s on %2$s", runCaseCommandStr, mRemoteDevice.getName()));
    221         String runName = mRunName == null ? mPackageName : mRunName;
    222         mParser = new InstrumentationResultParser(runName, listeners);
    223 
    224         try {
    225             mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser, mMaxTimeToOutputResponse,
    226                     mMaxTimeUnits);
    227         } catch (IOException e) {
    228             Log.w(LOG_TAG, String.format("IOException %1$s when running tests %2$s on %3$s",
    229                     e.toString(), getPackageName(), mRemoteDevice.getName()));
    230             // rely on parser to communicate results to listeners
    231             mParser.handleTestRunFailed(e.toString());
    232             throw e;
    233         } catch (ShellCommandUnresponsiveException e) {
    234             Log.w(LOG_TAG, String.format(
    235                     "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
    236                     e.toString(), getPackageName(), mRemoteDevice.getName()));
    237             mParser.handleTestRunFailed(String
    238                     .format("Failed to receive adb shell test output within %1$d ms. "
    239                             + "Test may have timed out, or adb connection to device became"
    240                             + "unresponsive", mMaxTimeToOutputResponse));
    241             throw e;
    242         } catch (TimeoutException e) {
    243             Log.w(LOG_TAG, String.format("TimeoutException when running tests %1$s on %2$s",
    244                     getPackageName(), mRemoteDevice.getName()));
    245             mParser.handleTestRunFailed(e.toString());
    246             throw e;
    247         } catch (AdbCommandRejectedException e) {
    248             Log.w(LOG_TAG, String.format(
    249                     "AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
    250                     e.toString(), getPackageName(), mRemoteDevice.getName()));
    251             mParser.handleTestRunFailed(e.toString());
    252             throw e;
    253         }
    254     }
    255 
    256     @Override
    257     public void cancel() {
    258         if (mParser != null) {
    259             mParser.cancel();
    260         }
    261     }
    262 
    263     /**
    264      * Returns the full instrumentation command line syntax for the provided
    265      * instrumentation arguments. Returns an empty string if no arguments were
    266      * specified.
    267      */
    268     private String getArgsCommand() {
    269         StringBuilder commandBuilder = new StringBuilder();
    270         for (Entry<String, String> argPair : mArgMap.entrySet()) {
    271             final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(),
    272                     argPair.getValue());
    273             commandBuilder.append(argCmd);
    274         }
    275         return commandBuilder.toString();
    276     }
    277 }
    278