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