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