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