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