• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.FileListingService;
20 import com.android.ddmlib.IShellOutputReceiver;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.device.CollectingOutputReceiver;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.result.ITestInvocationListener;
28 import com.android.tradefed.util.FileUtil;
29 
30 import com.google.common.annotations.VisibleForTesting;
31 
32 import org.json.JSONException;
33 import org.json.JSONObject;
34 
35 import java.io.File;
36 import java.io.IOException;
37 import java.util.List;
38 import java.util.concurrent.TimeUnit;
39 
40 /** A Test that runs a native test package on given device. */
41 @OptionClass(alias = "gtest")
42 public class GTest extends GTestBase implements IDeviceTest {
43 
44     static final String DEFAULT_NATIVETEST_PATH = "/data/nativetest";
45 
46     private ITestDevice mDevice = null;
47 
48     @Option(name = "native-test-device-path",
49             description="The path on the device where native tests are located.")
50     private String mNativeTestDevicePath = DEFAULT_NATIVETEST_PATH;
51 
52     @Option(
53             name = "reboot-before-test",
54             description = "Reboot the device before the test suite starts.")
55     private boolean mRebootBeforeTest = false;
56 
57     @Option(name = "stop-runtime",
58             description = "Stops the Java application runtime before test execution.")
59     private boolean mStopRuntime = false;
60 
61     // Max characters allowed for executing GTest via command line
62     private static final int GTEST_CMD_CHAR_LIMIT = 1000;
63     /**
64      * {@inheritDoc}
65      */
66     @Override
setDevice(ITestDevice device)67     public void setDevice(ITestDevice device) {
68         mDevice = device;
69     }
70 
71     /**
72      * {@inheritDoc}
73      */
74     @Override
getDevice()75     public ITestDevice getDevice() {
76         return mDevice;
77     }
78 
79     @Override
loadFilter(String binaryOnDevice)80     protected String loadFilter(String binaryOnDevice) throws DeviceNotAvailableException {
81         try {
82             String filterKey = getTestFilterKey();
83             CLog.i("Loading filter from file for key: '%s'", filterKey);
84             String filterFile = String.format("%s%s", binaryOnDevice, FILTER_EXTENSION);
85             if (getDevice().doesFileExist(filterFile)) {
86                 String content =
87                         getDevice().executeShellCommand(String.format("cat \"%s\"", filterFile));
88                 if (content != null && !content.isEmpty()) {
89                     JSONObject filter = new JSONObject(content);
90                     JSONObject filterObject = filter.getJSONObject(filterKey);
91                     return filterObject.getString("filter");
92                 }
93                 CLog.e("Error with content of the filter file %s: %s", filterFile, content);
94             } else {
95                 CLog.e("Filter file %s not found", filterFile);
96             }
97         } catch (JSONException e) {
98             CLog.e(e);
99         }
100         return null;
101     }
102 
103     /**
104      * Gets the path where native tests live on the device.
105      *
106      * @return The path on the device where the native tests live.
107      */
getTestPath()108     private String getTestPath() {
109         StringBuilder testPath = new StringBuilder(mNativeTestDevicePath);
110         String testModule = getTestModule();
111         if (testModule != null) {
112             testPath.append(FileListingService.FILE_SEPARATOR);
113             testPath.append(testModule);
114         }
115         return testPath.toString();
116     }
117 
118     /**
119      * Executes all native tests in a folder as well as in all subfolders recursively.
120      *
121      * @param root The root folder to begin searching for native tests
122      * @param testDevice The device to run tests on
123      * @param listener the {@link ITestInvocationListener}
124      * @throws DeviceNotAvailableException
125      */
126     @VisibleForTesting
doRunAllTestsInSubdirectory( String root, ITestDevice testDevice, ITestInvocationListener listener)127     void doRunAllTestsInSubdirectory(
128             String root, ITestDevice testDevice, ITestInvocationListener listener)
129             throws DeviceNotAvailableException {
130         if (testDevice.isDirectory(root)) {
131             // recursively run tests in all subdirectories
132             for (String child : testDevice.getChildren(root)) {
133                 doRunAllTestsInSubdirectory(root + "/" + child, testDevice, listener);
134             }
135         } else {
136             // assume every file is a valid gtest binary.
137             IShellOutputReceiver resultParser = createResultParser(getFileName(root), listener);
138             if (shouldSkipFile(root)) {
139                 return;
140             }
141             String flags = getAllGTestFlags(root);
142             CLog.i("Running gtest %s %s on %s", root, flags, testDevice.getSerialNumber());
143             if (isEnableXmlOutput()) {
144                 runTestXml(testDevice, root, flags, listener);
145             } else {
146                 runTest(testDevice, resultParser, root, flags);
147             }
148         }
149     }
150 
getFileName(String fullPath)151     String getFileName(String fullPath) {
152         int pos = fullPath.lastIndexOf('/');
153         if (pos == -1) {
154             return fullPath;
155         }
156         String fileName = fullPath.substring(pos + 1);
157         if (fileName.isEmpty()) {
158             throw new IllegalArgumentException("input should not end with \"/\"");
159         }
160         return fileName;
161     }
162 
163     /**
164      * Helper method to determine if we should skip the execution of a given file.
165      *
166      * @param fullPath the full path of the file in question
167      * @return true if we should skip the said file.
168      */
shouldSkipFile(String fullPath)169     protected boolean shouldSkipFile(String fullPath) throws DeviceNotAvailableException {
170         if (fullPath == null || fullPath.isEmpty()) {
171             return true;
172         }
173         // skip any file that's not executable
174         if (!mDevice.isExecutable(fullPath)) {
175             return true;
176         }
177         List<String> fileExclusionFilterRegex = getFileExclusionFilterRegex();
178         if (fileExclusionFilterRegex == null || fileExclusionFilterRegex.isEmpty()) {
179             return false;
180         }
181         for (String regex : fileExclusionFilterRegex) {
182             if (fullPath.matches(regex)) {
183                 CLog.i("File %s matches exclusion file regex %s, skipping", fullPath, regex);
184                 return true;
185             }
186         }
187         return false;
188     }
189 
190     /**
191      * Helper method to run a gtest command from a temporary script, in the case that the command
192      * is too long to be run directly by adb.
193      * @param testDevice the device on which to run the command
194      * @param cmd the command string to run
195      * @param resultParser the output receiver for reading test results
196      */
executeCommandByScript(final ITestDevice testDevice, final String cmd, final IShellOutputReceiver resultParser)197     protected void executeCommandByScript(final ITestDevice testDevice, final String cmd,
198             final IShellOutputReceiver resultParser) throws DeviceNotAvailableException {
199         String tmpFileDevice = "/data/local/tmp/gtest_script.sh";
200         testDevice.pushString(String.format("#!/bin/bash\n%s", cmd), tmpFileDevice);
201         // force file to be executable
202         testDevice.executeShellCommand(String.format("chmod 755 %s", tmpFileDevice));
203         testDevice.executeShellCommand(
204                 String.format("sh %s", tmpFileDevice),
205                 resultParser,
206                 getMaxTestTimeMs() /* maxTimeToShellOutputResponse */,
207                 TimeUnit.MILLISECONDS,
208                 0 /* retry attempts */);
209         testDevice.deleteFile(tmpFileDevice);
210     }
211 
212     @Override
getGTestCmdLine(String fullPath, String flags)213     protected String getGTestCmdLine(String fullPath, String flags) {
214         StringBuilder sb = new StringBuilder();
215         // When sharding a device GTest, add args to the command line
216         if (getShardCount() > 0) {
217             if (isCollectTestsOnly()) {
218                 CLog.w(
219                         "--collect-tests-only option ignores sharding parameters, and will cause "
220                                 + "each shard to collect all tests.");
221             }
222             sb.append(String.format("GTEST_SHARD_INDEX=%s ", getShardIndex()));
223             sb.append(String.format("GTEST_TOTAL_SHARDS=%s ", getShardCount()));
224         }
225         sb.append(super.getGTestCmdLine(fullPath, flags));
226         return sb.toString();
227     }
228 
229     /**
230      * Run the given gtest binary
231      *
232      * @param testDevice the {@link ITestDevice}
233      * @param resultParser the test run output parser
234      * @param fullPath absolute file system path to gtest binary on device
235      * @param flags gtest execution flags
236      * @throws DeviceNotAvailableException
237      */
runTest(final ITestDevice testDevice, final IShellOutputReceiver resultParser, final String fullPath, final String flags)238     private void runTest(final ITestDevice testDevice, final IShellOutputReceiver resultParser,
239             final String fullPath, final String flags) throws DeviceNotAvailableException {
240         // TODO: add individual test timeout support, and rerun support
241         try {
242             for (String cmd : getBeforeTestCmd()) {
243                 testDevice.executeShellCommand(cmd);
244             }
245 
246             if (mRebootBeforeTest && !isCollectTestsOnly()) {
247                 CLog.d("Rebooting device before test starts as requested.");
248                 testDevice.reboot();
249             }
250 
251             String cmd = getGTestCmdLine(fullPath, flags);
252             // ensure that command is not too long for adb
253             if (cmd.length() < GTEST_CMD_CHAR_LIMIT) {
254                 testDevice.executeShellCommand(
255                         cmd,
256                         resultParser,
257                         getMaxTestTimeMs() /* maxTimeToShellOutputResponse */,
258                         TimeUnit.MILLISECONDS,
259                         0 /* retryAttempts */);
260             } else {
261                 // wrap adb shell command in script if command is too long for direct execution
262                 executeCommandByScript(testDevice, cmd, resultParser);
263             }
264         } catch (DeviceNotAvailableException e) {
265             throw e;
266         } catch (RuntimeException e) {
267             throw e;
268         } finally {
269             // TODO: consider moving the flush of parser data on exceptions to TestDevice or
270             // AdbHelper
271             resultParser.flush();
272             for (String cmd : getAfterTestCmd()) {
273                 testDevice.executeShellCommand(cmd);
274             }
275         }
276     }
277 
278     /**
279      * Run the given gtest binary and parse XML results This methods typically requires the filter
280      * for .tff and .xml files, otherwise it will post some unwanted results.
281      *
282      * @param testDevice the {@link ITestDevice}
283      * @param fullPath absolute file system path to gtest binary on device
284      * @param flags gtest execution flags
285      * @param listener the {@link ITestInvocationListener}
286      * @throws DeviceNotAvailableException
287      */
runTestXml( final ITestDevice testDevice, final String fullPath, final String flags, ITestInvocationListener listener)288     private void runTestXml(
289             final ITestDevice testDevice,
290             final String fullPath,
291             final String flags,
292             ITestInvocationListener listener)
293             throws DeviceNotAvailableException {
294         CollectingOutputReceiver outputCollector = new CollectingOutputReceiver();
295         File tmpOutput = null;
296         try {
297             String testRunName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
298             tmpOutput = FileUtil.createTempFile(testRunName, ".xml");
299             String tmpResName = fullPath + "_res.xml";
300             String extraFlag = String.format(GTEST_XML_OUTPUT, tmpResName);
301             String fullFlagCmd =  String.format("%s %s", flags, extraFlag);
302 
303             // Run the tests with modified flags
304             runTest(testDevice, outputCollector, fullPath, fullFlagCmd);
305             // Pull the result file, may not exist if issue with the test.
306             testDevice.pullFile(tmpResName, tmpOutput);
307             // Clean the file on the device
308             testDevice.deleteFile(tmpResName);
309             GTestXmlResultParser parser = createXmlParser(testRunName, listener);
310             // Attempt to parse the file, doesn't matter if the content is invalid.
311             if (tmpOutput.exists()) {
312                 parser.parseResult(tmpOutput, outputCollector);
313             }
314         } catch (DeviceNotAvailableException | RuntimeException e) {
315             throw e;
316         } catch (IOException e) {
317             throw new RuntimeException(e);
318         } finally {
319             outputCollector.flush();
320             for (String cmd : getAfterTestCmd()) {
321                 testDevice.executeShellCommand(cmd);
322             }
323             FileUtil.deleteFile(tmpOutput);
324         }
325     }
326 
327     /**
328      * {@inheritDoc}
329      */
330     @Override
run(ITestInvocationListener listener)331     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
332         // TODO: add support for rerunning tests
333         if (mDevice == null) {
334             throw new IllegalArgumentException("Device has not been set");
335         }
336 
337         String testPath = getTestPath();
338         if (!mDevice.doesFileExist(testPath)) {
339             CLog.w("Could not find native test directory %s in %s!", testPath,
340                     mDevice.getSerialNumber());
341             return;
342         }
343         if (mStopRuntime) {
344             mDevice.executeShellCommand("stop");
345         }
346         // Insert the coverage listener if code coverage collection is enabled.
347         listener = addNativeCoverageListenerIfEnabled(mDevice, listener);
348         Throwable throwable = null;
349         try {
350             doRunAllTestsInSubdirectory(testPath, mDevice, listener);
351         } catch (Throwable t) {
352             throwable = t;
353             throw t;
354         } finally {
355             if (!(throwable instanceof DeviceNotAvailableException)) {
356                 if (mStopRuntime) {
357                     mDevice.executeShellCommand("start");
358                     mDevice.waitForDeviceAvailable();
359                 }
360             }
361         }
362     }
363 }
364