1 /* 2 * Copyright (C) 2018 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.IShellOutputReceiver; 20 import com.android.tradefed.config.IConfiguration; 21 import com.android.tradefed.config.IConfigurationReceiver; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.config.OptionCopier; 24 import com.android.tradefed.device.DeviceNotAvailableException; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.ITestInvocationListener; 27 import com.android.tradefed.testtype.coverage.CoverageOptions; 28 import com.android.tradefed.util.ArrayUtil; 29 import com.android.tradefed.util.FileUtil; 30 31 import com.google.common.annotations.VisibleForTesting; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.lang.reflect.InvocationTargetException; 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.LinkedHashSet; 39 import java.util.List; 40 import java.util.Set; 41 42 /** The base class of gTest */ 43 public abstract class GTestBase 44 implements IRemoteTest, 45 IConfigurationReceiver, 46 ITestFilterReceiver, 47 IRuntimeHintProvider, 48 ITestCollector, 49 IShardableTest, 50 IAbiReceiver { 51 52 private static final List<String> DEFAULT_FILE_EXCLUDE_FILTERS = new ArrayList<>(); 53 54 static { 55 // Exclude .so by default as they are not runnable. 56 DEFAULT_FILE_EXCLUDE_FILTERS.add(".*\\.so"); 57 } 58 59 @Option(name = "run-disable-tests", description = "Determine to run disable tests or not.") 60 private boolean mRunDisabledTests = false; 61 62 @Option(name = "module-name", description = "The name of the native test module to run.") 63 private String mTestModule = null; 64 65 @Option( 66 name = "file-exclusion-filter-regex", 67 description = "Regex to exclude certain files from executing. Can be repeated") 68 private List<String> mFileExclusionFilterRegex = new ArrayList<>(DEFAULT_FILE_EXCLUDE_FILTERS); 69 70 @Option( 71 name = "positive-testname-filter", 72 description = "The GTest-based positive filter of the test name to run.") 73 private String mTestNamePositiveFilter = null; 74 75 @Option( 76 name = "negative-testname-filter", 77 description = "The GTest-based negative filter of the test name to run.") 78 private String mTestNameNegativeFilter = null; 79 80 @Option( 81 name = "include-filter", 82 description = "The GTest-based positive filter of the test names to run." 83 ) 84 private Set<String> mIncludeFilters = new LinkedHashSet<>(); 85 86 @Option( 87 name = "exclude-filter", 88 description = "The GTest-based negative filter of the test names to run." 89 ) 90 private Set<String> mExcludeFilters = new LinkedHashSet<>(); 91 92 @Option( 93 name = "native-test-timeout", 94 description = 95 "The max time for a gtest to run. Test run will be aborted if any test " 96 + "takes longer.", 97 isTimeVal = true) 98 private long mMaxTestTimeMs = 1 * 60 * 1000L; 99 100 /** @deprecated use --coverage in CoverageOptions instead. */ 101 @Deprecated 102 @Option( 103 name = "coverage", 104 description = 105 "Collect code coverage for this test run. Note that the build under test must be a " 106 + "coverage build or else this will fail." 107 ) 108 private boolean mCoverage = false; 109 110 @Option( 111 name = "prepend-filename", 112 description = "Prepend filename as part of the classname for the tests.") 113 private boolean mPrependFileName = false; 114 115 @Option(name = "before-test-cmd", description = "adb shell command(s) to run before GTest.") 116 private List<String> mBeforeTestCmd = new ArrayList<>(); 117 118 @Option(name = "after-test-cmd", description = "adb shell command(s) to run after GTest.") 119 private List<String> mAfterTestCmd = new ArrayList<>(); 120 121 @Option(name = "run-test-as", description = "User to execute test binary as.") 122 private String mRunTestAs = null; 123 124 @Option( 125 name = "ld-library-path", 126 description = "LD_LIBRARY_PATH value to include in the GTest execution command.") 127 private String mLdLibraryPath = null; 128 129 @Option( 130 name = "native-test-flag", 131 description = 132 "Additional flag values to pass to the native test's shell command. " 133 + "Flags should be complete, including any necessary dashes: \"--flag=value\"") 134 private List<String> mGTestFlags = new ArrayList<>(); 135 136 @Option( 137 name = "runtime-hint", 138 description = "The hint about the test's runtime.", 139 isTimeVal = true) 140 private long mRuntimeHint = 60000; // 1 minute 141 142 @Option( 143 name = "xml-output", 144 description = 145 "Use gtest xml output for test results, " 146 + "if test binaries crash, no output will be available.") 147 private boolean mEnableXmlOutput = false; 148 149 @Option( 150 name = "collect-tests-only", 151 description = 152 "Only invoke the test binary to collect list of applicable test cases. " 153 + "All test run callbacks will be triggered, but test execution will " 154 + "not be actually carried out. This option ignores sharding parameters, so " 155 + "each shard will end up collecting all tests.") 156 private boolean mCollectTestsOnly = false; 157 158 @Option( 159 name = "test-filter-key", 160 description = 161 "run the gtest with the --gtest_filter populated with the filter from " 162 + "the json filter file associated with the binary, the filter file will have " 163 + "the same name as the binary with the .json extension.") 164 private String mTestFilterKey = null; 165 166 @Option( 167 name = "disable-duplicate-test-check", 168 description = "If set to true, it will not check that a method is only run once.") 169 private boolean mDisableDuplicateCheck = false; 170 171 // GTest flags... 172 protected static final String GTEST_FLAG_PRINT_TIME = "--gtest_print_time"; 173 protected static final String GTEST_FLAG_FILTER = "--gtest_filter"; 174 protected static final String GTEST_FLAG_RUN_DISABLED_TESTS = "--gtest_also_run_disabled_tests"; 175 protected static final String GTEST_FLAG_LIST_TESTS = "--gtest_list_tests"; 176 protected static final String GTEST_FLAG_FILE = "--gtest_flagfile"; 177 protected static final String GTEST_XML_OUTPUT = "--gtest_output=xml:%s"; 178 // Expected extension for the filter file associated with the binary (json formatted file) 179 @VisibleForTesting protected static final String FILTER_EXTENSION = ".filter"; 180 181 private int mShardCount = 0; 182 private int mShardIndex = 0; 183 private boolean mIsSharded = false; 184 185 private IConfiguration mConfiguration = null; 186 private IAbi mAbi; 187 188 @Override setAbi(IAbi abi)189 public void setAbi(IAbi abi) { 190 mAbi = abi; 191 } 192 193 @Override getAbi()194 public IAbi getAbi() { 195 return mAbi; 196 } 197 198 /** {@inheritDoc} */ 199 @Override setConfiguration(IConfiguration configuration)200 public void setConfiguration(IConfiguration configuration) { 201 mConfiguration = configuration; 202 } 203 204 /** 205 * Set the Android native test module to run. 206 * 207 * @param moduleName The name of the native test module to run 208 */ setModuleName(String moduleName)209 public void setModuleName(String moduleName) { 210 mTestModule = moduleName; 211 } 212 213 /** 214 * Get the Android native test module to run. 215 * 216 * @return the name of the native test module to run, or null if not set 217 */ getModuleName()218 public String getModuleName() { 219 return mTestModule; 220 } 221 222 /** Set whether GTest should run disabled tests. */ setRunDisabled(boolean runDisabled)223 protected void setRunDisabled(boolean runDisabled) { 224 mRunDisabledTests = runDisabled; 225 } 226 227 /** 228 * Get whether GTest should run disabled tests. 229 * 230 * @return True if disabled tests should be run, false otherwise 231 */ getRunDisabledTests()232 public boolean getRunDisabledTests() { 233 return mRunDisabledTests; 234 } 235 236 /** Set the max time in ms for a gtest to run. */ 237 @VisibleForTesting setMaxTestTimeMs(int timeout)238 void setMaxTestTimeMs(int timeout) { 239 mMaxTestTimeMs = timeout; 240 } 241 242 /** 243 * Adds an exclusion file filter regex. 244 * 245 * @param regex to exclude file. 246 */ 247 @VisibleForTesting addFileExclusionFilterRegex(String regex)248 void addFileExclusionFilterRegex(String regex) { 249 mFileExclusionFilterRegex.add(regex); 250 } 251 252 /** Sets the shard index of this test. */ setShardIndex(int shardIndex)253 public void setShardIndex(int shardIndex) { 254 mShardIndex = shardIndex; 255 } 256 257 /** Gets the shard index of this test. */ getShardIndex()258 public int getShardIndex() { 259 return mShardIndex; 260 } 261 262 /** Sets the shard count of this test. */ setShardCount(int shardCount)263 public void setShardCount(int shardCount) { 264 mShardCount = shardCount; 265 } 266 267 /** Returns the current shard-count. */ getShardCount()268 public int getShardCount() { 269 return mShardCount; 270 } 271 272 /** {@inheritDoc} */ 273 @Override getRuntimeHint()274 public long getRuntimeHint() { 275 return mRuntimeHint; 276 } 277 278 /** {@inheritDoc} */ 279 @Override addIncludeFilter(String filter)280 public void addIncludeFilter(String filter) { 281 if (mShardCount > 0) { 282 // If we explicitly start giving filters to GTest, reset the shard-count. GTest first 283 // applies filters then GTEST_TOTAL_SHARDS so it will probably end up not running 284 // anything 285 mShardCount = 0; 286 } 287 mIncludeFilters.add(cleanFilter(filter)); 288 } 289 290 /** {@inheritDoc} */ 291 @Override addAllIncludeFilters(Set<String> filters)292 public void addAllIncludeFilters(Set<String> filters) { 293 for (String filter : filters) { 294 mIncludeFilters.add(cleanFilter(filter)); 295 } 296 } 297 298 /** {@inheritDoc} */ 299 @Override addExcludeFilter(String filter)300 public void addExcludeFilter(String filter) { 301 mExcludeFilters.add(cleanFilter(filter)); 302 } 303 304 /** {@inheritDoc} */ 305 @Override addAllExcludeFilters(Set<String> filters)306 public void addAllExcludeFilters(Set<String> filters) { 307 for (String filter : filters) { 308 mExcludeFilters.add(cleanFilter(filter)); 309 } 310 } 311 312 /** {@inheritDoc} */ 313 @Override clearIncludeFilters()314 public void clearIncludeFilters() { 315 mIncludeFilters.clear(); 316 // Clear the filter file key, to not impact the base filters. 317 mTestFilterKey = null; 318 } 319 320 /** {@inheritDoc} */ 321 @Override getIncludeFilters()322 public Set<String> getIncludeFilters() { 323 return mIncludeFilters; 324 } 325 326 /** {@inheritDoc} */ 327 @Override getExcludeFilters()328 public Set<String> getExcludeFilters() { 329 return mExcludeFilters; 330 } 331 332 /** {@inheritDoc} */ 333 @Override clearExcludeFilters()334 public void clearExcludeFilters() { 335 mExcludeFilters.clear(); 336 } 337 338 /** Gets module name. */ getTestModule()339 public String getTestModule() { 340 return mTestModule; 341 } 342 343 /** Gets regex to exclude certain files from executing. */ getFileExclusionFilterRegex()344 public List<String> getFileExclusionFilterRegex() { 345 return mFileExclusionFilterRegex; 346 } 347 348 /** Gets the max time for a gtest to run. */ getMaxTestTimeMs()349 public long getMaxTestTimeMs() { 350 return mMaxTestTimeMs; 351 } 352 353 /** Gets shell command(s) to run before GTest. */ getBeforeTestCmd()354 public List<String> getBeforeTestCmd() { 355 return mBeforeTestCmd; 356 } 357 358 /** Gets shell command(s) to run after GTest. */ getAfterTestCmd()359 public List<String> getAfterTestCmd() { 360 return mAfterTestCmd; 361 } 362 363 /** Gets Additional flag values to pass to the native test's shell command. */ getGTestFlags()364 public List<String> getGTestFlags() { 365 return mGTestFlags; 366 } 367 368 /** Gets test filter key. */ getTestFilterKey()369 public String getTestFilterKey() { 370 return mTestFilterKey; 371 } 372 373 /** Gets use gtest xml output for test results or not. */ isEnableXmlOutput()374 public boolean isEnableXmlOutput() { 375 return mEnableXmlOutput; 376 } 377 378 /** Gets only invoke the test binary to collect list of applicable test cases or not. */ isCollectTestsOnly()379 public boolean isCollectTestsOnly() { 380 return mCollectTestsOnly; 381 } 382 383 /** Gets isSharded flag. */ isSharded()384 public boolean isSharded() { 385 return mIsSharded; 386 } 387 388 /** 389 * Define get filter method. 390 * 391 * <p>Sub class must implement how to get it's own filter. 392 * 393 * @param path the full path of the filter file. 394 * @return filter string. 395 */ loadFilter(String path)396 protected abstract String loadFilter(String path) throws DeviceNotAvailableException; 397 398 /** 399 * Helper to get the g-test filter of test to run. 400 * 401 * <p>Note that filters filter on the function name only (eg: Google Test "Test"); all Google 402 * Test "Test Cases" will be considered. 403 * 404 * @param path the full path of the binary on the device. 405 * @return the full filter flag to pass to the g-test, or an empty string if none have been 406 * specified 407 */ getGTestFilters(String path)408 protected String getGTestFilters(String path) throws DeviceNotAvailableException { 409 StringBuilder filter = new StringBuilder(); 410 if (mTestNamePositiveFilter != null) { 411 mIncludeFilters.add(mTestNamePositiveFilter); 412 } 413 if (mTestNameNegativeFilter != null) { 414 mExcludeFilters.add(mTestNameNegativeFilter); 415 } 416 if (mTestFilterKey != null) { 417 String fileFilters = loadFilter(path); 418 if (fileFilters != null && !fileFilters.isEmpty()) { 419 if (fileFilters.startsWith("-")) { 420 for (String filterString : fileFilters.substring(1).split(":")) { 421 mExcludeFilters.add(filterString); 422 } 423 } else { 424 String[] filterStrings = fileFilters.split("-"); 425 for (String filterString : filterStrings[0].split(":")) { 426 mIncludeFilters.add(filterString); 427 } 428 if (filterStrings.length == 2) { 429 for (String filterString : filterStrings[1].split(":")) { 430 mExcludeFilters.add(filterString); 431 } 432 } 433 } 434 } 435 } 436 if (!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) { 437 filter.append(GTEST_FLAG_FILTER); 438 filter.append("="); 439 if (!mIncludeFilters.isEmpty()) { 440 filter.append(ArrayUtil.join(":", mIncludeFilters)); 441 } 442 if (!mExcludeFilters.isEmpty()) { 443 filter.append("-"); 444 filter.append(ArrayUtil.join(":", mExcludeFilters)); 445 } 446 } 447 String filterFlag = filter.toString(); 448 // Handle long args 449 if (filterFlag.length() > 500) { 450 String tmpFlag = createFlagFile(filterFlag); 451 if (tmpFlag != null) { 452 return String.format("%s=%s", GTEST_FLAG_FILE, tmpFlag); 453 } 454 } 455 456 return filterFlag; 457 } 458 459 /** 460 * Create a file containing the filters that will be used via --gtest_flagfile to avoid any OS 461 * limitation in args size. 462 * 463 * @param filter The filter string 464 * @return The path to the file containing the filter. 465 * @throws DeviceNotAvailableException 466 */ createFlagFile(String filter)467 protected String createFlagFile(String filter) throws DeviceNotAvailableException { 468 File tmpFlagFile = null; 469 try { 470 tmpFlagFile = FileUtil.createTempFile("flagfile", ".txt"); 471 FileUtil.writeToFile(filter, tmpFlagFile); 472 } catch (IOException e) { 473 FileUtil.deleteFile(tmpFlagFile); 474 CLog.e(e); 475 return null; 476 } 477 return tmpFlagFile.getAbsolutePath(); 478 } 479 480 /** 481 * Helper to get all the GTest flags to pass into the adb shell command. 482 * 483 * @param path the full path of the binary on the device. 484 * @return the {@link String} of all the GTest flags that should be passed to the GTest 485 */ getAllGTestFlags(String path)486 protected String getAllGTestFlags(String path) throws DeviceNotAvailableException { 487 String flags = String.format("%s %s", GTEST_FLAG_PRINT_TIME, getGTestFilters(path)); 488 489 if (getRunDisabledTests()) { 490 flags = String.format("%s %s", flags, GTEST_FLAG_RUN_DISABLED_TESTS); 491 } 492 493 if (isCollectTestsOnly()) { 494 flags = String.format("%s %s", flags, GTEST_FLAG_LIST_TESTS); 495 } 496 497 for (String gTestFlag : getGTestFlags()) { 498 flags = String.format("%s %s", flags, gTestFlag); 499 } 500 return flags; 501 } 502 503 /* 504 * Conforms filters using a {@link TestDescription} format to be recognized by the GTest 505 * executable. 506 */ cleanFilter(String filter)507 public String cleanFilter(String filter) { 508 return filter.replace('#', '.'); 509 } 510 511 /** 512 * Exposed for testing 513 * 514 * @param testRunName 515 * @param listener 516 * @return a {@link GTestXmlResultParser} 517 */ 518 @VisibleForTesting createXmlParser(String testRunName, ITestInvocationListener listener)519 GTestXmlResultParser createXmlParser(String testRunName, ITestInvocationListener listener) { 520 return new GTestXmlResultParser(testRunName, listener); 521 } 522 523 /** 524 * Factory method for creating a {@link IShellOutputReceiver} that parses test output and 525 * forwards results to the result listener. 526 * 527 * @param listener 528 * @param runName 529 * @return a {@link IShellOutputReceiver} 530 */ 531 @VisibleForTesting createResultParser(String runName, ITestInvocationListener listener)532 IShellOutputReceiver createResultParser(String runName, ITestInvocationListener listener) { 533 IShellOutputReceiver receiver = null; 534 if (mCollectTestsOnly) { 535 GTestListTestParser resultParser = new GTestListTestParser(runName, listener); 536 resultParser.setPrependFileName(mPrependFileName); 537 receiver = resultParser; 538 } else { 539 GTestResultParser resultParser = new GTestResultParser(runName, listener); 540 resultParser.setPrependFileName(mPrependFileName); 541 receiver = resultParser; 542 } 543 // Erase the prepended binary name if needed 544 erasePrependedFileName(mExcludeFilters, runName); 545 erasePrependedFileName(mIncludeFilters, runName); 546 return receiver; 547 } 548 549 /** 550 * Helper method to build the gtest command to run. 551 * 552 * @param fullPath absolute file system path to gtest binary on device 553 * @param flags gtest execution flags 554 * @return the shell command line to run for the gtest 555 */ getGTestCmdLine(String fullPath, String flags)556 protected String getGTestCmdLine(String fullPath, String flags) { 557 StringBuilder gTestCmdLine = new StringBuilder(); 558 if (mLdLibraryPath != null) { 559 gTestCmdLine.append(String.format("LD_LIBRARY_PATH=%s ", mLdLibraryPath)); 560 } 561 562 if (getCoverageOptions().isCoverageEnabled()) { 563 gTestCmdLine.append("GCOV_PREFIX=/data/misc/trace/testcoverage "); 564 } 565 566 // su to requested user 567 if (mRunTestAs != null) { 568 gTestCmdLine.append(String.format("su %s ", mRunTestAs)); 569 } 570 571 gTestCmdLine.append(String.format("%s %s", fullPath, flags)); 572 return gTestCmdLine.toString(); 573 } 574 575 /** {@inheritDoc} */ 576 @Override setCollectTestsOnly(boolean shouldCollectTest)577 public void setCollectTestsOnly(boolean shouldCollectTest) { 578 mCollectTestsOnly = shouldCollectTest; 579 } 580 581 /** {@inheritDoc} */ 582 @Override split(int shardCountHint)583 public Collection<IRemoteTest> split(int shardCountHint) { 584 if (shardCountHint <= 1 || mIsSharded) { 585 return null; 586 } 587 if (mCollectTestsOnly) { 588 // GTest cannot shard and use collect tests only, so prevent sharding in this case. 589 return null; 590 } 591 Collection<IRemoteTest> tests = new ArrayList<>(); 592 for (int i = 0; i < shardCountHint; i++) { 593 tests.add(getTestShard(shardCountHint, i)); 594 } 595 return tests; 596 } 597 598 /** 599 * Make a best effort attempt to retrieve a meaningful short descriptive message for given 600 * {@link Exception} 601 * 602 * @param e the {@link Exception} 603 * @return a short message 604 */ getExceptionMessage(Exception e)605 protected String getExceptionMessage(Exception e) { 606 StringBuilder msgBuilder = new StringBuilder(); 607 if (e.getMessage() != null) { 608 msgBuilder.append(e.getMessage()); 609 } 610 if (e.getCause() != null) { 611 msgBuilder.append(" cause:"); 612 msgBuilder.append(e.getCause().getClass().getSimpleName()); 613 if (e.getCause().getMessage() != null) { 614 msgBuilder.append(" ("); 615 msgBuilder.append(e.getCause().getMessage()); 616 msgBuilder.append(")"); 617 } 618 } 619 return msgBuilder.toString(); 620 } 621 erasePrependedFileName(Set<String> filters, String filename)622 protected void erasePrependedFileName(Set<String> filters, String filename) { 623 if (!mPrependFileName) { 624 return; 625 } 626 Set<String> copy = new LinkedHashSet<>(); 627 for (String filter : filters) { 628 if (filter.startsWith(filename + ".")) { 629 copy.add(filter.substring(filename.length() + 1)); 630 } else { 631 copy.add(filter); 632 } 633 } 634 filters.clear(); 635 filters.addAll(copy); 636 } 637 getTestShard(int shardCount, int shardIndex)638 private IRemoteTest getTestShard(int shardCount, int shardIndex) { 639 GTestBase shard = null; 640 try { 641 shard = this.getClass().getDeclaredConstructor().newInstance(); 642 OptionCopier.copyOptionsNoThrow(this, shard); 643 shard.mShardIndex = shardIndex; 644 shard.mShardCount = shardCount; 645 shard.mIsSharded = true; 646 // We approximate the runtime of each shard to be equal since we can't know. 647 shard.mRuntimeHint = mRuntimeHint / shardCount; 648 shard.mAbi = mAbi; 649 } catch (InstantiationException 650 | IllegalAccessException 651 | InvocationTargetException 652 | NoSuchMethodException e) { 653 // This cannot happen because the class was already created once at that point. 654 throw new RuntimeException( 655 String.format( 656 "%s (%s) when attempting to create shard object", 657 e.getClass().getSimpleName(), getExceptionMessage(e))); 658 } 659 return shard; 660 } 661 662 /** 663 * Returns the test configuration. 664 * 665 * @return an IConfiguration 666 */ getConfiguration()667 protected IConfiguration getConfiguration() { 668 return mConfiguration; 669 } 670 671 /** 672 * Returns the {@link CoverageOptions} for this test, if it exists. Otherwise returns a default 673 * {@link CoverageOptions} object with all coverage disabled. 674 */ getCoverageOptions()675 protected CoverageOptions getCoverageOptions() { 676 if (mConfiguration != null) { 677 return mConfiguration.getCoverageOptions(); 678 } 679 return new CoverageOptions(); 680 } 681 682 /** 683 * Returns the {@link GTestListener} that provides extra debugging info, like detects and 684 * reports duplicate tests if mDisabledDuplicateCheck is false. Otherwise, returns the passed-in 685 * listener. 686 */ getGTestListener(ITestInvocationListener listener)687 protected ITestInvocationListener getGTestListener(ITestInvocationListener listener) { 688 if (mDisableDuplicateCheck) { 689 return listener; 690 } 691 692 return new GTestListener(listener); 693 } 694 } 695