Home | History | Annotate | Download | only in testtype
      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
    180     public void setDevice(ITestDevice device) {
    181         mDevice = device;
    182     }
    183 
    184     /**
    185      * {@inheritDoc}
    186      */
    187     @Override
    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      */
    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      */
    206     public String getModuleName() {
    207         return mTestModule;
    208     }
    209 
    210     /**
    211      * Set whether GTest should run disabled tests.
    212      */
    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      */
    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
    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
    240     void addFileExclusionFilterRegex(String regex) {
    241         mFileExclusionFilterRegex.add(regex);
    242     }
    243 
    244     /**
    245      * Sets the shard index of this test.
    246      */
    247     @VisibleForTesting
    248     void setShardIndex(int shardIndex) {
    249         mShardIndex = shardIndex;
    250     }
    251 
    252     /**
    253      * Gets the shard index of this test.
    254      */
    255     @VisibleForTesting
    256     int getShardIndex() {
    257         return mShardIndex;
    258     }
    259 
    260     /**
    261      * Sets the shard count of this test.
    262      */
    263     @VisibleForTesting
    264     void setShardCount(int shardCount) {
    265         mShardCount = shardCount;
    266     }
    267 
    268     /**
    269      * Sets the shard count of this test.
    270      */
    271     @VisibleForTesting
    272     int getShardCount() {
    273         return mShardCount;
    274     }
    275 
    276     /**
    277      * {@inheritDoc}
    278      */
    279     @Override
    280     public long getRuntimeHint() {
    281         return mRuntimeHint;
    282     }
    283 
    284     /**
    285      * {@inheritDoc}
    286      */
    287     @Override
    288     public void addIncludeFilter(String filter) {
    289         mIncludeFilters.add(cleanFilter(filter));
    290     }
    291 
    292     /**
    293      * {@inheritDoc}
    294      */
    295     @Override
    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
    306     public void addExcludeFilter(String filter) {
    307         mExcludeFilters.add(cleanFilter(filter));
    308     }
    309 
    310     /**
    311      * {@inheritDoc}
    312      */
    313     @Override
    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      */
    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      */
    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 
    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      */
    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      */
    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
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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
    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
    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
    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
    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
    763     public void setCollectTestsOnly(boolean shouldCollectTest) {
    764         mCollectTestsOnly = shouldCollectTest;
    765     }
    766 
    767 }
    768