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