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