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