Home | History | Annotate | Download | only in testtype
      1 /*
      2  * Copyright (C) 2012 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.tradefed.testtype;
     18 
     19 import com.android.ddmlib.AdbCommandRejectedException;
     20 import com.android.ddmlib.IDevice;
     21 import com.android.ddmlib.ShellCommandUnresponsiveException;
     22 import com.android.ddmlib.TimeoutException;
     23 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
     24 import com.android.ddmlib.testrunner.ITestRunListener;
     25 import com.android.ddmlib.testrunner.InstrumentationResultParser;
     26 import com.android.tradefed.log.LogUtil.CLog;
     27 import com.android.tradefed.util.ArrayUtil;
     28 
     29 import java.io.IOException;
     30 import java.util.Arrays;
     31 import java.util.Collection;
     32 import java.util.LinkedHashMap;
     33 import java.util.Map;
     34 import java.util.Map.Entry;
     35 import java.util.concurrent.TimeUnit;
     36 
     37 /**
     38  * Runs UI Automator test on device and reports results.
     39  *
     40  * UI Automator test is a dedicated test runner for running UI automation tests that
     41  * utilizes UI Automator framework. The test runner on device emulates instrumentation
     42  * test output format so that existing parsing code in ddmlib and TF can be reused.
     43  *
     44  * Essentially, this is a wrapper around this command:
     45  * adb shell uiautomator runtest (jar files) -e class (test classes) ...
     46  *
     47  */
     48 public class UiAutomatorRunner implements IRemoteAndroidTestRunner {
     49 
     50     private static final String CLASS_ARG_NAME = "class";
     51     private static final String DEBUG_ARG_NAME = "debug";
     52     private static final String RUNNER_ARG_NAME = "runner";
     53     private static final char METHOD_SEPARATOR = '#';
     54     private static final char CLASS_SEPARATOR = ',';
     55     private static final String DEFAULT_RUNNER_NAME =
     56             "com.android.uiautomator.testrunner.UiAutomatorTestRunner";
     57     private static final String UIAUTOMATOR_RUNNER_PATH = "/system/bin/uiautomator";
     58 
     59     private Map<String, String> mArgsMap;
     60     private String[] mJarPaths;
     61     private String mPackageName;
     62     // default to no timeout
     63     private long mMaxTimeToOutputResponse = 0;
     64     private IDevice mRemoteDevice;
     65     private String mRunName;
     66     private InstrumentationResultParser mParser;
     67     private String mRunnerPath = UIAUTOMATOR_RUNNER_PATH;
     68     private String mRunnerName = DEFAULT_RUNNER_NAME;
     69     private boolean mIgnoreSighup = false;
     70 
     71     /**
     72      * Create a UiAutomatorRunner for running UI automation tests
     73      *
     74      * @param remoteDevice the remote device to interact with: run test, collect results etc
     75      * @param jarPaths the paths to jar files where UI Automator test cases are; the paths must be
     76      *                absolute or relative to /data/local/tmp/ on device
     77      * @param runnerPath alternative uiautomator runner to use, may be <code>null</code> and default
     78      *                   will be used in this case
     79      */
     80     public UiAutomatorRunner(IDevice remoteDevice, String[] jarPaths, String runnerPath) {
     81         mRemoteDevice = remoteDevice;
     82         mJarPaths = jarPaths;
     83         mArgsMap = new LinkedHashMap<String, String>(); // ensure the order that the args are added
     84         if (runnerPath != null) {
     85             mRunnerPath = runnerPath;
     86         }
     87     }
     88 
     89     /**
     90      * Returns the package name of last Java class added
     91      */
     92     @Override
     93     public String getPackageName() {
     94         return mPackageName;
     95     }
     96 
     97     /**
     98      * Returns default UiAutomatorTestRunner class name
     99      */
    100     @Override
    101     public String getRunnerName() {
    102         return mRunnerName;
    103     }
    104 
    105     protected String getRunnerPath() {
    106         return mRunnerPath;
    107     }
    108 
    109     /**
    110      * Sets the option in the uiautomator to ignore SIGHUP.
    111      * @param value ignore the signal if set to true
    112      */
    113     public void setIgnoreSighup(boolean value) {
    114         mIgnoreSighup = value;
    115     }
    116 
    117     protected String getRunCommand() {
    118         String jarArg = ArrayUtil.join(" ", (Object[])mJarPaths);
    119         String command = String.format("%s runtest %s %s",
    120                 getRunnerPath(), jarArg, getArgsCommand());
    121         if (mIgnoreSighup) {
    122             return command + " --nohup";
    123         } else {
    124             return command;
    125         }
    126     }
    127 
    128     /**
    129      * Returns the full instrumentation command line syntax for the provided instrumentation
    130      * arguments.
    131      * Returns an empty string if no arguments were specified.
    132      */
    133     private String getArgsCommand() {
    134         StringBuilder commandBuilder = new StringBuilder();
    135         for (Entry<String, String> argPair : mArgsMap.entrySet()) {
    136             final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(),
    137                     argPair.getValue());
    138             commandBuilder.append(argCmd);
    139         }
    140         return commandBuilder.toString();
    141     }
    142 
    143     /**
    144      * {@inheritDoc}
    145      */
    146     @Override
    147     public void setClassName(String className) {
    148         int pos = className.lastIndexOf('.');
    149         // get package name segment
    150         if (pos == -1) {
    151             mPackageName = "(default)";
    152         } else {
    153             mPackageName = className.substring(0, pos);
    154         }
    155         addInstrumentationArg(CLASS_ARG_NAME, className);
    156     }
    157 
    158     /**
    159      * {@inheritDoc}
    160      */
    161     @Override
    162     public void setClassNames(String[] classNames) {
    163         StringBuilder classArgBuilder = new StringBuilder();
    164 
    165         for (int i = 0; i < classNames.length; i++) {
    166             if (i != 0) {
    167                 classArgBuilder.append(CLASS_SEPARATOR);
    168             }
    169             classArgBuilder.append(classNames[i]);
    170         }
    171         setClassName(classArgBuilder.toString());
    172     }
    173 
    174     /**
    175      * {@inheritDoc}
    176      */
    177     @Override
    178     public void setMethodName(String className, String testName) {
    179         setClassName(className + METHOD_SEPARATOR + testName);
    180     }
    181 
    182     /**
    183      * {@inheritDoc}
    184      */
    185     @Override
    186     public void setTestPackageName(String packageName) {
    187         throw new UnsupportedOperationException("specifying package name is not supported");
    188     }
    189 
    190     /**
    191      * {@inheritDoc}
    192      */
    193     @Override
    194     public void setTestSize(TestSize size) {
    195         throw new UnsupportedOperationException("specifying test size is not supported");
    196     }
    197 
    198     /**
    199      * {@inheritDoc}
    200      */
    201     @Override
    202     public void addInstrumentationArg(String name, String value) {
    203         if (name == null) {
    204             throw new IllegalArgumentException("name cannot be null");
    205         }
    206         if (RUNNER_ARG_NAME.equals(name)) {
    207             mRunnerName = name;
    208         }
    209         mArgsMap.put(name, value);
    210     }
    211 
    212     /**
    213      * {@inheritDoc}
    214      */
    215     @Override
    216     public void removeInstrumentationArg(String name) {
    217         if (name == null) {
    218             throw new IllegalArgumentException("name cannot be null");
    219         }
    220         mArgsMap.remove(name);
    221     }
    222 
    223     /**
    224      * {@inheritDoc}
    225      */
    226     @Override
    227     public void addBooleanArg(String name, boolean value) {
    228         addInstrumentationArg(name, Boolean.toString(value));
    229     }
    230 
    231     /**
    232      * {@inheritDoc}
    233      */
    234     @Override
    235     public void setLogOnly(boolean logOnly) {
    236         //TODO: we need to support log only for Eclipse and re-run support
    237         throw new UnsupportedOperationException("log only mode is not supported");
    238     }
    239 
    240     /**
    241      * {@inheritDoc}
    242      */
    243     @Override
    244     public void setDebug(boolean debug) {
    245         addBooleanArg(DEBUG_ARG_NAME, debug);
    246     }
    247 
    248     /**
    249      * {@inheritDoc}
    250      */
    251     @Override
    252     public void setCoverage(boolean coverage) {
    253         // TODO potentially it's possible to run with coverage, need more investigation
    254         throw new UnsupportedOperationException("coverage mode is not supported");
    255     }
    256 
    257     @Override
    258     public void setTestCollection(boolean b) {
    259         throw new UnsupportedOperationException("Test Collection mode is not supported");
    260     }
    261 
    262     /**
    263      * {@inheritDoc}
    264      * @deprecated use {@link #setMaxTimeToOutputResponse(long, TimeUnit)} instead.
    265      */
    266     @Deprecated
    267     @Override
    268     public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) {
    269         setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
    270     }
    271 
    272     /**
    273      * {@inheritDoc}
    274      */
    275     @Override
    276     public void setMaxTimeToOutputResponse(long timeout, TimeUnit unit) {
    277         mMaxTimeToOutputResponse = unit.toMillis(timeout);
    278     }
    279 
    280     /**
    281      * {@inheritDoc}
    282      */
    283     @Override
    284     public void setRunName(String runName) {
    285         mRunName = runName;
    286     }
    287 
    288     /**
    289      * {@inheritDoc}
    290      */
    291     @Override
    292     public void run(ITestRunListener... listeners) throws TimeoutException,
    293             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
    294         run(Arrays.asList(listeners));
    295     }
    296 
    297     /**
    298      * {@inheritDoc}
    299      */
    300     @Override
    301     public void run(Collection<ITestRunListener> listeners) throws TimeoutException,
    302             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
    303         String cmdLine = getRunCommand();
    304         CLog.i("Running %s on %s", cmdLine, mRemoteDevice.getSerialNumber());
    305         String runName = mRunName == null ? mPackageName : mRunName;
    306         mParser = new InstrumentationResultParser(runName, listeners);
    307 
    308         try {
    309             mRemoteDevice.executeShellCommand(cmdLine,
    310                     mParser, mMaxTimeToOutputResponse, TimeUnit.MILLISECONDS);
    311         } catch (IOException e) {
    312             CLog.w(String.format("IOException %1$s when running tests %2$s on %3$s",
    313                     e.toString(), getPackageName(), mRemoteDevice.getSerialNumber()));
    314             // rely on parser to communicate results to listeners
    315             mParser.handleTestRunFailed(e.toString());
    316             throw e;
    317         } catch (ShellCommandUnresponsiveException e) {
    318             CLog.w("ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
    319                     e.toString(), getPackageName(), mRemoteDevice.getSerialNumber());
    320             mParser.handleTestRunFailed(String.format(
    321                     "Failed to receive adb shell test output within %1$d ms. " +
    322                     "Test may have timed out, or adb connection to device became unresponsive",
    323                     mMaxTimeToOutputResponse));
    324             throw e;
    325         } catch (TimeoutException e) {
    326             CLog.w("TimeoutException when running tests %1$s on %2$s", getPackageName(),
    327                     mRemoteDevice.getSerialNumber());
    328             mParser.handleTestRunFailed(e.toString());
    329             throw e;
    330         } catch (AdbCommandRejectedException e) {
    331             CLog.w("AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
    332                     e.toString(), getPackageName(), mRemoteDevice.getSerialNumber());
    333             mParser.handleTestRunFailed(e.toString());
    334             throw e;
    335         }
    336     }
    337 
    338     /**
    339      * {@inheritDoc}
    340      */
    341     @Override
    342     public void cancel() {
    343         if (mParser != null) {
    344             mParser.cancel();
    345         }
    346     }
    347 
    348     @Override
    349     public void setEnforceTimeStamp(boolean arg0) {
    350         // ignore, UiAutomator runner does not need this.
    351     }
    352 }
    353