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.invoker.TestInformation; 27 import com.android.tradefed.log.LogUtil.CLog; 28 import com.android.tradefed.result.ITestInvocationListener; 29 import com.android.tradefed.testtype.coverage.CoverageOptions; 30 import com.android.tradefed.util.AbiUtils; 31 import com.android.tradefed.util.FileUtil; 32 33 import com.google.common.annotations.VisibleForTesting; 34 35 import org.json.JSONException; 36 import org.json.JSONObject; 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.util.ArrayList; 41 import java.util.LinkedHashSet; 42 import java.util.List; 43 import java.util.Set; 44 import java.util.concurrent.TimeUnit; 45 46 /** A Test that runs a native test package on given device. */ 47 @OptionClass(alias = "gtest") 48 public class GTest extends GTestBase implements IDeviceTest { 49 50 static final String DEFAULT_NATIVETEST_PATH = "/data/nativetest"; 51 52 private ITestDevice mDevice = null; 53 54 @Option(name = "native-test-device-path", 55 description="The path on the device where native tests are located.") 56 private String mNativeTestDevicePath = DEFAULT_NATIVETEST_PATH; 57 58 @Option( 59 name = "reboot-before-test", 60 description = "Reboot the device before the test suite starts.") 61 private boolean mRebootBeforeTest = false; 62 63 @Option(name = "stop-runtime", 64 description = "Stops the Java application runtime before test execution.") 65 private boolean mStopRuntime = false; 66 67 /** @deprecated Use the --coverage-flush option in CoverageOptions instead. */ 68 @Deprecated 69 @Option( 70 name = "coverage-flush", 71 description = "Forces coverage data to be flushed at the end of the test." 72 ) 73 private boolean mCoverageFlush = false; 74 75 /** @deprecated Use the --coverage-processes option in CoverageOptions instead. */ 76 @Deprecated 77 @Option( 78 name = "coverage-processes", 79 description = "Name of processes to collect coverage data from." 80 ) 81 private List<String> mCoverageProcesses = new ArrayList<>(); 82 83 /** @deprecated Merged into the --coverage-flush option in CoverageOptions instead. */ 84 @Deprecated 85 @Option( 86 name = "coverage-clear-before-test", 87 description = "Clears all coverage counters before test execution." 88 ) 89 private boolean mCoverageClearBeforeTest = true; 90 91 @Option( 92 name = "filter-non-matching-abi-folders", 93 description = 94 "If an abi specific hierarchy seem to exists, only run the parts that " 95 + "match abi under test.") 96 private boolean mFilterAbiFolders = true; 97 98 @Option( 99 name = "use-updated-shard-retry", 100 description = "Whether to use the updated logic for retry with sharding.") 101 private boolean mUseUpdatedShardRetry = true; 102 103 @Option( 104 name = "force-no-test-error", 105 description = "Whether to throw an error if no test binary is found to execute.") 106 private boolean mForceNoTestError = false; 107 108 /** Whether any incomplete test is found in the current run. */ 109 private boolean mIncompleteTestFound = false; 110 111 /** List of tests that failed in the current test run when test run was complete. */ 112 private Set<String> mCurFailedTests = new LinkedHashSet<>(); 113 114 // Max characters allowed for executing GTest via command line 115 private static final int GTEST_CMD_CHAR_LIMIT = 1000; 116 /** 117 * {@inheritDoc} 118 */ 119 @Override setDevice(ITestDevice device)120 public void setDevice(ITestDevice device) { 121 mDevice = device; 122 } 123 124 /** 125 * {@inheritDoc} 126 */ 127 @Override getDevice()128 public ITestDevice getDevice() { 129 return mDevice; 130 } 131 132 @Override loadFilter(String binaryOnDevice)133 protected String loadFilter(String binaryOnDevice) throws DeviceNotAvailableException { 134 try { 135 String filterKey = getTestFilterKey(); 136 CLog.i("Loading filter from file for key: '%s'", filterKey); 137 String filterFile = String.format("%s%s", binaryOnDevice, FILTER_EXTENSION); 138 if (getDevice().doesFileExist(filterFile)) { 139 String content = 140 getDevice().executeShellCommand(String.format("cat \"%s\"", filterFile)); 141 if (content != null && !content.isEmpty()) { 142 JSONObject filter = new JSONObject(content); 143 JSONObject filterObject = filter.getJSONObject(filterKey); 144 return filterObject.getString("filter"); 145 } 146 CLog.e("Error with content of the filter file %s: %s", filterFile, content); 147 } else { 148 CLog.e("Filter file %s not found", filterFile); 149 } 150 } catch (JSONException e) { 151 CLog.e(e); 152 } 153 return null; 154 } 155 156 /** 157 * Gets the path where native tests live on the device. 158 * 159 * @return The path on the device where the native tests live. 160 */ getTestPath()161 private String getTestPath() { 162 StringBuilder testPath = new StringBuilder(mNativeTestDevicePath); 163 String testModule = getTestModule(); 164 if (testModule != null) { 165 testPath.append(FileListingService.FILE_SEPARATOR); 166 testPath.append(testModule); 167 } 168 return testPath.toString(); 169 } 170 setNativeTestDevicePath(String path)171 public void setNativeTestDevicePath(String path) { 172 mNativeTestDevicePath = path; 173 } 174 175 /** 176 * Executes all native tests in a folder as well as in all subfolders recursively. 177 * 178 * @param root The root folder to begin searching for native tests 179 * @param testDevice The device to run tests on 180 * @param listener the {@link ITestInvocationListener} 181 * @throws DeviceNotAvailableException 182 */ 183 @VisibleForTesting doRunAllTestsInSubdirectory( String root, ITestDevice testDevice, ITestInvocationListener listener)184 void doRunAllTestsInSubdirectory( 185 String root, ITestDevice testDevice, ITestInvocationListener listener) 186 throws DeviceNotAvailableException { 187 Set<String> excludeDirectories = new LinkedHashSet<>(); 188 // Decide to filter out a folder sub-path based on whether we should enforce the 189 // current abi under test. 190 if (mFilterAbiFolders && getAbi() != null) { 191 String currentArch = AbiUtils.getArchForAbi(getAbi().getName()); 192 // exclude all abi specific folders, that is not the current abi, from the search 193 for (String supportedArch : AbiUtils.getArchSupported()) { 194 if (!supportedArch.equals(currentArch)) { 195 excludeDirectories.add(supportedArch); 196 } 197 } 198 } 199 String[] executableFiles = getExecutableFiles(testDevice, root, excludeDirectories); 200 boolean gtestExecutableFound = false; 201 for (String filePath : executableFiles) { 202 if (shouldRunFile(filePath)) { 203 gtestExecutableFound = true; 204 IShellOutputReceiver resultParser = 205 createResultParser(getFileName(filePath), listener); 206 String flags = getAllGTestFlags(filePath); 207 CLog.i("Running gtest %s %s on %s", filePath, flags, testDevice.getSerialNumber()); 208 if (isEnableXmlOutput()) { 209 runTestXml(testDevice, filePath, flags, listener); 210 } else { 211 runTest(testDevice, resultParser, filePath, flags); 212 } 213 } 214 } 215 if (!gtestExecutableFound) { 216 CLog.d("Failed to find any native test in directory %s.", root); 217 if (mForceNoTestError) { 218 throw new RuntimeException( 219 String.format("Failed to find any native test in directory %s.", root)); 220 } 221 } 222 } 223 getFileName(String fullPath)224 String getFileName(String fullPath) { 225 int pos = fullPath.lastIndexOf('/'); 226 if (pos == -1) { 227 return fullPath; 228 } 229 String fileName = fullPath.substring(pos + 1); 230 if (fileName.isEmpty()) { 231 throw new IllegalArgumentException("input should not end with \"/\""); 232 } 233 return fileName; 234 } 235 236 /** 237 * Helper method to determine if we should execute a given file. 238 * 239 * @param fullPath the full path of the file in question 240 * @return true if we should execute the said file. 241 */ shouldRunFile(String fullPath)242 protected boolean shouldRunFile(String fullPath) { 243 if (fullPath == null || fullPath.isEmpty()) { 244 return false; 245 } 246 247 // look for files that start with the module name if it is set 248 String moduleName = getTestModule(); 249 String fileName = getFileName(fullPath); 250 if (moduleName != null && !fileName.startsWith(moduleName)) { 251 return false; 252 } 253 254 // filter out files excluded by the exclusion regex, for example .so files 255 List<String> fileExclusionFilterRegex = getFileExclusionFilterRegex(); 256 for (String regex : fileExclusionFilterRegex) { 257 if (fullPath.matches(regex)) { 258 CLog.i("File %s matches exclusion file regex %s, skipping", fullPath, regex); 259 return false; 260 } 261 } 262 263 return true; 264 } 265 266 /** 267 * Helper method to run a gtest command from a temporary script, in the case that the command 268 * is too long to be run directly by adb. 269 * @param testDevice the device on which to run the command 270 * @param cmd the command string to run 271 * @param resultParser the output receiver for reading test results 272 */ executeCommandByScript(final ITestDevice testDevice, final String cmd, final IShellOutputReceiver resultParser)273 protected void executeCommandByScript(final ITestDevice testDevice, final String cmd, 274 final IShellOutputReceiver resultParser) throws DeviceNotAvailableException { 275 String tmpFileDevice = "/data/local/tmp/gtest_script.sh"; 276 testDevice.pushString(String.format("#!/bin/bash\n%s", cmd), tmpFileDevice); 277 // force file to be executable 278 testDevice.executeShellCommand(String.format("chmod 755 %s", tmpFileDevice)); 279 testDevice.executeShellCommand( 280 String.format("sh %s", tmpFileDevice), 281 resultParser, 282 getMaxTestTimeMs() /* maxTimeToShellOutputResponse */, 283 TimeUnit.MILLISECONDS, 284 0 /* retry attempts */); 285 testDevice.deleteFile(tmpFileDevice); 286 } 287 288 @Override getGTestCmdLine(String fullPath, String flags)289 protected String getGTestCmdLine(String fullPath, String flags) { 290 StringBuilder sb = new StringBuilder(); 291 // When sharding a device GTest, add args to the command line 292 if (getShardCount() > 0) { 293 if (isCollectTestsOnly()) { 294 CLog.w( 295 "--collect-tests-only option ignores sharding parameters, and will cause " 296 + "each shard to collect all tests."); 297 } 298 sb.append(String.format("GTEST_SHARD_INDEX=%s ", getShardIndex())); 299 sb.append(String.format("GTEST_TOTAL_SHARDS=%s ", getShardCount())); 300 } 301 if (isCoverageEnabled()) { 302 sb.append("LLVM_PROFILE_FILE=/data/local/tmp/clang-%m.profraw "); 303 } 304 sb.append(super.getGTestCmdLine(fullPath, flags)); 305 return sb.toString(); 306 } 307 308 @Override createFlagFile(String filter)309 protected String createFlagFile(String filter) throws DeviceNotAvailableException { 310 String flagPath = super.createFlagFile(filter); 311 if (flagPath == null) { 312 // Return null to fall back to base filter 313 return null; 314 } 315 File flagFile = new File(flagPath); 316 String devicePath = "/data/local/tmp/" + flagFile.getName(); 317 try { 318 if (!mDevice.pushFile(flagFile, devicePath)) { 319 // Failed to push flagfile, return null to fall back to base filter 320 return null; 321 } 322 } finally { 323 FileUtil.deleteFile(flagFile); 324 } 325 return devicePath; 326 } 327 328 /** 329 * Run the given gtest binary 330 * 331 * @param testDevice the {@link ITestDevice} 332 * @param resultParser the test run output parser 333 * @param fullPath absolute file system path to gtest binary on device 334 * @param flags gtest execution flags 335 * @throws DeviceNotAvailableException 336 */ runTest(final ITestDevice testDevice, final IShellOutputReceiver resultParser, final String fullPath, final String flags)337 private void runTest(final ITestDevice testDevice, final IShellOutputReceiver resultParser, 338 final String fullPath, final String flags) throws DeviceNotAvailableException { 339 // TODO: add individual test timeout support, and rerun support 340 try { 341 for (String cmd : getBeforeTestCmd()) { 342 testDevice.executeShellCommand(cmd); 343 } 344 345 if (mRebootBeforeTest && !isCollectTestsOnly()) { 346 CLog.d("Rebooting device before test starts as requested."); 347 testDevice.reboot(); 348 } 349 350 String cmd = getGTestCmdLine(fullPath, flags); 351 // ensure that command is not too long for adb 352 if (cmd.length() < GTEST_CMD_CHAR_LIMIT) { 353 testDevice.executeShellCommand( 354 cmd, 355 resultParser, 356 getMaxTestTimeMs() /* maxTimeToShellOutputResponse */, 357 TimeUnit.MILLISECONDS, 358 0 /* retryAttempts */); 359 } else { 360 // wrap adb shell command in script if command is too long for direct execution 361 executeCommandByScript(testDevice, cmd, resultParser); 362 } 363 } catch (DeviceNotAvailableException e) { 364 throw e; 365 } catch (RuntimeException e) { 366 throw e; 367 } finally { 368 // TODO: consider moving the flush of parser data on exceptions to TestDevice or 369 // AdbHelper 370 resultParser.flush(); 371 if (resultParser instanceof GTestResultParser) { 372 if (((GTestResultParser) resultParser).isTestRunIncomplete()) { 373 mIncompleteTestFound = true; 374 } else { 375 // if test run is complete, collect the failed tests so that they can be retried 376 mCurFailedTests.addAll(((GTestResultParser) resultParser).getFailedTests()); 377 } 378 } 379 for (String cmd : getAfterTestCmd()) { 380 testDevice.executeShellCommand(cmd); 381 } 382 } 383 } 384 385 /** 386 * Run the given gtest binary and parse XML results This methods typically requires the filter 387 * for .tff and .xml files, otherwise it will post some unwanted results. 388 * 389 * @param testDevice the {@link ITestDevice} 390 * @param fullPath absolute file system path to gtest binary on device 391 * @param flags gtest execution flags 392 * @param listener the {@link ITestInvocationListener} 393 * @throws DeviceNotAvailableException 394 */ runTestXml( final ITestDevice testDevice, final String fullPath, final String flags, ITestInvocationListener listener)395 private void runTestXml( 396 final ITestDevice testDevice, 397 final String fullPath, 398 final String flags, 399 ITestInvocationListener listener) 400 throws DeviceNotAvailableException { 401 CollectingOutputReceiver outputCollector = new CollectingOutputReceiver(); 402 File tmpOutput = null; 403 try { 404 String testRunName = fullPath.substring(fullPath.lastIndexOf("/") + 1); 405 tmpOutput = FileUtil.createTempFile(testRunName, ".xml"); 406 String tmpResName = fullPath + "_res.xml"; 407 String extraFlag = String.format(GTEST_XML_OUTPUT, tmpResName); 408 String fullFlagCmd = String.format("%s %s", flags, extraFlag); 409 410 // Run the tests with modified flags 411 runTest(testDevice, outputCollector, fullPath, fullFlagCmd); 412 // Pull the result file, may not exist if issue with the test. 413 testDevice.pullFile(tmpResName, tmpOutput); 414 // Clean the file on the device 415 testDevice.deleteFile(tmpResName); 416 GTestXmlResultParser parser = createXmlParser(testRunName, listener); 417 // Attempt to parse the file, doesn't matter if the content is invalid. 418 if (tmpOutput.exists()) { 419 parser.parseResult(tmpOutput, outputCollector); 420 if (parser.isTestRunIncomplete()) { 421 mIncompleteTestFound = true; 422 } else { 423 // if test run is complete, collect the failed tests so that they can be retried 424 mCurFailedTests.addAll(parser.getFailedTests()); 425 } 426 } 427 } catch (DeviceNotAvailableException | RuntimeException e) { 428 throw e; 429 } catch (IOException e) { 430 throw new RuntimeException(e); 431 } finally { 432 outputCollector.flush(); 433 for (String cmd : getAfterTestCmd()) { 434 testDevice.executeShellCommand(cmd); 435 } 436 FileUtil.deleteFile(tmpOutput); 437 } 438 } 439 440 /** {@inheritDoc} */ 441 @Override run(TestInformation testInfo, ITestInvocationListener listener)442 public void run(TestInformation testInfo, ITestInvocationListener listener) 443 throws DeviceNotAvailableException { 444 // TODO: add support for rerunning tests 445 if (mDevice == null) { 446 throw new IllegalArgumentException("Device has not been set"); 447 } 448 449 String testPath = getTestPath(); 450 if (!mDevice.doesFileExist(testPath)) { 451 CLog.w("Could not find native test directory %s in %s!", testPath, 452 mDevice.getSerialNumber()); 453 return; 454 } 455 // Reset flags that are used to track results of current test run. 456 mIncompleteTestFound = false; 457 mCurFailedTests = new LinkedHashSet<>(); 458 459 if (mStopRuntime) { 460 mDevice.executeShellCommand("stop"); 461 } 462 listener = getGTestListener(listener); 463 464 Throwable throwable = null; 465 try { 466 doRunAllTestsInSubdirectory(testPath, mDevice, listener); 467 } catch (Throwable t) { 468 throwable = t; 469 // if we encounter any errors, count it as test Incomplete so that retry attempts 470 // during sharding uses a full retry. 471 mIncompleteTestFound = true; 472 throw t; 473 } finally { 474 if (mUseUpdatedShardRetry) { 475 // notify of test execution will enable the new sharding retry behavior since Gtest 476 // will be aware of retries. 477 notifyTestExecution(mIncompleteTestFound, mCurFailedTests); 478 } 479 if (!(throwable instanceof DeviceNotAvailableException)) { 480 if (mStopRuntime) { 481 mDevice.executeShellCommand("start"); 482 mDevice.waitForDeviceAvailable(); 483 } 484 } 485 } 486 } 487 isRebootBeforeTestEnabled()488 public boolean isRebootBeforeTestEnabled() { 489 return mRebootBeforeTest; 490 } 491 492 /** 493 * Searches directories recursively to find all executable files. 494 * 495 * @param device {@link ITestDevice} where the search will occur. 496 * @param deviceFilePath is the path on the device where to do the search. 497 * @param excludeDirectories Set of directory names that must be excluded from the search. 498 * @return Array of string containing all the executable file paths on the device. 499 * @throws DeviceNotAvailableException 500 */ getExecutableFiles( ITestDevice device, String deviceFilePath, Set<String> excludeDirectories)501 private String[] getExecutableFiles( 502 ITestDevice device, String deviceFilePath, Set<String> excludeDirectories) 503 throws DeviceNotAvailableException { 504 String cmd = String.format("find -L %s -type f -perm -u+r,u+x", deviceFilePath); 505 506 if (excludeDirectories != null && !excludeDirectories.isEmpty()) { 507 for (String directoryName : excludeDirectories) { 508 cmd += String.format(" -not -path \"*/%s/*\"", directoryName); 509 } 510 } 511 512 String output = device.executeShellCommand(cmd); 513 if (output.trim().isEmpty()) { 514 return new String[0]; 515 } 516 return output.split("\r?\n"); 517 } 518 519 /** Checks if native coverage is enabled. */ isCoverageEnabled()520 private boolean isCoverageEnabled() { 521 CoverageOptions options = getConfiguration().getCoverageOptions(); 522 return options.isCoverageEnabled() 523 && (options.getCoverageToolchains().contains(CoverageOptions.Toolchain.GCOV) 524 || options.getCoverageToolchains() 525 .contains(CoverageOptions.Toolchain.CLANG)); 526 } 527 } 528