1 /*
2  * Copyright (C) 2010 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.FileListingService;
20 import com.android.ddmlib.IShellOutputReceiver;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.config.OptionCopier;
24 import com.android.tradefed.device.CollectingOutputReceiver;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.result.ITestInvocationListener;
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 org.json.JSONException;
35 import org.json.JSONObject;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 import java.util.concurrent.TimeUnit;
45 import java.util.regex.Pattern;
46 
47 /** A Test that runs a native test package on given device. */
48 @OptionClass(alias = "gtest")
49 public class GTest
50         implements IDeviceTest,
51                 IRemoteTest,
52                 ITestFilterReceiver,
53                 IRuntimeHintProvider,
54                 ITestCollector,
55                 IShardableTest,
56                 IStrictShardableTest {
57 
58     static final String DEFAULT_NATIVETEST_PATH = "/data/nativetest";
59     private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+");
60 
61     private ITestDevice mDevice = null;
62     private boolean mRunDisabledTests = false;
63 
64     @Option(name = "native-test-device-path",
65             description="The path on the device where native tests are located.")
66     private String mNativeTestDevicePath = DEFAULT_NATIVETEST_PATH;
67 
68     @Option(name = "file-exclusion-filter-regex",
69             description = "Regex to exclude certain files from executing. Can be repeated")
70     private List<String> mFileExclusionFilterRegex = new ArrayList<>();
71 
72     @Option(name = "module-name",
73             description="The name of the native test module to run.")
74     private String mTestModule = null;
75 
76     @Option(name = "positive-testname-filter",
77             description="The GTest-based positive filter of the test name to run.")
78     private String mTestNamePositiveFilter = null;
79     @Option(name = "negative-testname-filter",
80             description="The GTest-based negative filter of the test name to run.")
81     private String mTestNameNegativeFilter = null;
82 
83     @Option(name = "include-filter",
84             description="The GTest-based positive filter of the test names to run.")
85     private Set<String> mIncludeFilters = new HashSet<>();
86     @Option(name = "exclude-filter",
87             description="The GTest-based negative filter of the test names to run.")
88     private Set<String> mExcludeFilters = new HashSet<>();
89 
90     @Option(
91         name = "native-test-timeout",
92         description =
93                 "The max time for a gtest to run. Test run will be aborted if any test "
94                         + "takes longer.",
95         isTimeVal = true
96     )
97     private long mMaxTestTimeMs = 1 * 60 * 1000L;
98 
99     @Option(name = "send-coverage",
100             description = "Send coverage target info to test listeners.")
101     private boolean mSendCoverage = true;
102 
103     @Option(name ="prepend-filename",
104             description = "Prepend filename as part of the classname for the tests.")
105     private boolean mPrependFileName = false;
106 
107     @Option(name = "before-test-cmd",
108             description = "adb shell command(s) to run before GTest.")
109     private List<String> mBeforeTestCmd = new ArrayList<>();
110 
111 
112     @Option(
113         name = "reboot-before-test",
114         description = "Reboot the device before the test suite starts."
115     )
116     private boolean mRebootBeforeTest = false;
117 
118     @Option(name = "after-test-cmd",
119             description = "adb shell command(s) to run after GTest.")
120     private List<String> mAfterTestCmd = new ArrayList<>();
121 
122     @Option(name = "run-test-as", description = "User to execute test binary as.")
123     private String mRunTestAs = null;
124 
125     @Option(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(name = "native-test-flag", description =
130             "Additional flag values to pass to the native test's shell command. " +
131             "Flags should be complete, including any necessary dashes: \"--flag=value\"")
132     private List<String> mGTestFlags = new ArrayList<>();
133 
134     @Option(name = "runtime-hint", description="The hint about the test's runtime.",
135             isTimeVal = true)
136     private long mRuntimeHint = 60000;// 1 minute
137 
138     @Option(name = "xml-output", description = "Use gtest xml output for test results, "
139             + "if test binaries crash, no output will be available.")
140     private boolean mEnableXmlOutput = false;
141 
142     @Option(name = "stop-runtime",
143             description = "Stops the Java application runtime before test execution.")
144     private boolean mStopRuntime = false;
145 
146     @Option(name = "collect-tests-only",
147             description = "Only invoke the test binary to collect list of applicable test cases. "
148                     + "All test run callbacks will be triggered, but test execution will "
149                     + "not be actually carried out. This option ignores sharding parameters, so "
150                     + "each shard will end up collecting all tests.")
151     private boolean mCollectTestsOnly = false;
152 
153     @Option(name = "test-filter-key",
154             description = "run the gtest with the --gtest_filter populated with the filter from "
155                     + "the json filter file associated with the binary, the filter file will have "
156                     + "the same name as the binary with the .json extension.")
157     private String mTestFilterKey = null;
158 
159     private int mShardCount = 0;
160     private int mShardIndex = 0;
161     private boolean mIsSharded = false;
162 
163     /** coverage target value. Just report all gtests as 'native' for now */
164     private static final String COVERAGE_TARGET = "Native";
165 
166     // GTest flags...
167     private static final String GTEST_FLAG_PRINT_TIME = "--gtest_print_time";
168     private static final String GTEST_FLAG_FILTER = "--gtest_filter";
169     private static final String GTEST_FLAG_RUN_DISABLED_TESTS = "--gtest_also_run_disabled_tests";
170     private static final String GTEST_FLAG_LIST_TESTS = "--gtest_list_tests";
171     private static final String GTEST_XML_OUTPUT = "--gtest_output=xml:%s";
172     // Max characters allowed for executing GTest via command line
173     private static final int GTEST_CMD_CHAR_LIMIT = 1000;
174     // Expected extension for the filter file associated with the binary (json formatted file)
175     protected static final String FILTER_EXTENSION = ".filter";
176     /**
177      * {@inheritDoc}
178      */
179     @Override
setDevice(ITestDevice device)180     public void setDevice(ITestDevice device) {
181         mDevice = device;
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
getDevice()188     public ITestDevice getDevice() {
189         return mDevice;
190     }
191 
192     /**
193      * Set the Android native test module to run.
194      *
195      * @param moduleName The name of the native test module to run
196      */
setModuleName(String moduleName)197     public void setModuleName(String moduleName) {
198         mTestModule = moduleName;
199     }
200 
201     /**
202      * Get the Android native test module to run.
203      *
204      * @return the name of the native test module to run, or null if not set
205      */
getModuleName()206     public String getModuleName() {
207         return mTestModule;
208     }
209 
210     /**
211      * Set whether GTest should run disabled tests.
212      */
setRunDisabled(boolean runDisabled)213     public void setRunDisabled(boolean runDisabled) {
214         mRunDisabledTests = runDisabled;
215     }
216 
217     /**
218      * Get whether GTest should run disabled tests.
219      *
220      * @return True if disabled tests should be run, false otherwise
221      */
getRunDisabledTests()222     public boolean getRunDisabledTests() {
223         return mRunDisabledTests;
224     }
225 
226     /**
227      * Set the max time in ms for a gtest to run.
228      */
229     @VisibleForTesting
setMaxTestTimeMs(int timeout)230     void setMaxTestTimeMs(int timeout) {
231         mMaxTestTimeMs = timeout;
232     }
233 
234     /**
235      * Adds an exclusion file filter regex.
236      *
237      * @param regex to exclude file.
238      */
239     @VisibleForTesting
addFileExclusionFilterRegex(String regex)240     void addFileExclusionFilterRegex(String regex) {
241         mFileExclusionFilterRegex.add(regex);
242     }
243 
244     /**
245      * Sets the shard index of this test.
246      */
247     @VisibleForTesting
setShardIndex(int shardIndex)248     void setShardIndex(int shardIndex) {
249         mShardIndex = shardIndex;
250     }
251 
252     /**
253      * Gets the shard index of this test.
254      */
255     @VisibleForTesting
getShardIndex()256     int getShardIndex() {
257         return mShardIndex;
258     }
259 
260     /**
261      * Sets the shard count of this test.
262      */
263     @VisibleForTesting
setShardCount(int shardCount)264     void setShardCount(int shardCount) {
265         mShardCount = shardCount;
266     }
267 
268     /**
269      * Sets the shard count of this test.
270      */
271     @VisibleForTesting
getShardCount()272     int getShardCount() {
273         return mShardCount;
274     }
275 
276     /**
277      * {@inheritDoc}
278      */
279     @Override
getRuntimeHint()280     public long getRuntimeHint() {
281         return mRuntimeHint;
282     }
283 
284     /**
285      * {@inheritDoc}
286      */
287     @Override
addIncludeFilter(String filter)288     public void addIncludeFilter(String filter) {
289         mIncludeFilters.add(cleanFilter(filter));
290     }
291 
292     /**
293      * {@inheritDoc}
294      */
295     @Override
addAllIncludeFilters(Set<String> filters)296     public void addAllIncludeFilters(Set<String> filters) {
297         for (String filter : filters) {
298             mIncludeFilters.add(cleanFilter(filter));
299         }
300     }
301 
302     /**
303      * {@inheritDoc}
304      */
305     @Override
addExcludeFilter(String filter)306     public void addExcludeFilter(String filter) {
307         mExcludeFilters.add(cleanFilter(filter));
308     }
309 
310     /**
311      * {@inheritDoc}
312      */
313     @Override
addAllExcludeFilters(Set<String> filters)314     public void addAllExcludeFilters(Set<String> filters) {
315         for (String filter : filters) {
316             mExcludeFilters.add(cleanFilter(filter));
317         }
318     }
319 
320     /*
321      * Conforms filters using a {@link TestDescription} format to be recognized by the GTest
322      * executable.
323      */
cleanFilter(String filter)324     private String cleanFilter(String filter) {
325         return filter.replace('#', '.');
326     }
327 
328     /**
329      * Helper to get the adb gtest filter of test to run.
330      *
331      * Note that filters filter on the function name only (eg: Google Test "Test"); all Google Test
332      * "Test Cases" will be considered.
333      *
334      * @param binaryOnDevice the full path of the binary on the device.
335      * @return the full filter flag to pass to the Gtest, or an empty string if none have been
336      * specified
337      */
getGTestFilters(String binaryOnDevice)338     private String getGTestFilters(String binaryOnDevice) throws DeviceNotAvailableException {
339         StringBuilder filter = new StringBuilder();
340         if (mTestNamePositiveFilter != null) {
341             mIncludeFilters.add(mTestNamePositiveFilter);
342         }
343         if (mTestNameNegativeFilter != null) {
344             mExcludeFilters.add(mTestNameNegativeFilter);
345         }
346         if (mTestFilterKey != null) {
347             if (!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) {
348                 CLog.w("Using json file filter, --include/exclude-filter will be ignored.");
349             }
350             String fileFilters = loadFilter(binaryOnDevice);
351             if (fileFilters != null && !fileFilters.isEmpty()) {
352                 filter.append(GTEST_FLAG_FILTER);
353                 filter.append("=");
354                 filter.append(fileFilters);
355             }
356         } else {
357             if (!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) {
358                 filter.append(GTEST_FLAG_FILTER);
359                 filter.append("=");
360                 if (!mIncludeFilters.isEmpty()) {
361                   filter.append(ArrayUtil.join(":", mIncludeFilters));
362                 }
363                 if (!mExcludeFilters.isEmpty()) {
364                   filter.append("-");
365                   filter.append(ArrayUtil.join(":", mExcludeFilters));
366               }
367             }
368         }
369         return filter.toString();
370     }
371 
loadFilter(String binaryOnDevice)372     private String loadFilter(String binaryOnDevice) throws DeviceNotAvailableException {
373         CLog.i("Loading filter from file for key: '%s'", mTestFilterKey);
374         String filterFile = String.format("%s%s", binaryOnDevice, FILTER_EXTENSION);
375         if (getDevice().doesFileExist(filterFile)) {
376             String content =
377                     getDevice().executeShellCommand(String.format("cat \"%s\"", filterFile));
378             if (content != null && !content.isEmpty()) {
379                 try {
380                     JSONObject filter = new JSONObject(content);
381                     String key = mTestFilterKey;
382                     JSONObject filterObject = filter.getJSONObject(key);
383                     return filterObject.getString("filter");
384                 } catch (JSONException e) {
385                     CLog.e(e);
386                 }
387             }
388             CLog.e("Error with content of the filter file %s: %s", filterFile, content);
389         } else {
390             CLog.e("Filter file %s not found", filterFile);
391         }
392         return null;
393     }
394 
395     /**
396      * Helper to get all the GTest flags to pass into the adb shell command.
397      *
398      * @param binaryOnDevice the full path of the binary on the device.
399      * @return the {@link String} of all the GTest flags that should be passed to the GTest
400      */
getAllGTestFlags(String binaryOnDevice)401     private String getAllGTestFlags(String binaryOnDevice) throws DeviceNotAvailableException {
402         String flags = String.format("%s %s", GTEST_FLAG_PRINT_TIME,
403                 getGTestFilters(binaryOnDevice));
404 
405         if (mRunDisabledTests) {
406             flags = String.format("%s %s", flags, GTEST_FLAG_RUN_DISABLED_TESTS);
407         }
408 
409         if (mCollectTestsOnly) {
410             flags = String.format("%s %s", flags, GTEST_FLAG_LIST_TESTS);
411         }
412 
413         for (String gTestFlag : mGTestFlags) {
414             flags = String.format("%s %s", flags, gTestFlag);
415         }
416         return flags;
417     }
418 
419     /**
420      * Gets the path where native tests live on the device.
421      *
422      * @return The path on the device where the native tests live.
423      */
getTestPath()424     private String getTestPath() {
425         StringBuilder testPath = new StringBuilder(mNativeTestDevicePath);
426         if (mTestModule != null) {
427             testPath.append(FileListingService.FILE_SEPARATOR);
428             testPath.append(mTestModule);
429         }
430         return testPath.toString();
431     }
432 
433     /**
434      * Executes all native tests in a folder as well as in all subfolders recursively.
435      *
436      * @param root The root folder to begin searching for native tests
437      * @param testDevice The device to run tests on
438      * @param listener the {@link ITestInvocationListener}
439      * @throws DeviceNotAvailableException
440      */
441     @VisibleForTesting
doRunAllTestsInSubdirectory( String root, ITestDevice testDevice, ITestInvocationListener listener)442     void doRunAllTestsInSubdirectory(
443             String root, ITestDevice testDevice, ITestInvocationListener listener)
444             throws DeviceNotAvailableException {
445         if (testDevice.isDirectory(root)) {
446             // recursively run tests in all subdirectories
447             for (String child : testDevice.getChildren(root)) {
448                 doRunAllTestsInSubdirectory(root + "/" + child, testDevice, listener);
449             }
450         } else {
451             // assume every file is a valid gtest binary.
452             IShellOutputReceiver resultParser = createResultParser(getFileName(root), listener);
453             if (shouldSkipFile(root)) {
454                 return;
455             }
456             String flags = getAllGTestFlags(root);
457             CLog.i("Running gtest %s %s on %s", root, flags, testDevice.getSerialNumber());
458             if (mEnableXmlOutput) {
459                 runTestXml(testDevice, root, flags, listener);
460             } else {
461                 runTest(testDevice, resultParser, root, flags);
462             }
463         }
464     }
465 
getFileName(String fullPath)466     String getFileName(String fullPath) {
467         int pos = fullPath.lastIndexOf('/');
468         if (pos == -1) {
469             return fullPath;
470         }
471         String fileName = fullPath.substring(pos + 1);
472         if (fileName.isEmpty()) {
473             throw new IllegalArgumentException("input should not end with \"/\"");
474         }
475         return fileName;
476     }
477 
isDeviceFileExecutable(String fullPath)478     protected boolean isDeviceFileExecutable(String fullPath) throws DeviceNotAvailableException {
479         String fileMode = mDevice.executeShellCommand(String.format("ls -l %s", fullPath));
480         if (fileMode != null) {
481             return EXE_FILE.matcher(fileMode).find();
482         }
483         return false;
484     }
485 
486     /**
487      * Helper method to determine if we should skip the execution of a given file.
488      *
489      * @param fullPath the full path of the file in question
490      * @return true if we should skip the said file.
491      */
shouldSkipFile(String fullPath)492     protected boolean shouldSkipFile(String fullPath) throws DeviceNotAvailableException {
493         if (fullPath == null || fullPath.isEmpty()) {
494             return true;
495         }
496         // skip any file that's not executable
497         if (!isDeviceFileExecutable(fullPath)) {
498             return true;
499         }
500         if (mFileExclusionFilterRegex == null || mFileExclusionFilterRegex.isEmpty()) {
501             return false;
502         }
503         for (String regex : mFileExclusionFilterRegex) {
504             if (fullPath.matches(regex)) {
505                 CLog.i("File %s matches exclusion file regex %s, skipping", fullPath, regex);
506                 return true;
507             }
508         }
509         return false;
510     }
511 
512     /**
513      * Helper method to run a gtest command from a temporary script, in the case that the command
514      * is too long to be run directly by adb.
515      * @param testDevice the device on which to run the command
516      * @param cmd the command string to run
517      * @param resultParser the output receiver for reading test results
518      */
executeCommandByScript(final ITestDevice testDevice, final String cmd, final IShellOutputReceiver resultParser)519     protected void executeCommandByScript(final ITestDevice testDevice, final String cmd,
520             final IShellOutputReceiver resultParser) throws DeviceNotAvailableException {
521         String tmpFileDevice = "/data/local/tmp/gtest_script.sh";
522         testDevice.pushString(String.format("#!/bin/bash\n%s", cmd), tmpFileDevice);
523         // force file to be executable
524         testDevice.executeShellCommand(String.format("chmod 755 %s", tmpFileDevice));
525         testDevice.executeShellCommand(String.format("sh %s", tmpFileDevice),
526                 resultParser, mMaxTestTimeMs /* maxTimeToShellOutputResponse */,
527                 TimeUnit.MILLISECONDS, 0 /* retry attempts */);
528         testDevice.executeShellCommand(String.format("rm %s", tmpFileDevice));
529     }
530 
531     /**
532      * Run the given gtest binary
533      *
534      * @param testDevice the {@link ITestDevice}
535      * @param resultParser the test run output parser
536      * @param fullPath absolute file system path to gtest binary on device
537      * @param flags gtest execution flags
538      * @throws DeviceNotAvailableException
539      */
runTest(final ITestDevice testDevice, final IShellOutputReceiver resultParser, final String fullPath, final String flags)540     private void runTest(final ITestDevice testDevice, final IShellOutputReceiver resultParser,
541             final String fullPath, final String flags) throws DeviceNotAvailableException {
542         // TODO: add individual test timeout support, and rerun support
543         try {
544             for (String cmd : mBeforeTestCmd) {
545                 testDevice.executeShellCommand(cmd);
546             }
547 
548             if (mRebootBeforeTest) {
549                 CLog.d("Rebooting device before test starts as requested.");
550                 testDevice.reboot();
551             }
552 
553             String cmd = getGTestCmdLine(fullPath, flags);
554             // ensure that command is not too long for adb
555             if (cmd.length() < GTEST_CMD_CHAR_LIMIT) {
556                 testDevice.executeShellCommand(cmd, resultParser,
557                         mMaxTestTimeMs /* maxTimeToShellOutputResponse */,
558                         TimeUnit.MILLISECONDS,
559                         0 /* retryAttempts */);
560             } else {
561                 // wrap adb shell command in script if command is too long for direct execution
562                 executeCommandByScript(testDevice, cmd, resultParser);
563             }
564         } catch (DeviceNotAvailableException e) {
565             throw e;
566         } catch (RuntimeException e) {
567             throw e;
568         } finally {
569             // TODO: consider moving the flush of parser data on exceptions to TestDevice or
570             // AdbHelper
571             resultParser.flush();
572             for (String cmd : mAfterTestCmd) {
573                 testDevice.executeShellCommand(cmd);
574             }
575         }
576     }
577 
578     /**
579      * Run the given gtest binary and parse XML results This methods typically requires the filter
580      * for .tff and .xml files, otherwise it will post some unwanted results.
581      *
582      * @param testDevice the {@link ITestDevice}
583      * @param fullPath absolute file system path to gtest binary on device
584      * @param flags gtest execution flags
585      * @param listener the {@link ITestInvocationListener}
586      * @throws DeviceNotAvailableException
587      */
runTestXml( final ITestDevice testDevice, final String fullPath, final String flags, ITestInvocationListener listener)588     private void runTestXml(
589             final ITestDevice testDevice,
590             final String fullPath,
591             final String flags,
592             ITestInvocationListener listener)
593             throws DeviceNotAvailableException {
594         CollectingOutputReceiver outputCollector = new CollectingOutputReceiver();
595         File tmpOutput = null;
596         try {
597             String testRunName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
598             tmpOutput = FileUtil.createTempFile(testRunName, ".xml");
599             String tmpResName = fullPath + "_res.xml";
600             String extraFlag = String.format(GTEST_XML_OUTPUT, tmpResName);
601             String fullFlagCmd =  String.format("%s %s", flags, extraFlag);
602 
603             // Run the tests with modified flags
604             runTest(testDevice, outputCollector, fullPath, fullFlagCmd);
605             // Pull the result file, may not exist if issue with the test.
606             testDevice.pullFile(tmpResName, tmpOutput);
607             // Clean the file on the device
608             testDevice.executeShellCommand("rm " + tmpResName);
609             GTestXmlResultParser parser = createXmlParser(testRunName, listener);
610             // Attempt to parse the file, doesn't matter if the content is invalid.
611             if (tmpOutput.exists()) {
612                 parser.parseResult(tmpOutput, outputCollector);
613             }
614         } catch (DeviceNotAvailableException | RuntimeException e) {
615             throw e;
616         } catch (IOException e) {
617             throw new RuntimeException(e);
618         } finally {
619             outputCollector.flush();
620             for (String cmd : mAfterTestCmd) {
621                 testDevice.executeShellCommand(cmd);
622             }
623             FileUtil.deleteFile(tmpOutput);
624         }
625     }
626 
627     /**
628      * Exposed for testing
629      *
630      * @param testRunName
631      * @param listener
632      * @return a {@link GTestXmlResultParser}
633      */
634     @VisibleForTesting
createXmlParser(String testRunName, ITestInvocationListener listener)635     GTestXmlResultParser createXmlParser(String testRunName, ITestInvocationListener listener) {
636         return new GTestXmlResultParser(testRunName, listener);
637     }
638 
639     /**
640      * Helper method to build the gtest command to run.
641      *
642      * @param fullPath absolute file system path to gtest binary on device
643      * @param flags gtest execution flags
644      * @return the shell command line to run for the gtest
645      */
getGTestCmdLine(String fullPath, String flags)646     protected String getGTestCmdLine(String fullPath, String flags) {
647         StringBuilder gTestCmdLine = new StringBuilder();
648         if (mLdLibraryPath != null) {
649             gTestCmdLine.append(String.format("LD_LIBRARY_PATH=%s ", mLdLibraryPath));
650         }
651         if (mShardCount > 0) {
652             if (mCollectTestsOnly) {
653                 CLog.w("--collect-tests-only option ignores sharding parameters, and will cause "
654                         + "each shard to collect all tests.");
655             }
656             gTestCmdLine.append(String.format("GTEST_SHARD_INDEX=%s ", mShardIndex));
657             gTestCmdLine.append(String.format("GTEST_TOTAL_SHARDS=%s ", mShardCount));
658         }
659 
660         // su to requested user
661         if (mRunTestAs != null) {
662             gTestCmdLine.append(String.format("su %s ", mRunTestAs));
663         }
664 
665         gTestCmdLine.append(String.format("%s %s", fullPath, flags));
666         return gTestCmdLine.toString();
667     }
668 
669     /**
670      * {@inheritDoc}
671      */
672     @Override
getTestShard(int shardCount, int shardIndex)673     public IRemoteTest getTestShard(int shardCount, int shardIndex) {
674         GTest shard = new GTest();
675         OptionCopier.copyOptionsNoThrow(this, shard);
676         shard.mShardIndex = shardIndex;
677         shard.mShardCount = shardCount;
678         shard.mIsSharded = true;
679         // We approximate the runtime of each shard to be equal since we can't know.
680         shard.mRuntimeHint = mRuntimeHint / shardCount;
681         return shard;
682     }
683 
684     /** {@inheritDoc} */
685     @Override
split(int shardCountHint)686     public Collection<IRemoteTest> split(int shardCountHint) {
687         if (shardCountHint <= 1 || mIsSharded) {
688             return null;
689         }
690         Collection<IRemoteTest> tests = new ArrayList<>();
691         for (int i = 0; i < shardCountHint; i++) {
692             tests.add(getTestShard(shardCountHint, i));
693         }
694         return tests;
695     }
696 
697     /**
698      * Factory method for creating a {@link IShellOutputReceiver} that parses test output and
699      * forwards results to the result listener.
700      *
701      * @param listener
702      * @param runName
703      * @return a {@link IShellOutputReceiver}
704      */
705     @VisibleForTesting
createResultParser(String runName, ITestInvocationListener listener)706     IShellOutputReceiver createResultParser(String runName, ITestInvocationListener listener) {
707         IShellOutputReceiver receiver = null;
708         if (mCollectTestsOnly) {
709             GTestListTestParser resultParser = new GTestListTestParser(runName, listener);
710             resultParser.setPrependFileName(mPrependFileName);
711             receiver = resultParser;
712         } else {
713             GTestResultParser resultParser = new GTestResultParser(runName, listener);
714             resultParser.setPrependFileName(mPrependFileName);
715             // TODO: find a better solution for sending coverage info
716             if (mSendCoverage) {
717                 resultParser.setCoverageTarget(COVERAGE_TARGET);
718             }
719             receiver = resultParser;
720         }
721         return receiver;
722     }
723 
724     /**
725      * {@inheritDoc}
726      */
727     @Override
run(ITestInvocationListener listener)728     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
729         // TODO: add support for rerunning tests
730         if (mDevice == null) {
731             throw new IllegalArgumentException("Device has not been set");
732         }
733 
734         String testPath = getTestPath();
735         if (!mDevice.doesFileExist(testPath)) {
736             CLog.w("Could not find native test directory %s in %s!", testPath,
737                     mDevice.getSerialNumber());
738             return;
739         }
740         if (mStopRuntime) {
741             mDevice.executeShellCommand("stop");
742         }
743         Throwable throwable = null;
744         try {
745             doRunAllTestsInSubdirectory(testPath, mDevice, listener);
746         } catch (Throwable t) {
747             throwable = t;
748             throw t;
749         } finally {
750             if (!(throwable instanceof DeviceNotAvailableException)) {
751                 if (mStopRuntime) {
752                     mDevice.executeShellCommand("start");
753                     mDevice.waitForDeviceAvailable();
754                 }
755             }
756         }
757     }
758 
759     /**
760      * {@inheritDoc}
761      */
762     @Override
setCollectTestsOnly(boolean shouldCollectTest)763     public void setCollectTestsOnly(boolean shouldCollectTest) {
764         mCollectTestsOnly = shouldCollectTest;
765     }
766 
767 }
768