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.cts.tradefed.testtype;
     18 
     19 import com.android.cts.tradefed.build.CtsBuildHelper;
     20 import com.android.cts.tradefed.device.DeviceInfoCollector;
     21 import com.android.cts.tradefed.result.CtsTestStatus;
     22 import com.android.cts.tradefed.result.PlanCreator;
     23 import com.android.ddmlib.Log;
     24 import com.android.ddmlib.Log.LogLevel;
     25 import com.android.ddmlib.testrunner.TestIdentifier;
     26 import com.android.tradefed.build.IBuildInfo;
     27 import com.android.tradefed.config.ConfigurationException;
     28 import com.android.tradefed.config.Option;
     29 import com.android.tradefed.config.Option.Importance;
     30 import com.android.tradefed.device.DeviceNotAvailableException;
     31 import com.android.tradefed.device.ITestDevice;
     32 import com.android.tradefed.device.TestDeviceOptions;
     33 import com.android.tradefed.log.LogUtil.CLog;
     34 import com.android.tradefed.result.ITestInvocationListener;
     35 import com.android.tradefed.result.InputStreamSource;
     36 import com.android.tradefed.result.LogDataType;
     37 import com.android.tradefed.result.ResultForwarder;
     38 import com.android.tradefed.testtype.IBuildReceiver;
     39 import com.android.tradefed.testtype.IDeviceTest;
     40 import com.android.tradefed.testtype.IRemoteTest;
     41 import com.android.tradefed.testtype.IResumableTest;
     42 import com.android.tradefed.testtype.IShardableTest;
     43 import com.android.tradefed.util.RunUtil;
     44 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
     45 
     46 import java.io.BufferedInputStream;
     47 import java.io.File;
     48 import java.io.FileInputStream;
     49 import java.io.FileNotFoundException;
     50 import java.io.InputStream;
     51 import java.lang.InterruptedException;
     52 import java.lang.System;
     53 import java.lang.Thread;
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.Collection;
     57 import java.util.HashMap;
     58 import java.util.HashSet;
     59 import java.util.LinkedHashSet;
     60 import java.util.LinkedList;
     61 import java.util.List;
     62 import java.util.Map;
     63 import java.util.Queue;
     64 import java.util.Set;
     65 
     66 import junit.framework.Test;
     67 
     68 /**
     69  * A {@link Test} for running CTS tests.
     70  * <p/>
     71  * Supports running all the tests contained in a CTS plan, or individual test packages.
     72  */
     73 public class CtsTest implements IDeviceTest, IResumableTest, IShardableTest, IBuildReceiver {
     74     private static final String LOG_TAG = "CtsTest";
     75 
     76     public static final String PLAN_OPTION = "plan";
     77     private static final String PACKAGE_OPTION = "package";
     78     private static final String CLASS_OPTION = "class";
     79     private static final String METHOD_OPTION = "method";
     80     public static final String CONTINUE_OPTION = "continue-session";
     81     public static final String RUN_KNOWN_FAILURES_OPTION = "run-known-failures";
     82 
     83     public static final String PACKAGE_NAME_METRIC = "packageName";
     84     public static final String PACKAGE_DIGEST_METRIC = "packageDigest";
     85 
     86     private ITestDevice mDevice;
     87 
     88     @Option(name = PLAN_OPTION, description = "the test plan to run.",
     89             importance = Importance.IF_UNSET)
     90     private String mPlanName = null;
     91 
     92     @Option(name = PACKAGE_OPTION, shortName = 'p', description = "the test packages(s) to run.",
     93             importance = Importance.IF_UNSET)
     94     private Collection<String> mPackageNames = new ArrayList<String>();
     95 
     96     @Option(name = "exclude-package", description = "the test packages(s) to exclude from the run.")
     97     private Collection<String> mExcludedPackageNames = new ArrayList<String>();
     98 
     99     @Option(name = CLASS_OPTION, shortName = 'c', description = "run a specific test class.",
    100             importance = Importance.IF_UNSET)
    101     private String mClassName = null;
    102 
    103     @Option(name = METHOD_OPTION, shortName = 'm',
    104             description = "run a specific test method, from given --class.",
    105             importance = Importance.IF_UNSET)
    106     private String mMethodName = null;
    107 
    108     @Option(name = CONTINUE_OPTION,
    109             description = "continue a previous test session.",
    110             importance = Importance.IF_UNSET)
    111     private Integer mContinueSessionId = null;
    112 
    113     @Option(name = "skip-device-info", shortName = 'd', description =
    114         "flag to control whether to collect info from device. Providing this flag will speed up " +
    115         "test execution for short test runs but will result in required data being omitted from " +
    116         "the test report.")
    117     private boolean mSkipDeviceInfo = false;
    118 
    119     @Option(name = "resume", description =
    120         "flag to attempt to automatically resume aborted test run on another connected device. ")
    121     private boolean mResume = false;
    122 
    123     @Option(name = "shards", description =
    124         "shard the tests to run into separately runnable chunks to execute on multiple devices " +
    125         "concurrently.")
    126     private int mShards = 1;
    127 
    128     @Option(name = "screenshot", description =
    129         "flag for taking a screenshot of the device when test execution is complete.")
    130     private boolean mScreenshot = false;
    131 
    132     @Option(name = "bugreport", shortName = 'b', description =
    133         "take a bugreport after each failed test. " +
    134         "Warning: can potentially use a lot of disk space.")
    135     private boolean mBugreport = false;
    136 
    137     @Option(name = RUN_KNOWN_FAILURES_OPTION, shortName = 'k', description =
    138         "run tests including known failures")
    139     private boolean mIncludeKnownFailures;
    140 
    141     @Option(name = "disable-reboot", description =
    142             "Do not reboot device after running some amount of tests. Default behavior is to reboot.")
    143     private boolean mDisableReboot = false;
    144 
    145     @Option(name = "reboot-wait-time", description =
    146             "Additional wait time in ms after boot complete.")
    147     private int mRebootWaitTimeMSec = 2 * 60 * 1000;
    148 
    149     @Option(name = "reboot-interval", description =
    150             "Interval between each reboot in min.")
    151     private int mRebootIntervalMin = 30;
    152 
    153     @Option(name = "screenshot-on-failure", description =
    154             "take a screenshot on every test failure.")
    155     private boolean mScreenshotOnFailures = false;
    156 
    157     @Option(name = "logcat-on-failure", description =
    158             "take a logcat snapshot on every test failure. Unlike --bugreport, this can capture" +
    159             "logs even if connection with device has been lost, as well as being much more " +
    160             "performant.")
    161     private boolean mLogcatOnFailures = false;
    162 
    163     @Option(name = "logcat-on-failure-size", description =
    164             "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " +
    165             "Should be an amount that can comfortably fit in memory.")
    166     private int mMaxLogcatBytes = 500 * 1024; // 500K
    167 
    168     private long mPrevRebootTime; // last reboot time
    169 
    170     /** data structure for a {@link IRemoteTest} and its known tests */
    171     class TestPackage {
    172         private final IRemoteTest mTestForPackage;
    173         private final Collection<TestIdentifier> mKnownTests;
    174         private final ITestPackageDef mPackageDef;
    175 
    176         TestPackage(ITestPackageDef packageDef, IRemoteTest testForPackage,
    177                 Collection<TestIdentifier> knownTests) {
    178             mPackageDef = packageDef;
    179             mTestForPackage = testForPackage;
    180             mKnownTests = knownTests;
    181         }
    182 
    183         IRemoteTest getTestForPackage() {
    184             return mTestForPackage;
    185         }
    186 
    187         Collection<TestIdentifier> getKnownTests() {
    188             return mKnownTests;
    189         }
    190 
    191         ITestPackageDef getPackageDef() {
    192             return mPackageDef;
    193         }
    194 
    195         /**
    196          * Return the test run name that should be used for the TestPackage
    197          */
    198         String getTestRunName() {
    199             return mPackageDef.getUri();
    200         }
    201     }
    202 
    203     /**
    204      * A {@link ResultForwarder} that will forward a bugreport on each failed test.
    205      */
    206     private static class FailedTestBugreportGenerator extends ResultForwarder {
    207         private ITestDevice mDevice;
    208 
    209         public FailedTestBugreportGenerator(ITestInvocationListener listener, ITestDevice device) {
    210             super(listener);
    211             mDevice = device;
    212         }
    213 
    214         @Override
    215         public void testFailed(TestFailure status, TestIdentifier test, String trace) {
    216             super.testFailed(status, test, trace);
    217             InputStreamSource bugSource = mDevice.getBugreport();
    218             super.testLog(String.format("bug-%s_%s", test.getClassName(), test.getTestName()),
    219                     LogDataType.TEXT, bugSource);
    220             bugSource.cancel();
    221         }
    222     }
    223 
    224     /**
    225      * A {@link ResultForwarder} that will forward a logcat snapshot on each failed test.
    226      */
    227     private static class FailedTestLogcatGenerator extends ResultForwarder {
    228         private ITestDevice mDevice;
    229         private int mNumLogcatBytes;
    230 
    231         public FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device,
    232                 int maxLogcatBytes) {
    233             super(listener);
    234             mDevice = device;
    235             mNumLogcatBytes = maxLogcatBytes;
    236         }
    237 
    238         @Override
    239         public void testFailed(TestFailure status, TestIdentifier test, String trace) {
    240             super.testFailed(status, test, trace);
    241             // sleep a small amount of time to ensure test failure stack trace makes it into logcat
    242             // capture
    243             RunUtil.getDefault().sleep(10);
    244             InputStreamSource logSource = mDevice.getLogcat(mNumLogcatBytes);
    245             super.testLog(String.format("logcat-%s_%s", test.getClassName(), test.getTestName()),
    246                     LogDataType.TEXT, logSource);
    247             logSource.cancel();
    248         }
    249     }
    250 
    251     /**
    252      * A {@link ResultForwarder} that will forward a screenshot on test failures.
    253      */
    254     private static class FailedTestScreenshotGenerator extends ResultForwarder {
    255         private ITestDevice mDevice;
    256 
    257         public FailedTestScreenshotGenerator(ITestInvocationListener listener,
    258                 ITestDevice device) {
    259             super(listener);
    260             mDevice = device;
    261         }
    262 
    263         @Override
    264         public void testFailed(TestFailure status, TestIdentifier test, String trace) {
    265             super.testFailed(status, test, trace);
    266 
    267             try {
    268                 InputStreamSource screenSource = mDevice.getScreenshot();
    269                 super.testLog(String.format("screenshot-%s_%s", test.getClassName(),
    270                         test.getTestName()), LogDataType.PNG, screenSource);
    271                 screenSource.cancel();
    272             } catch (DeviceNotAvailableException e) {
    273                 // TODO: rethrow this somehow
    274                 CLog.e("Device %s became unavailable while capturing screenshot, %s",
    275                         mDevice.getSerialNumber(), e.toString());
    276             }
    277         }
    278     }
    279 
    280     /** list of remaining tests to execute */
    281     private List<TestPackage> mRemainingTestPkgs = null;
    282 
    283     private CtsBuildHelper mCtsBuild = null;
    284     private IBuildInfo mBuildInfo = null;
    285 
    286     /**
    287      * {@inheritDoc}
    288      */
    289     @Override
    290     public ITestDevice getDevice() {
    291         return mDevice;
    292     }
    293 
    294     /**
    295      * {@inheritDoc}
    296      */
    297     @Override
    298     public void setDevice(ITestDevice device) {
    299         mDevice = device;
    300     }
    301 
    302     /**
    303      * Set the plan name to run.
    304      * <p/>
    305      * Exposed for unit testing
    306      */
    307     void setPlanName(String planName) {
    308         mPlanName = planName;
    309     }
    310 
    311     /**
    312      * Set the skip collect device info flag.
    313      * <p/>
    314      * Exposed for unit testing
    315      */
    316     void setSkipDeviceInfo(boolean skipDeviceInfo) {
    317         mSkipDeviceInfo = skipDeviceInfo;
    318     }
    319 
    320     /**
    321      * Adds a package name to the list of test packages to run.
    322      * <p/>
    323      * Exposed for unit testing
    324      */
    325     void addPackageName(String packageName) {
    326         mPackageNames.add(packageName);
    327     }
    328 
    329     /**
    330      * Adds a package name to the list of test packages to exclude.
    331      * <p/>
    332      * Exposed for unit testing
    333      */
    334     void addExcludedPackageName(String packageName) {
    335         mExcludedPackageNames.add(packageName);
    336     }
    337 
    338     /**
    339      * Set the test class name to run.
    340      * <p/>
    341      * Exposed for unit testing
    342      */
    343     void setClassName(String className) {
    344         mClassName = className;
    345     }
    346 
    347     /**
    348      * Set the test method name to run.
    349      * <p/>
    350      * Exposed for unit testing
    351      */
    352     void setMethodName(String methodName) {
    353         mMethodName = methodName;
    354     }
    355 
    356     /**
    357      * Sets the test session id to continue.
    358      * <p/>
    359      * Exposed for unit testing
    360      */
    361      void setContinueSessionId(int sessionId) {
    362         mContinueSessionId = sessionId;
    363     }
    364 
    365     /**
    366      * {@inheritDoc}
    367      */
    368     @Override
    369     public boolean isResumable() {
    370         return mResume;
    371     }
    372 
    373     /**
    374      * {@inheritDoc}
    375      */
    376     @Override
    377     public void setBuild(IBuildInfo build) {
    378         mCtsBuild = CtsBuildHelper.createBuildHelper(build);
    379         mBuildInfo = build;
    380     }
    381 
    382     /**
    383      * Set the CTS build container.
    384      * <p/>
    385      * Exposed so unit tests can mock the provided build.
    386      *
    387      * @param buildHelper
    388      */
    389     void setBuildHelper(CtsBuildHelper buildHelper) {
    390         mCtsBuild = buildHelper;
    391     }
    392 
    393     /**
    394      * {@inheritDoc}
    395      */
    396     @Override
    397     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    398         if (getDevice() == null) {
    399             throw new IllegalArgumentException("missing device");
    400         }
    401 
    402         if (mRemainingTestPkgs == null) {
    403             checkFields();
    404             mRemainingTestPkgs = buildTestsToRun();
    405         }
    406         if (mBugreport) {
    407             FailedTestBugreportGenerator bugListener = new FailedTestBugreportGenerator(listener,
    408                     getDevice());
    409             listener = bugListener;
    410         }
    411         if (mScreenshotOnFailures) {
    412             FailedTestScreenshotGenerator screenListener = new FailedTestScreenshotGenerator(
    413                     listener, getDevice());
    414             listener = screenListener;
    415         }
    416         if (mLogcatOnFailures) {
    417             FailedTestLogcatGenerator logcatListener = new FailedTestLogcatGenerator(
    418                     listener, getDevice(), mMaxLogcatBytes);
    419             listener = logcatListener;
    420         }
    421 
    422         // collect and install the prerequisiteApks first, to save time when multiple test
    423         // packages are using the same prerequisite apk (I'm looking at you, CtsTestStubs!)
    424         Collection<String> prerequisiteApks = getPrerequisiteApks(mRemainingTestPkgs);
    425         Collection<String> uninstallPackages = getPrerequisitePackageNames(mRemainingTestPkgs);
    426         ResultFilter filter = new ResultFilter(listener, mRemainingTestPkgs);
    427 
    428         try {
    429             installPrerequisiteApks(prerequisiteApks);
    430 
    431             // always collect the device info, even for resumed runs, since test will likely be
    432             // running on a different device
    433             collectDeviceInfo(getDevice(), mCtsBuild, listener);
    434             if (mRemainingTestPkgs.size() > 1 && !mDisableReboot) {
    435                 Log.i(LOG_TAG, "Initial reboot for multiple packages");
    436                 rebootDevice();
    437             }
    438             mPrevRebootTime = System.currentTimeMillis();
    439 
    440             while (!mRemainingTestPkgs.isEmpty()) {
    441                 TestPackage knownTests = mRemainingTestPkgs.get(0);
    442 
    443                 IRemoteTest test = knownTests.getTestForPackage();
    444                 if (test instanceof IDeviceTest) {
    445                     ((IDeviceTest)test).setDevice(getDevice());
    446                 }
    447                 if (test instanceof IBuildReceiver) {
    448                     ((IBuildReceiver)test).setBuild(mBuildInfo);
    449                 }
    450 
    451                 forwardPackageDetails(knownTests.getPackageDef(), listener);
    452                 test.run(filter);
    453                 mRemainingTestPkgs.remove(0);
    454                 if (mRemainingTestPkgs.size() > 0) {
    455                     rebootIfNecessary(knownTests, mRemainingTestPkgs.get(0));
    456                     // remove artifacts like status bar from the previous test.
    457                     // But this cannot dismiss dialog popped-up.
    458                     changeToHomeScreen();
    459                 }
    460             }
    461 
    462             if (mScreenshot) {
    463                 InputStreamSource screenshotSource = getDevice().getScreenshot();
    464                 try {
    465                     listener.testLog("screenshot", LogDataType.PNG, screenshotSource);
    466                 } finally {
    467                     screenshotSource.cancel();
    468                 }
    469             }
    470 
    471             uninstallPrequisiteApks(uninstallPackages);
    472 
    473         } finally {
    474             filter.reportUnexecutedTests();
    475         }
    476     }
    477 
    478     private void rebootIfNecessary(TestPackage testFinished, TestPackage testToRun)
    479             throws DeviceNotAvailableException {
    480         // If there comes spurious failure like INJECT_EVENTS for a package,
    481         // reboot it before running it.
    482         // Also reboot after package which is know to leave pop-up behind
    483         final List<String> rebootAfterList = Arrays.asList(
    484                 "CtsMediaTestCases",
    485                 "CtsAccessibilityTestCases");
    486         final List<String> rebootBeforeList = Arrays.asList(
    487                 "CtsAnimationTestCases",
    488                 "CtsGraphicsTestCases",
    489                 "CtsViewTestCases",
    490                 "CtsWidgetTestCases" );
    491         long intervalInMSec = mRebootIntervalMin * 60 * 1000;
    492         if (mDevice.getSerialNumber().startsWith("emulator-")) {
    493             return;
    494         }
    495         if (!mDisableReboot) {
    496             long currentTime = System.currentTimeMillis();
    497             if (((currentTime - mPrevRebootTime) > intervalInMSec) ||
    498                     rebootAfterList.contains(testFinished.getPackageDef().getName()) ||
    499                     rebootBeforeList.contains(testToRun.getPackageDef().getName()) ) {
    500                 Log.i(LOG_TAG,
    501                         String.format("Rebooting after running package %s, before package %s",
    502                                 testFinished.getPackageDef().getName(),
    503                                 testToRun.getPackageDef().getName()));
    504                 rebootDevice();
    505                 mPrevRebootTime = System.currentTimeMillis();
    506             }
    507         }
    508     }
    509 
    510     private void rebootDevice() throws DeviceNotAvailableException {
    511         final int TIMEOUT_MS = 10 * 60 * 1000;
    512         TestDeviceOptions options = mDevice.getOptions();
    513         // store default value and increase time-out for reboot
    514         int rebootTimeout = options.getRebootTimeout();
    515         long onlineTimeout = options.getOnlineTimeout();
    516         options.setRebootTimeout(TIMEOUT_MS);
    517         options.setOnlineTimeout(TIMEOUT_MS);
    518         mDevice.setOptions(options);
    519 
    520         mDevice.reboot();
    521 
    522         // restore default values
    523         options.setRebootTimeout(rebootTimeout);
    524         options.setOnlineTimeout(onlineTimeout);
    525         mDevice.setOptions(options);
    526         Log.i(LOG_TAG, "Rebooting done");
    527         try {
    528             Thread.sleep(mRebootWaitTimeMSec);
    529         } catch (InterruptedException e) {
    530             Log.i(LOG_TAG, "Boot wait interrupted");
    531         }
    532     }
    533 
    534     private void changeToHomeScreen() throws DeviceNotAvailableException {
    535         final String homeCmd = "input keyevent 3";
    536 
    537         mDevice.executeShellCommand(homeCmd);
    538         try {
    539             Thread.sleep(1000);
    540         } catch (InterruptedException e) {
    541             //ignore
    542         }
    543     }
    544     /**
    545      * Build the list of test packages to run
    546      */
    547     private List<TestPackage> buildTestsToRun() {
    548         List<TestPackage> testPkgList = new LinkedList<TestPackage>();
    549         try {
    550             ITestPackageRepo testRepo = createTestCaseRepo();
    551             Collection<ITestPackageDef> testPkgDefs = getTestPackagesToRun(testRepo);
    552 
    553             for (ITestPackageDef testPkgDef : testPkgDefs) {
    554                 addTestPackage(testPkgList, testPkgDef);
    555             }
    556             if (testPkgList.isEmpty()) {
    557                 Log.logAndDisplay(LogLevel.WARN, LOG_TAG, "No tests to run");
    558             }
    559         } catch (FileNotFoundException e) {
    560             throw new IllegalArgumentException("failed to find CTS plan file", e);
    561         } catch (ParseException e) {
    562             throw new IllegalArgumentException("failed to parse CTS plan file", e);
    563         } catch (ConfigurationException e) {
    564             throw new IllegalArgumentException("failed to process arguments", e);
    565         }
    566         return testPkgList;
    567     }
    568 
    569     /**
    570      * Adds a test package to the list of packages to test
    571      *
    572      * @param testList
    573      * @param testPkgDef
    574      */
    575     private void addTestPackage(List<TestPackage> testList, ITestPackageDef testPkgDef) {
    576         IRemoteTest testForPackage = testPkgDef.createTest(mCtsBuild.getTestCasesDir());
    577         if (testForPackage != null) {
    578             Collection<TestIdentifier> knownTests = testPkgDef.getTests();
    579             testList.add(new TestPackage(testPkgDef, testForPackage, knownTests));
    580         }
    581     }
    582 
    583     /**
    584      * Return the list of test package defs to run
    585      *
    586      * @return the list of test package defs to run
    587      * @throws ParseException
    588      * @throws FileNotFoundException
    589      * @throws ConfigurationException
    590      */
    591     private Collection<ITestPackageDef> getTestPackagesToRun(ITestPackageRepo testRepo)
    592             throws ParseException, FileNotFoundException, ConfigurationException {
    593         // use LinkedHashSet to have predictable iteration order
    594         Set<ITestPackageDef> testPkgDefs = new LinkedHashSet<ITestPackageDef>();
    595         if (mPlanName != null) {
    596             Log.i(LOG_TAG, String.format("Executing CTS test plan %s", mPlanName));
    597             File ctsPlanFile = mCtsBuild.getTestPlanFile(mPlanName);
    598             ITestPlan plan = createPlan(mPlanName);
    599             plan.parse(createXmlStream(ctsPlanFile));
    600             for (String uri : plan.getTestUris()) {
    601                 if (!mExcludedPackageNames.contains(uri)) {
    602                     ITestPackageDef testPackage = testRepo.getTestPackage(uri);
    603                     testPackage.setExcludedTestFilter(plan.getExcludedTestFilter(uri));
    604                     testPkgDefs.add(testPackage);
    605                 }
    606             }
    607         } else if (mPackageNames.size() > 0){
    608             Log.i(LOG_TAG, String.format("Executing CTS test packages %s", mPackageNames));
    609             for (String uri : mPackageNames) {
    610                 ITestPackageDef testPackage = testRepo.getTestPackage(uri);
    611                 if (testPackage != null) {
    612                     testPkgDefs.add(testPackage);
    613                 } else {
    614                     throw new IllegalArgumentException(String.format(
    615                             "Could not find test package %s. " +
    616                             "Use 'list packages' to see available packages." , uri));
    617                 }
    618             }
    619         } else if (mClassName != null) {
    620             Log.i(LOG_TAG, String.format("Executing CTS test class %s", mClassName));
    621             // try to find package to run from class name
    622             String packageUri = testRepo.findPackageForTest(mClassName);
    623             if (packageUri != null) {
    624                 ITestPackageDef testPackageDef = testRepo.getTestPackage(packageUri);
    625                 testPackageDef.setClassName(mClassName, mMethodName);
    626                 testPkgDefs.add(testPackageDef);
    627             } else {
    628                 Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
    629                         "Could not find package for test class %s", mClassName));
    630             }
    631         } else if (mContinueSessionId != null) {
    632             // create an in-memory derived plan that contains the notExecuted tests from previous
    633             // session
    634             // use timestamp as plan name so it will hopefully be unique
    635             String uniquePlanName = Long.toString(System.currentTimeMillis());
    636             PlanCreator planCreator = new PlanCreator(uniquePlanName, mContinueSessionId,
    637                     CtsTestStatus.NOT_EXECUTED);
    638             ITestPlan plan = createPlan(planCreator);
    639             for (String uri : plan.getTestUris()) {
    640                 if (!mExcludedPackageNames.contains(uri)) {
    641                     ITestPackageDef testPackage = testRepo.getTestPackage(uri);
    642                     testPackage.setExcludedTestFilter(plan.getExcludedTestFilter(uri));
    643                     testPkgDefs.add(testPackage);
    644                 }
    645             }
    646         } else {
    647             // should never get here - was checkFields() not called?
    648             throw new IllegalStateException("nothing to run?");
    649         }
    650         return testPkgDefs;
    651     }
    652 
    653     /**
    654      * Return the list of unique prerequisite Android package names
    655      * @param testPackages
    656      */
    657     private Collection<String> getPrerequisitePackageNames(List<TestPackage> testPackages) {
    658         Set<String> pkgNames = new HashSet<String>();
    659         for (TestPackage testPkg : testPackages) {
    660             String pkgName = testPkg.mPackageDef.getTargetPackageName();
    661             if (pkgName != null) {
    662                 pkgNames.add(pkgName);
    663             }
    664         }
    665         return pkgNames;
    666     }
    667 
    668     /**
    669      * Return the list of unique prerequisite apks to install
    670      * @param testPackages
    671      */
    672     private Collection<String> getPrerequisiteApks(List<TestPackage> testPackages) {
    673         Set<String> apkNames = new HashSet<String>();
    674         for (TestPackage testPkg : testPackages) {
    675             String apkName = testPkg.mPackageDef.getTargetApkName();
    676             if (apkName != null) {
    677                 apkNames.add(apkName);
    678             }
    679         }
    680         return apkNames;
    681     }
    682 
    683     /**
    684      * Install the collection of test apk file names
    685      *
    686      * @param prerequisiteApks
    687      * @throws DeviceNotAvailableException
    688      */
    689     private void installPrerequisiteApks(Collection<String> prerequisiteApks)
    690             throws DeviceNotAvailableException {
    691         for (String apkName : prerequisiteApks) {
    692             try {
    693                 File apkFile = mCtsBuild.getTestApp(apkName);
    694                 String errorCode = getDevice().installPackage(apkFile, true);
    695                 if (errorCode != null) {
    696                     CLog.e("Failed to install %s. Reason: %s", apkName, errorCode);
    697                 }
    698             } catch (FileNotFoundException e) {
    699                 CLog.e("Could not find test apk %s", apkName);
    700             }
    701         }
    702     }
    703 
    704     /**
    705      * Uninstalls the collection of android package names from device.
    706      *
    707      * @param uninstallPackages
    708      */
    709     private void uninstallPrequisiteApks(Collection<String> uninstallPackages)
    710             throws DeviceNotAvailableException {
    711         for (String pkgName : uninstallPackages) {
    712             getDevice().uninstallPackage(pkgName);
    713         }
    714     }
    715 
    716     /**
    717      * {@inheritDoc}
    718      */
    719     @Override
    720     public Collection<IRemoteTest> split() {
    721         if (mShards <= 1) {
    722             return null;
    723         }
    724         checkFields();
    725         List<TestPackage> allTests = buildTestsToRun();
    726 
    727         if (allTests.size() <= 1) {
    728             Log.w(LOG_TAG, "no tests to shard!");
    729             return null;
    730         }
    731 
    732         // treat shardQueue as a circular queue, to sequentially distribute tests among shards
    733         Queue<IRemoteTest> shardQueue = new LinkedList<IRemoteTest>();
    734         // don't create more shards than the number of tests we have!
    735         for (int i = 0; i < mShards && i < allTests.size(); i++) {
    736             CtsTest shard = new CtsTest();
    737             shard.mRemainingTestPkgs = new LinkedList<TestPackage>();
    738             shardQueue.add(shard);
    739         }
    740         while (!allTests.isEmpty()) {
    741             TestPackage testPair = allTests.remove(0);
    742             CtsTest shard = (CtsTest)shardQueue.poll();
    743             shard.mRemainingTestPkgs.add(testPair);
    744             shardQueue.add(shard);
    745         }
    746         return shardQueue;
    747     }
    748 
    749     /**
    750      * Runs the device info collector instrumentation on device, and forwards it to test listeners
    751      * as run metrics.
    752      * <p/>
    753      * Exposed so unit tests can mock.
    754      *
    755      * @throws DeviceNotAvailableException
    756      */
    757     void collectDeviceInfo(ITestDevice device, CtsBuildHelper ctsBuild,
    758             ITestInvocationListener listener) throws DeviceNotAvailableException {
    759         if (!mSkipDeviceInfo) {
    760             DeviceInfoCollector.collectDeviceInfo(device, ctsBuild.getTestCasesDir(), listener);
    761         }
    762     }
    763 
    764     /**
    765      * Factory method for creating a {@link ITestPackageRepo}.
    766      * <p/>
    767      * Exposed for unit testing
    768      */
    769     ITestPackageRepo createTestCaseRepo() {
    770         return new TestPackageRepo(mCtsBuild.getTestCasesDir(), mIncludeKnownFailures);
    771     }
    772 
    773     /**
    774      * Factory method for creating a {@link TestPlan}.
    775      * <p/>
    776      * Exposed for unit testing
    777      */
    778     ITestPlan createPlan(String planName) {
    779         return new TestPlan(planName);
    780     }
    781 
    782     /**
    783      * Factory method for creating a {@link TestPlan} from a {@link PlanCreator}.
    784      * <p/>
    785      * Exposed for unit testing
    786      * @throws ConfigurationException
    787      */
    788     ITestPlan createPlan(PlanCreator planCreator) throws ConfigurationException {
    789         return planCreator.createDerivedPlan(mCtsBuild);
    790     }
    791 
    792     /**
    793      * Factory method for creating a {@link InputStream} from a plan xml file.
    794      * <p/>
    795      * Exposed for unit testing
    796      */
    797     InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
    798         return new BufferedInputStream(new FileInputStream(xmlFile));
    799     }
    800 
    801     private void checkFields() {
    802         // for simplicity of command line usage, make --plan, --package, and --class mutually
    803         // exclusive
    804         boolean mutualExclusiveArgs = xor(mPlanName != null, mPackageNames.size() > 0,
    805                 mClassName != null, mContinueSessionId != null);
    806 
    807         if (!mutualExclusiveArgs) {
    808             throw new IllegalArgumentException(String.format(
    809                     "Ambiguous or missing arguments. " +
    810                     "One and only one of --%s --%s(s), --%s or --%s to run can be specified",
    811                     PLAN_OPTION, PACKAGE_OPTION, CLASS_OPTION, CONTINUE_OPTION));
    812         }
    813         if (mMethodName != null && mClassName == null) {
    814             throw new IllegalArgumentException(String.format(
    815                     "Must specify --%s when --%s is used", CLASS_OPTION, METHOD_OPTION));
    816         }
    817         if (mCtsBuild == null) {
    818             throw new IllegalArgumentException("missing CTS build");
    819         }
    820     }
    821 
    822     /**
    823      * Helper method to perform exclusive or on list of boolean arguments
    824      *
    825      * @param args set of booleans on which to perform exclusive or
    826      * @return <code>true</code> if one and only one of <var>args</code> is <code>true</code>.
    827      *         Otherwise return <code>false</code>.
    828      */
    829     private boolean xor(boolean... args) {
    830         boolean currentVal = args[0];
    831         for (int i=1; i < args.length; i++) {
    832             if (currentVal && args[i]) {
    833                 return false;
    834             }
    835             currentVal |= args[i];
    836         }
    837         return currentVal;
    838     }
    839 
    840     /**
    841      * Forward the digest and package name to the listener as a metric
    842      *
    843      * @param listener
    844      */
    845     private void forwardPackageDetails(ITestPackageDef def, ITestInvocationListener listener) {
    846         Map<String, String> metrics = new HashMap<String, String>(2);
    847         metrics.put(PACKAGE_NAME_METRIC, def.getName());
    848         metrics.put(PACKAGE_DIGEST_METRIC, def.getDigest());
    849         listener.testRunStarted(def.getUri(), 0);
    850         listener.testRunEnded(0, metrics);
    851     }
    852 }
    853