• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
PrintTestRemoteTestRunner(String packageName, String runnerName, IShellEnabledDevice remoteDevice)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      */
PrintTestRemoteTestRunner(String packageName, IShellEnabledDevice remoteDevice)99     public PrintTestRemoteTestRunner(String packageName, IShellEnabledDevice remoteDevice) {
100         this(packageName, null, remoteDevice);
101     }
102 
103     @Override
getPackageName()104     public String getPackageName() {
105         return mPackageName;
106     }
107 
108     @Override
getRunnerName()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      */
getRunnerPath()119     private String getRunnerPath() {
120         return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
121     }
122 
123     @Override
setClassName(String className)124     public void setClassName(String className) {
125         addInstrumentationArg(CLASS_ARG_NAME, className);
126     }
127 
128     @Override
setClassNames(String[] classNames)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
setMethodName(String className, String testName)142     public void setMethodName(String className, String testName) {
143         setClassName(className + METHOD_SEPARATOR + testName);
144     }
145 
146     @Override
setTestPackageName(String packageName)147     public void setTestPackageName(String packageName) {
148         addInstrumentationArg(PACKAGE_ARG_NAME, packageName);
149     }
150 
151     @Override
addInstrumentationArg(String name, String value)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
removeInstrumentationArg(String name)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
addBooleanArg(String name, boolean value)168     public void addBooleanArg(String name, boolean value) {
169         addInstrumentationArg(name, Boolean.toString(value));
170     }
171 
172     @Override
setLogOnly(boolean logOnly)173     public void setLogOnly(boolean logOnly) {
174         addBooleanArg(LOG_ARG_NAME, logOnly);
175     }
176 
177     @Override
setDebug(boolean debug)178     public void setDebug(boolean debug) {
179         addBooleanArg(DEBUG_ARG_NAME, debug);
180     }
181 
182     @Override
setCoverage(boolean coverage)183     public void setCoverage(boolean coverage) {
184         addBooleanArg(COVERAGE_ARG_NAME, coverage);
185     }
186 
187     @Override
setTestCollection(boolean b)188     public void setTestCollection(boolean b) {
189         throw new UnsupportedOperationException("Test Collection mode is not supported");
190     }
191 
192     @Override
setTestSize(TestSize size)193     public void setTestSize(TestSize size) {
194         addInstrumentationArg(SIZE_ARG_NAME, ""/*size.getRunnerValue()*/);
195     }
196 
197     @Override
setMaxtimeToOutputResponse(int maxTimeToOutputResponse)198     public void setMaxtimeToOutputResponse(int maxTimeToOutputResponse) {
199         setMaxTimeToOutputResponse(maxTimeToOutputResponse, TimeUnit.MILLISECONDS);
200     }
201 
202     @Override
setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits)203     public void setMaxTimeToOutputResponse(long maxTimeToOutputResponse, TimeUnit maxTimeUnits) {
204         mMaxTimeToOutputResponse = maxTimeToOutputResponse;
205         mMaxTimeUnits = maxTimeUnits;
206     }
207 
208     @Override
setRunName(String runName)209     public void setRunName(String runName) {
210         mRunName = runName;
211     }
212 
213     @Override
run(ITestRunListener... listeners)214     public void run(ITestRunListener... listeners) throws TimeoutException,
215             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
216         run(Arrays.asList(listeners));
217     }
218 
219     @Override
run(Collection<ITestRunListener> listeners)220     public void run(Collection<ITestRunListener> listeners) throws TimeoutException,
221             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
222         final String runCaseCommandStr = String.format(INSTRUMENTATION_COMMAND,
223               getArgsCommand(), getRunnerPath());
224         Log.i(LOG_TAG,
225                 String.format("Running %1$s on %2$s", runCaseCommandStr, mRemoteDevice.getName()));
226         String runName = mRunName == null ? mPackageName : mRunName;
227         mParser = new InstrumentationResultParser(runName, listeners);
228 
229         try {
230             mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser, mMaxTimeToOutputResponse,
231                     mMaxTimeUnits);
232         } catch (IOException e) {
233             Log.w(LOG_TAG, String.format("IOException %1$s when running tests %2$s on %3$s",
234                     e.toString(), getPackageName(), mRemoteDevice.getName()));
235             // rely on parser to communicate results to listeners
236             mParser.handleTestRunFailed(e.toString());
237             throw e;
238         } catch (ShellCommandUnresponsiveException e) {
239             Log.w(LOG_TAG, String.format(
240                     "ShellCommandUnresponsiveException %1$s when running tests %2$s on %3$s",
241                     e.toString(), getPackageName(), mRemoteDevice.getName()));
242             mParser.handleTestRunFailed(String
243                     .format("Failed to receive adb shell test output within %1$d ms. "
244                             + "Test may have timed out, or adb connection to device became"
245                             + "unresponsive", mMaxTimeToOutputResponse));
246             throw e;
247         } catch (TimeoutException e) {
248             Log.w(LOG_TAG, String.format("TimeoutException when running tests %1$s on %2$s",
249                     getPackageName(), mRemoteDevice.getName()));
250             mParser.handleTestRunFailed(e.toString());
251             throw e;
252         } catch (AdbCommandRejectedException e) {
253             Log.w(LOG_TAG, String.format(
254                     "AdbCommandRejectedException %1$s when running tests %2$s on %3$s",
255                     e.toString(), getPackageName(), mRemoteDevice.getName()));
256             mParser.handleTestRunFailed(e.toString());
257             throw e;
258         }
259     }
260 
261     @Override
cancel()262     public void cancel() {
263         if (mParser != null) {
264             mParser.cancel();
265         }
266     }
267 
268     /**
269      * Returns the full instrumentation command line syntax for the provided
270      * instrumentation arguments. Returns an empty string if no arguments were
271      * specified.
272      */
getArgsCommand()273     private String getArgsCommand() {
274         StringBuilder commandBuilder = new StringBuilder();
275         for (Entry<String, String> argPair : mArgMap.entrySet()) {
276             final String argCmd = String.format(" -e %1$s %2$s", argPair.getKey(),
277                     argPair.getValue());
278             commandBuilder.append(argCmd);
279         }
280         return commandBuilder.toString();
281     }
282 }
283