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.compatibility.common.util.AbiUtils;
20 import com.android.compatibility.common.util.MonitoringUtils;
21 import com.android.cts.tradefed.build.CtsBuildHelper;
22 import com.android.cts.tradefed.device.DeviceInfoCollector;
23 import com.android.cts.tradefed.result.CtsTestStatus;
24 import com.android.cts.tradefed.result.PlanCreator;
25 import com.android.cts.tradefed.util.ReportLogUtil;
26 import com.android.ddmlib.Log;
27 import com.android.ddmlib.Log.LogLevel;
28 import com.android.ddmlib.testrunner.TestIdentifier;
29 import com.android.tradefed.build.IBuildInfo;
30 import com.android.tradefed.config.ConfigurationException;
31 import com.android.tradefed.config.Option;
32 import com.android.tradefed.config.Option.Importance;
33 import com.android.tradefed.config.OptionCopier;
34 import com.android.tradefed.device.DeviceNotAvailableException;
35 import com.android.tradefed.device.DeviceUnresponsiveException;
36 import com.android.tradefed.device.ITestDevice;
37 import com.android.tradefed.device.TestDeviceOptions;
38 import com.android.tradefed.log.LogUtil.CLog;
39 import com.android.tradefed.result.ITestInvocationListener;
40 import com.android.tradefed.result.InputStreamSource;
41 import com.android.tradefed.result.LogDataType;
42 import com.android.tradefed.result.ResultForwarder;
43 import com.android.tradefed.targetprep.BuildError;
44 import com.android.tradefed.targetprep.ITargetCleaner;
45 import com.android.tradefed.targetprep.ITargetPreparer;
46 import com.android.tradefed.targetprep.TargetSetupError;
47 import com.android.tradefed.testtype.IAbi;
48 import com.android.tradefed.testtype.IAbiReceiver;
49 import com.android.tradefed.testtype.IBuildReceiver;
50 import com.android.tradefed.testtype.IDeviceTest;
51 import com.android.tradefed.testtype.IRemoteTest;
52 import com.android.tradefed.testtype.IResumableTest;
53 import com.android.tradefed.testtype.IShardableTest;
54 import com.android.tradefed.testtype.InstrumentationTest;
55 import com.android.tradefed.util.AbiFormatter;
56 import com.android.tradefed.util.RunUtil;
57 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
58 
59 import junit.framework.Test;
60 
61 import java.io.BufferedInputStream;
62 import java.io.ByteArrayOutputStream;
63 import java.io.File;
64 import java.io.FileInputStream;
65 import java.io.FileNotFoundException;
66 import java.io.IOException;
67 import java.io.InputStream;
68 import java.io.PrintWriter;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.LinkedHashSet;
76 import java.util.LinkedList;
77 import java.util.List;
78 import java.util.ListIterator;
79 import java.util.Map;
80 import java.util.Set;
81 
82 
83 /**
84  * A {@link Test} for running CTS tests.
85  * <p/>
86  * Supports running all the tests contained in a CTS plan, or individual test packages.
87  */
88 public class CtsTest implements IDeviceTest, IResumableTest, IShardableTest, IBuildReceiver {
89     private static final String LOG_TAG = "CtsTest";
90 
91     public static final String PLAN_OPTION = "plan";
92     private static final String PACKAGE_OPTION = "package";
93     private static final String CLASS_OPTION = "class";
94     private static final String METHOD_OPTION = "method";
95     private static final String TEST_OPTION = "test";
96     public static final String CONTINUE_OPTION = "continue-session";
97     public static final String RUN_KNOWN_FAILURES_OPTION = "run-known-failures";
98     private static final String INCLUDE_FILTERS_OPTION = "include";
99     private static final String EXCLUDE_FILTERS_OPTION = "exclude";
100 
101     public static final String PACKAGE_NAME_METRIC = "packageName";
102     public static final String PACKAGE_ABI_METRIC = "packageAbi";
103     public static final String PACKAGE_DIGEST_METRIC = "packageDigest";
104 
105     @Option(name = PLAN_OPTION, description = "the test plan to run.",
106             importance = Importance.IF_UNSET)
107     private String mPlanName = null;
108 
109     @Option(name = PACKAGE_OPTION, shortName = 'p', description = "the test packages(s) to run.",
110             importance = Importance.IF_UNSET)
111     private Collection<String> mPackageNames = new ArrayList<String>();
112 
113     @Option(name = "exclude-package", description = "the test packages(s) to exclude from the run.")
114     private Collection<String> mExcludedPackageNames = new ArrayList<String>();
115 
116     @Option(name = CLASS_OPTION, shortName = 'c', description = "run a specific test class.",
117             importance = Importance.IF_UNSET)
118     private String mClassName = null;
119 
120     @Option(name = METHOD_OPTION, shortName = 'm',
121             description = "run a specific test method, from given --class.",
122             importance = Importance.IF_UNSET)
123     private String mMethodName = null;
124 
125     @Option(name = TEST_OPTION, shortName = 't', description = "run a specific test",
126             importance = Importance.IF_UNSET)
127     private String mTestName = null;
128 
129     @Option(name = CONTINUE_OPTION,
130             description = "continue a previous test session.",
131             importance = Importance.IF_UNSET)
132     private Integer mContinueSessionId = null;
133 
134     @Option(name = "skip-device-info", shortName = 'd', description =
135         "flag to control whether to collect info from device. Providing this flag will speed up " +
136         "test execution for short test runs but will result in required data being omitted from " +
137         "the test report.")
138     private boolean mSkipDeviceInfo = false;
139 
140     @Option(name = "resume", description =
141         "flag to attempt to automatically resume aborted test run on another connected device. ")
142     private boolean mResume = false;
143 
144     @Option(name = "shards", description =
145         "shard the tests to run into separately runnable chunks to execute on multiple devices " +
146         "concurrently.")
147     private int mShards = 1;
148 
149     @Option(name = "screenshot", description =
150         "flag for taking a screenshot of the device when test execution is complete.")
151     private boolean mScreenshot = false;
152 
153     @Option(name = "bugreport", shortName = 'b', description =
154         "take a bugreport after each failed test. " +
155         "Warning: can potentially use a lot of disk space.")
156     private boolean mBugreport = false;
157 
158     @Option(name = RUN_KNOWN_FAILURES_OPTION, shortName = 'k', description =
159         "run tests including known failures")
160     private boolean mIncludeKnownFailures;
161 
162     @Option(name = "disable-reboot", description =
163             "Do not reboot device after running some amount of tests. Default behavior is to reboot.")
164     private boolean mDisableReboot = false;
165 
166     @Option(name = "reboot-wait-time", description =
167             "Additional wait time in ms after boot complete.")
168     private int mRebootWaitTimeMSec = 2 * 60 * 1000;
169 
170     @Option(name = "reboot-interval", description =
171             "Interval between each reboot in min.")
172     private int mRebootIntervalMin = 30;
173 
174     @Option(name = "screenshot-on-failure", description =
175             "take a screenshot on every test failure.")
176     private boolean mScreenshotOnFailures = false;
177 
178     @Option(name = "logcat-on-failure", description =
179             "take a logcat snapshot on every test failure. Unlike --bugreport, this can capture" +
180             "logs even if connection with device has been lost, as well as being much more " +
181             "performant.")
182     private boolean mLogcatOnFailures = false;
183 
184     @Option(name = AbiFormatter.FORCE_ABI_STRING,
185             description = AbiFormatter.FORCE_ABI_DESCRIPTION,
186             importance = Importance.IF_UNSET)
187     private String mForceAbi = null;
188 
189     @Option(name = "logcat-on-failure-size", description =
190             "The max number of logcat data in bytes to capture when --logcat-on-failure is on. " +
191             "Should be an amount that can comfortably fit in memory.")
192     private int mMaxLogcatBytes = 500 * 1024; // 500K
193 
194     @Option(name = "collect-deqp-logs", description =
195             "Collect dEQP logs from the device.")
196     private boolean mCollectDeqpLogs = false;
197 
198     @Option(name = INCLUDE_FILTERS_OPTION, description = "Positive filters to pass to tests.")
199     private List<String> mPositiveFilters = new ArrayList<> ();
200 
201     @Option(name = EXCLUDE_FILTERS_OPTION, description = "Negative filters to pass to tests.")
202     private List<String> mNegativeFilters = new ArrayList<> ();
203 
204     @Option(name = "min-pre-reboot-package-count", description =
205             "The minimum number of packages to require a pre test reboot")
206     private int mMinPreRebootPackageCount = 2;
207 
208     @Option(name = "skip-connectivity-check",
209             description = "Don't verify device connectivity between module execution.")
210     private boolean mSkipConnectivityCheck = false;
211 
212     private final int mShardAssignment;
213     private final int mTotalShards;
214     private ITestDevice mDevice = null;
215     private CtsBuildHelper mCtsBuild = null;
216     private IBuildInfo mBuildInfo = null;
217     // last reboot time
218     private long mPrevRebootTime;
219     // The list of packages to run. populated in {@code setupTestPackageList}
220     // This is a member variable so that run can be called more than once
221     // and the test run is resumed.
222     private List<TestPackage> mTestPackageList = new ArrayList<>();
223     // The index in the pacakge list of the last test to complete
224     private int mLastTestPackageIndex = 0;
225 
226     /** data structure for a {@link IRemoteTest} and its known tests */
227     static class TestPackage {
228         private final IRemoteTest mTestForPackage;
229         private final ITestPackageDef mPackageDef;
230         private final Collection<TestIdentifier> mKnownTests;
231 
TestPackage(ITestPackageDef packageDef, IRemoteTest testForPackage)232         TestPackage(ITestPackageDef packageDef, IRemoteTest testForPackage) {
233             mPackageDef = packageDef;
234             mTestForPackage = testForPackage;
235             mKnownTests = packageDef.getTests();
236         }
237 
getTestForPackage()238         IRemoteTest getTestForPackage() {
239             return mTestForPackage;
240         }
241 
getKnownTests()242         Collection<TestIdentifier> getKnownTests() {
243             return mKnownTests;
244         }
245 
getPackageDef()246         ITestPackageDef getPackageDef() {
247             return mPackageDef;
248         }
249 
250         /**
251          * @return the test run name that should be used for the TestPackage.
252          */
getTestRunName()253         String getTestRunName() {
254             return mPackageDef.getId();
255         }
256 
257         /**
258          * @return the ABI on which the test will run.
259          */
getAbi()260         IAbi getAbi() {
261             return mPackageDef.getAbi();
262         }
263     }
264 
265     /**
266      * A {@link ResultForwarder} that will forward a bugreport on each failed test.
267      */
268     private static class FailedTestBugreportGenerator extends ResultForwarder {
269         private ITestDevice mDevice;
270 
FailedTestBugreportGenerator(ITestInvocationListener listener, ITestDevice device)271         public FailedTestBugreportGenerator(ITestInvocationListener listener, ITestDevice device) {
272             super(listener);
273             mDevice = device;
274         }
275 
276         @Override
testFailed(TestIdentifier test, String trace)277         public void testFailed(TestIdentifier test, String trace) {
278             super.testFailed(test, trace);
279             InputStreamSource bugSource = mDevice.getBugreport();
280             super.testLog(String.format("bug-%s_%s", test.getClassName(), test.getTestName()),
281                     LogDataType.TEXT, bugSource);
282             bugSource.cancel();
283         }
284     }
285 
286     /**
287      * A {@link ResultForwarder} that will forward a logcat snapshot on each failed test.
288      */
289     private static class FailedTestLogcatGenerator extends ResultForwarder {
290         private ITestDevice mDevice;
291         private int mNumLogcatBytes;
292 
FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device, int maxLogcatBytes)293         public FailedTestLogcatGenerator(ITestInvocationListener listener, ITestDevice device,
294                 int maxLogcatBytes) {
295             super(listener);
296             mDevice = device;
297             mNumLogcatBytes = maxLogcatBytes;
298         }
299 
300         @Override
testFailed(TestIdentifier test, String trace)301         public void testFailed(TestIdentifier test, String trace) {
302             super.testFailed(test, trace);
303             // sleep 2s to ensure test failure stack trace makes it into logcat capture
304             RunUtil.getDefault().sleep(2 * 1000);
305             InputStreamSource logSource = mDevice.getLogcat(mNumLogcatBytes);
306             super.testLog(String.format("logcat-%s_%s", test.getClassName(), test.getTestName()),
307                     LogDataType.TEXT, logSource);
308             logSource.cancel();
309         }
310     }
311 
312     /**
313      * A {@link ResultForwarder} that will forward a screenshot on test failures.
314      */
315     private static class FailedTestScreenshotGenerator extends ResultForwarder {
316         private ITestDevice mDevice;
317 
FailedTestScreenshotGenerator(ITestInvocationListener listener, ITestDevice device)318         public FailedTestScreenshotGenerator(ITestInvocationListener listener,
319                 ITestDevice device) {
320             super(listener);
321             mDevice = device;
322         }
323 
324         @Override
testFailed(TestIdentifier test, String trace)325         public void testFailed(TestIdentifier test, String trace) {
326             super.testFailed(test, trace);
327 
328             try {
329                 InputStreamSource screenSource = mDevice.getScreenshot();
330                 super.testLog(String.format("screenshot-%s_%s", test.getClassName(),
331                         test.getTestName()), LogDataType.PNG, screenSource);
332                 screenSource.cancel();
333             } catch (DeviceNotAvailableException e) {
334                 // TODO: rethrow this somehow
335                 CLog.e("Device %s became unavailable while capturing screenshot, %s",
336                         mDevice.getSerialNumber(), e.toString());
337             }
338         }
339     }
340 
341     /**
342      * Create a new {@link CtsTest} that will run the default list of {@link TestPackage}s.
343      */
CtsTest()344     public CtsTest() {
345         this(0 /*shardAssignment*/, 1 /*totalShards*/);
346     }
347 
348     /**
349      * Create a new {@link CtsTest} that will run the given {@link List} of {@link TestPackage}s.
350      */
CtsTest(int shardAssignment, int totalShards)351     public CtsTest(int shardAssignment, int totalShards) {
352         if (shardAssignment < 0) {
353             throw new IllegalArgumentException(
354                 "shardAssignment cannot be negative. found:" + shardAssignment);
355         }
356         if (totalShards < 1) {
357             throw new IllegalArgumentException(
358                 "shardAssignment must be at least 1. found:" + totalShards);
359         }
360         this.mShardAssignment = shardAssignment;
361         this.mTotalShards = totalShards;
362     }
363 
364     /**
365      * {@inheritDoc}
366      */
367     @Override
getDevice()368     public ITestDevice getDevice() {
369         return mDevice;
370     }
371 
372     /**
373      * {@inheritDoc}
374      */
375     @Override
setDevice(ITestDevice device)376     public void setDevice(ITestDevice device) {
377         mDevice = device;
378     }
379 
380     /**
381      * Set the plan name to run.
382      * <p/>
383      * Exposed for unit testing
384      */
setPlanName(String planName)385     void setPlanName(String planName) {
386         mPlanName = planName;
387     }
388 
389     /**
390      * Set the skip collect device info flag.
391      * <p/>
392      * Exposed for unit testing
393      */
setSkipDeviceInfo(boolean skipDeviceInfo)394     void setSkipDeviceInfo(boolean skipDeviceInfo) {
395         mSkipDeviceInfo = skipDeviceInfo;
396     }
397 
398     /**
399      * Adds a package name to the list of test packages to run.
400      * <p/>
401      * Exposed for unit testing
402      */
addPackageName(String packageName)403     void addPackageName(String packageName) {
404         mPackageNames.add(packageName);
405     }
406 
407     /**
408      * Adds a package name to the list of test packages to exclude.
409      * <p/>
410      * Exposed for unit testing
411      */
addExcludedPackageName(String packageName)412     void addExcludedPackageName(String packageName) {
413         mExcludedPackageNames.add(packageName);
414     }
415 
416     /**
417      * Set the test class name to run.
418      * <p/>
419      * Exposed for unit testing
420      */
setClassName(String className)421     void setClassName(String className) {
422         mClassName = className;
423     }
424 
425     /**
426      * Set the test method name to run.
427      * <p/>
428      * Exposed for unit testing
429      */
setMethodName(String methodName)430     void setMethodName(String methodName) {
431         mMethodName = methodName;
432     }
433 
434     /**
435      * Set the test name to run e.g. android.test.cts.SampleTest#testSample
436      * <p/>
437      * Exposed for unit testing
438      */
setTestName(String testName)439     void setTestName(String testName) {
440         mTestName = testName;
441     }
442 
443     /**
444      * Sets the test session id to continue.
445      * <p/>
446      * Exposed for unit testing
447      */
setContinueSessionId(int sessionId)448      void setContinueSessionId(int sessionId) {
449         mContinueSessionId = sessionId;
450     }
451 
452     /**
453      * {@inheritDoc}
454      */
455     @Override
isResumable()456     public boolean isResumable() {
457         return mResume;
458     }
459 
460     /**
461      * {@inheritDoc}
462      */
463     @Override
setBuild(IBuildInfo build)464     public void setBuild(IBuildInfo build) {
465         mCtsBuild = CtsBuildHelper.createBuildHelper(build);
466         mBuildInfo = build;
467     }
468 
469     /**
470      * Set the CTS build container.
471      * <p/>
472      * Exposed so unit tests can mock the provided build.
473      */
setBuildHelper(CtsBuildHelper buildHelper)474     void setBuildHelper(CtsBuildHelper buildHelper) {
475         mCtsBuild = buildHelper;
476     }
477 
478     /**
479      * {@inheritDoc}
480      */
481     @Override
run(ITestInvocationListener listener)482     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
483         if (getDevice() == null) {
484             throw new IllegalArgumentException("missing device");
485         }
486 
487         Set<String> abiSet = getAbis();
488         if (abiSet == null || abiSet.isEmpty()) {
489             throw new IllegalArgumentException("could not get device's ABIs");
490         }
491         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "ABIs: " + abiSet);
492 
493         checkFields();
494         setupTestPackageList(abiSet);
495         if (mBugreport) {
496             listener = new FailedTestBugreportGenerator(listener, getDevice());
497         }
498         if (mScreenshotOnFailures) {
499             listener = new FailedTestScreenshotGenerator(listener, getDevice());
500         }
501         if (mLogcatOnFailures) {
502             listener = new FailedTestLogcatGenerator(listener, getDevice(), mMaxLogcatBytes);
503         }
504 
505         // Setup the a map of Test id to ResultFilter
506         Map<String, ResultFilter> filterMap = new HashMap<>();
507         int totalTestCount = 0;
508         for (TestPackage testPackage : mTestPackageList) {
509             ResultFilter resultFilter = new ResultFilter(listener, testPackage);
510             totalTestCount += resultFilter.getKnownTestCount();
511             filterMap.put(testPackage.getPackageDef().getId(), resultFilter);
512         }
513 
514         // collect and install the prerequisiteApks first, to save time when multiple test
515         // packages are using the same prerequisite apk
516         Map<String, Set<String>> prerequisiteApks = getPrerequisiteApks(mTestPackageList, abiSet);
517         Collection<String> uninstallPackages = getPrerequisitePackageNames(mTestPackageList);
518 
519         try {
520             // always collect the device info, even for resumed runs, since test will likely be
521             // running on a different device
522             collectDeviceInfo(getDevice(), mCtsBuild, listener);
523             // prepare containers to hold test metric report logs.
524             prepareReportLogContainers(getDevice(), mBuildInfo);
525             preRebootIfNecessary(mTestPackageList);
526 
527             mPrevRebootTime = System.currentTimeMillis();
528             int remainingPackageCount = mTestPackageList.size();
529             Log.logAndDisplay(LogLevel.INFO, LOG_TAG,
530                 String.format("Start test run of %,d packages, containing %,d tests",
531                     remainingPackageCount, totalTestCount));
532             IAbi currentAbi = null;
533 
534             // check connectivity upfront
535             if (!mSkipConnectivityCheck) {
536                 MonitoringUtils.checkDeviceConnectivity(getDevice(), listener, "start");
537             }
538             for (int i = mLastTestPackageIndex; i < mTestPackageList.size(); i++) {
539                 TestPackage testPackage = mTestPackageList.get(i);
540 
541                 if (currentAbi == null ||
542                     !currentAbi.getName().equals(testPackage.getAbi().getName())) {
543                     currentAbi = testPackage.getAbi();
544                     installPrerequisiteApks(
545                         prerequisiteApks.get(currentAbi.getName()), currentAbi);
546                 }
547 
548                 IRemoteTest test = testPackage.getTestForPackage();
549                 if (test instanceof IBuildReceiver) {
550                     ((IBuildReceiver) test).setBuild(mBuildInfo);
551                 }
552                 if (test instanceof IDeviceTest) {
553                     ((IDeviceTest) test).setDevice(getDevice());
554                 }
555                 if (test instanceof DeqpTestRunner) {
556                     ((DeqpTestRunner)test).setCollectLogs(mCollectDeqpLogs);
557                 }
558                 if (test instanceof GeeTest) {
559                     if (!mPositiveFilters.isEmpty()) {
560                         String positivePatterns = join(mPositiveFilters, ":");
561                         ((GeeTest)test).setPositiveFilters(positivePatterns);
562                     }
563                     if (!mNegativeFilters.isEmpty()) {
564                         String negativePatterns = join(mNegativeFilters, ":");
565                         ((GeeTest)test).setPositiveFilters(negativePatterns);
566                     }
567                 }
568                 if (test instanceof InstrumentationTest) {
569                     if (!mPositiveFilters.isEmpty()) {
570                         String annotation = join(mPositiveFilters, ",");
571                         ((InstrumentationTest)test).addInstrumentationArg(
572                                 "annotation", annotation);
573                     }
574                     if (!mNegativeFilters.isEmpty()) {
575                         String notAnnotation = join(mNegativeFilters, ",");
576                         ((InstrumentationTest)test).addInstrumentationArg(
577                                 "notAnnotation", notAnnotation);
578                     }
579                 }
580 
581                 forwardPackageDetails(testPackage.getPackageDef(), listener);
582                 try {
583                     performPackagePrepareSetup(testPackage.getPackageDef());
584                     test.run(filterMap.get(testPackage.getPackageDef().getId()));
585                     performPackagePreparerTearDown(testPackage.getPackageDef());
586                 } catch (DeviceUnresponsiveException due) {
587                     // being able to catch a DeviceUnresponsiveException here implies that recovery
588                     // was successful, and test execution should proceed to next module
589                     ByteArrayOutputStream stack = new ByteArrayOutputStream();
590                     due.printStackTrace(new PrintWriter(stack, true));
591                     try {
592                         stack.close();
593                     } catch (IOException ioe) {
594                         // won't happen on BAOS
595                     }
596                     CLog.w("Ignored DeviceUnresponsiveException because recovery was successful, "
597                             + "proceeding with next test package. Stack trace: %s",
598                             stack.toString());
599                     CLog.w("This may be due to incorrect timeout setting on test package %s",
600                             testPackage.getPackageDef().getName());
601                 }
602                 if (!mSkipConnectivityCheck) {
603                     MonitoringUtils.checkDeviceConnectivity(getDevice(), listener,
604                             String.format("%s-%s", testPackage.getPackageDef().getName(),
605                                     testPackage.getPackageDef().getAbi().getName()));
606                 }
607                 if (i < mTestPackageList.size() - 1) {
608                     TestPackage nextPackage = mTestPackageList.get(i + 1);
609                     rebootIfNecessary(testPackage, nextPackage);
610                     changeToHomeScreen();
611                 }
612                 // Track of the last complete test package index for resume
613                 mLastTestPackageIndex = i;
614             }
615 
616             if (mScreenshot) {
617                 InputStreamSource screenshotSource = getDevice().getScreenshot();
618                 try {
619                     listener.testLog("screenshot", LogDataType.PNG, screenshotSource);
620                 } finally {
621                     screenshotSource.cancel();
622                 }
623             }
624 
625             uninstallPrequisiteApks(uninstallPackages);
626             // Collect test metric report logs.
627             collectReportLogs(getDevice(), mBuildInfo);
628         } catch (RuntimeException e) {
629             CLog.e(e);
630             throw e;
631         } catch (Error e) {
632             CLog.e(e);
633             throw e;
634         } finally {
635             for (ResultFilter filter : filterMap.values()) {
636                 filter.reportUnexecutedTests();
637             }
638         }
639     }
640 
641     /**
642      * Invokes {@link ITargetPreparer}s configured for the test package. {@link TargetSetupError}s
643      * thrown by any preparer will be rethrown as {@link RuntimeException} so that the entire test
644      * package will be skipped for execution. Note that preparers will be invoked in the same order
645      * as they are defined in the module test config.
646      * @param packageDef definition for the test package
647      * @throws DeviceNotAvailableException
648      */
performPackagePrepareSetup(ITestPackageDef packageDef)649     private void performPackagePrepareSetup(ITestPackageDef packageDef)
650             throws DeviceNotAvailableException {
651         List<ITargetPreparer> preparers = packageDef.getPackagePreparers();
652         if (preparers != null) {
653             for (ITargetPreparer preparer : preparers) {
654                 if (preparer instanceof IAbiReceiver) {
655                     ((IAbiReceiver) preparer).setAbi(packageDef.getAbi());
656                 }
657                 try {
658                     preparer.setUp(getDevice(), mBuildInfo);
659                 } catch (BuildError e) {
660                     // This should only happen for flashing new build
661                     CLog.e("Unexpected BuildError from preparer: %s",
662                         preparer.getClass().getCanonicalName());
663                 } catch (TargetSetupError e) {
664                     // log preparer class then rethrow & let caller handle
665                     CLog.e("TargetSetupError in preparer: %s",
666                         preparer.getClass().getCanonicalName());
667                     throw new RuntimeException(e);
668                 }
669             }
670         }
671     }
672 
673     /**
674      * Invokes clean up step for {@link ITargetCleaner}s configured for the test package. Note that
675      * the cleaners will be invoked in the reverse order as they are defined in module test config.
676      * @param packageDef definition for the test package
677      * @throws DeviceNotAvailableException
678      */
performPackagePreparerTearDown(ITestPackageDef packageDef)679     private void performPackagePreparerTearDown(ITestPackageDef packageDef)
680             throws DeviceNotAvailableException {
681         List<ITargetPreparer> preparers = packageDef.getPackagePreparers();
682         if (preparers != null) {
683             ListIterator<ITargetPreparer> itr = preparers.listIterator(preparers.size());
684             // do teardown in reverse order
685             while (itr.hasPrevious()) {
686                 ITargetPreparer preparer = itr.previous();
687                 if (preparer instanceof ITargetCleaner) {
688                     ((ITargetCleaner) preparer).tearDown(getDevice(), mBuildInfo, null);
689                 }
690             }
691         }
692     }
693 
694     /**
695      * Helper method to join strings. Exposed for unit tests
696      * @param input
697      * @param conjunction
698      * @return string with elements of the input list with interleaved conjunction.
699      */
join(List<String> input, String conjunction)700     protected static String join(List<String> input, String conjunction) {
701         StringBuilder sb = new StringBuilder();
702         boolean first = true;
703         for (String item : input) {
704             if (first) {
705                 first = false;
706             } else {
707                 sb.append(conjunction);
708             }
709             sb.append(item);
710         }
711         return sb.toString();
712     }
713 
714     /**
715      * @param allTestPackageDefList The package list to filter
716      * @param deviceAbiSet The ABIs supported by the device being tested
717      * @return A {@link List} of {@link ITestPackageDef}s that should be tested
718      */
filterByAbi( List<ITestPackageDef> allTestPackageDefList, Set<String> deviceAbiSet)719     private static List<ITestPackageDef> filterByAbi(
720             List<ITestPackageDef> allTestPackageDefList, Set<String> deviceAbiSet) {
721         List<ITestPackageDef> filteredTestPackageDefList = new LinkedList<>();
722         for (ITestPackageDef testPackageDef : allTestPackageDefList) {
723             if (deviceAbiSet.contains(testPackageDef.getAbi().getName())) {
724                 // We only need test packages that are not empty and of matching ABIs
725                 filteredTestPackageDefList.add(testPackageDef);
726             }
727         }
728         return filteredTestPackageDefList;
729     }
730 
731     /** Reboot then the device iff the list of packages exceeds the minimum */
preRebootIfNecessary(List<TestPackage> testPackageList)732     private void preRebootIfNecessary(List<TestPackage> testPackageList)
733             throws DeviceNotAvailableException {
734         if (mDisableReboot) {
735             return;
736         }
737 
738         Set<String> packageNameSet = new HashSet<>();
739         for (TestPackage testPackage : testPackageList) {
740             // Parse the package name
741             packageNameSet.add(AbiUtils.parseTestName(testPackage.getPackageDef().getId()));
742         }
743         if (packageNameSet.size() < mMinPreRebootPackageCount) {
744             // There is actually only one unique package name. No need to reboot.
745             return;
746         }
747 
748         // Reboot is needed
749         Log.logAndDisplay(LogLevel.INFO, LOG_TAG,
750             String.format("Pre-test reboot (%,d packages). Use --disable-reboot to skip",
751                 packageNameSet.size()));
752 
753         rebootDevice();
754     }
755 
rebootIfNecessary(TestPackage testFinished, TestPackage testToRun)756     private void rebootIfNecessary(TestPackage testFinished, TestPackage testToRun)
757             throws DeviceNotAvailableException {
758         // If there comes spurious failure like INJECT_EVENTS for a package,
759         // reboot it before running it.
760         // Also reboot after package which is know to leave pop-up behind
761         final List<String> rebootAfterList = Arrays.asList(
762                 "CtsMediaTestCases",
763                 "CtsAccessibilityTestCases",
764                 "CtsAccountManagerTestCases");
765         final List<String> rebootBeforeList = Arrays.asList(
766                 "CtsAnimationTestCases",
767                 "CtsGraphicsTestCases",
768                 "CtsViewTestCases",
769                 "CtsWidgetTestCases" );
770         long intervalInMSec = mRebootIntervalMin * 60 * 1000;
771         if (mDisableReboot || mDevice.getSerialNumber().startsWith("emulator-")) {
772             return;
773         }
774         long currentTime = System.currentTimeMillis();
775         if (((currentTime - mPrevRebootTime) > intervalInMSec) ||
776                 rebootAfterList.contains(testFinished.getPackageDef().getName()) ||
777                 rebootBeforeList.contains(testToRun.getPackageDef().getName()) ) {
778             Log.i(LOG_TAG,
779                     String.format("Rebooting after running package %s, before package %s",
780                             testFinished.getPackageDef().getName(),
781                             testToRun.getPackageDef().getName()));
782             rebootDevice();
783             mPrevRebootTime = System.currentTimeMillis();
784         }
785     }
786 
rebootDevice()787     private void rebootDevice() throws DeviceNotAvailableException {
788         final int TIMEOUT_MS = 10 * 60 * 1000;
789         TestDeviceOptions options = mDevice.getOptions();
790         // store default value and increase time-out for reboot
791         int rebootTimeout = options.getRebootTimeout();
792         long onlineTimeout = options.getOnlineTimeout();
793         options.setRebootTimeout(TIMEOUT_MS);
794         options.setOnlineTimeout(TIMEOUT_MS);
795         mDevice.setOptions(options);
796 
797         mDevice.reboot();
798 
799         // restore default values
800         options.setRebootTimeout(rebootTimeout);
801         options.setOnlineTimeout(onlineTimeout);
802         mDevice.setOptions(options);
803         Log.i(LOG_TAG, "Rebooting done");
804         try {
805             Thread.sleep(mRebootWaitTimeMSec);
806         } catch (InterruptedException e) {
807             Log.i(LOG_TAG, "Boot wait interrupted");
808         }
809     }
810 
811     /**
812      * Remove artifacts like status bar from the previous test.
813      * But this cannot dismiss dialog popped-up.
814      */
changeToHomeScreen()815     private void changeToHomeScreen() throws DeviceNotAvailableException {
816         final String homeCmd = "input keyevent 3";
817 
818         mDevice.executeShellCommand(homeCmd);
819         try {
820             Thread.sleep(1000);
821         } catch (InterruptedException e) {
822             //ignore
823         }
824     }
825 
826     /**
827      * Set {@code mTestPackageList} to the list of test packages to run filtered by ABI.
828      */
setupTestPackageList(Set<String> abis)829     private void setupTestPackageList(Set<String> abis) throws DeviceNotAvailableException {
830         if (!mTestPackageList.isEmpty()) {
831             Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Resume tests using existing package list");
832             return;
833         }
834         try {
835             // Collect ALL tests
836             ITestPackageRepo testRepo = createTestCaseRepo();
837             List<ITestPackageDef> testPkgDefs = new ArrayList<>(getAvailableTestPackages(testRepo));
838             testPkgDefs = filterByAbi(testPkgDefs, abis);
839             // Note: run() relies on the fact that the list is reliably sorted for sharding purposes
840             Collections.sort(testPkgDefs);
841             // Create test package list.
842             List<TestPackage> testPackageList = new ArrayList<>();
843             for (ITestPackageDef testPackageDef : testPkgDefs) {
844                 // Note: createTest filters the test list inside of testPackageDef by exclusion list
845                 IRemoteTest testForPackage = testPackageDef.createTest(mCtsBuild.getTestCasesDir());
846                 if (testPackageDef.getTests().size() > 0) {
847                     testPackageList.add(new TestPackage(testPackageDef, testForPackage));
848                 }
849             }
850 
851             // Filter by shard
852             int numTestPackages = testPackageList.size();
853             int totalShards = Math.min(mTotalShards, numTestPackages);
854 
855             List<TestPackage> shardTestPackageList = new ArrayList<>();
856             for (int i = mShardAssignment; i < numTestPackages; i += totalShards) {
857                 shardTestPackageList.add(testPackageList.get(i));
858             }
859             mTestPackageList.addAll(shardTestPackageList);
860         } catch (FileNotFoundException e) {
861             throw new IllegalArgumentException("failed to find test plan file", e);
862         } catch (ParseException e) {
863             throw new IllegalArgumentException("failed to parse test plan file", e);
864         } catch (ConfigurationException e) {
865             throw new IllegalArgumentException("failed to process arguments", e);
866         }
867     }
868 
869     /**
870      * Return the {@link Set} of {@link ITestPackageDef}s to run unfiltered by ABI
871      *
872      * @return the {@link Set} of {@link ITestPackageDef}s to run
873      * @throws ParseException
874      * @throws FileNotFoundException
875      * @throws ConfigurationException
876      */
getAvailableTestPackages(ITestPackageRepo testRepo)877     private Set<ITestPackageDef> getAvailableTestPackages(ITestPackageRepo testRepo)
878                 throws ParseException, FileNotFoundException, ConfigurationException {
879         // use LinkedHashSet to have predictable iteration order
880         Set<ITestPackageDef> testPkgDefs = new LinkedHashSet<>();
881         if (mPlanName != null) {
882             Log.i(LOG_TAG, String.format("Executing CTS test plan %s", mPlanName));
883             File ctsPlanFile = mCtsBuild.getTestPlanFile(mPlanName);
884             ITestPlan plan = createPlan(mPlanName);
885             plan.parse(createXmlStream(ctsPlanFile));
886 
887             for (String testId : plan.getTestIds()) {
888                 if (mExcludedPackageNames.contains(AbiUtils.parseTestName(testId))) {
889                     continue;
890                 }
891                 ITestPackageDef testPackageDef = testRepo.getTestPackage(testId);
892                 if (testPackageDef == null) {
893                     CLog.e("Could not find test id %s referenced in plan %s", testId, mPlanName);
894                     continue;
895                 }
896 
897                 testPackageDef.setTestFilter(plan.getTestFilter(testId));
898                 testPkgDefs.add(testPackageDef);
899             }
900         } else if (mPackageNames.size() > 0){
901             Log.i(LOG_TAG, String.format("Executing test packages %s", mPackageNames));
902 
903             Map<String, List<ITestPackageDef>> testPackageDefMap =
904                     testRepo.getTestPackageDefsByName();
905 
906             for (String name : mPackageNames) {
907                 if (!testPackageDefMap.containsKey(name)) {
908                     throw new IllegalArgumentException(String.format(
909                             "Could not find test package %s. " +
910                                     "Use 'list packages' to see available packages.", name));
911                 }
912                 testPkgDefs.addAll(testPackageDefMap.get(name));
913             }
914         } else if (mClassName != null) {
915             Log.i(LOG_TAG, String.format("Executing CTS test class %s", mClassName));
916             testPkgDefs.addAll(buildTestPackageDefSet(testRepo, mClassName, mMethodName));
917         } else if (mTestName != null) {
918             Log.i(LOG_TAG, String.format("Executing CTS test %s", mTestName));
919             String [] split = mTestName.split("#");
920             if (split.length != 2) {
921                 Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
922                         "Could not parse class and method from test %s", mTestName));
923             } else {
924                 String className = split[0];
925                 String methodName = split[1];
926                 testPkgDefs.addAll(buildTestPackageDefSet(testRepo, className, methodName));
927             }
928         } else if (mContinueSessionId != null) {
929             // create an in-memory derived plan that contains the notExecuted tests from previous
930             // session use timestamp as plan name so it will hopefully be unique
931             String uniquePlanName = Long.toString(System.currentTimeMillis());
932             PlanCreator planCreator = new PlanCreator(uniquePlanName, mContinueSessionId,
933                     CtsTestStatus.NOT_EXECUTED);
934             ITestPlan plan = createPlan(planCreator);
935             for (String testId : plan.getTestIds()) {
936                 if (mExcludedPackageNames.contains(AbiUtils.parseTestName(testId))) {
937                     continue;
938                 }
939                 ITestPackageDef testPackageDef = testRepo.getTestPackage(testId);
940                 if (testPackageDef == null) {
941                     CLog.e("Could not find test id %s referenced in plan %s", testId, mPlanName);
942                     continue;
943                 }
944 
945                 testPackageDef.setTestFilter(plan.getTestFilter(testId));
946                 testPkgDefs.add(testPackageDef);
947             }
948         } else {
949             // should never get here - was checkFields() not called?
950             throw new IllegalStateException("nothing to run?");
951         }
952         return testPkgDefs;
953     }
954 
955     /**
956      * Return the list of unique prerequisite Android package names
957      *
958      * @param testPackages The {@link TestPackage}s that contain prerequisites
959      */
getPrerequisitePackageNames(List<TestPackage> testPackages)960     private Collection<String> getPrerequisitePackageNames(List<TestPackage> testPackages) {
961         Set<String> pkgNames = new HashSet<>();
962         for (TestPackage testPkg : testPackages) {
963             String pkgName = testPkg.mPackageDef.getTargetPackageName();
964             if (pkgName != null) {
965                 pkgNames.add(pkgName);
966             }
967         }
968         return pkgNames;
969     }
970 
971     /**
972      * @return a {@link Set} containing {@link ITestPackageDef}s pertaining to the given
973      *     {@code className} and {@code methodName}.
974      */
buildTestPackageDefSet( ITestPackageRepo testRepo, String className, String methodName)975     private static Set<ITestPackageDef> buildTestPackageDefSet(
976             ITestPackageRepo testRepo, String className, String methodName) {
977         Set<ITestPackageDef> testPkgDefs = new LinkedHashSet<>();
978         // try to find packages to run from class name
979         List<String> packageIds = testRepo.findPackageIdsForTest(className);
980         if (packageIds.isEmpty()) {
981             Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
982                     "Could not find package for test class %s", className));
983         }
984         for (String packageId: packageIds) {
985             ITestPackageDef testPackageDef = testRepo.getTestPackage(packageId);
986             if (testPackageDef != null) {
987                 testPackageDef.setClassName(className, methodName);
988                 testPkgDefs.add(testPackageDef);
989             }
990         }
991         return testPkgDefs;
992     }
993 
994     /**
995      * Return the list (by abi) of unique prerequisite apks to install
996      *
997      * @param testPackages The {@link List} of {@link TestPackage} that contain prerequisite APKs
998      */
getPrerequisiteApks( List<TestPackage> testPackages, Set<String> abiSet)999     private Map<String, Set<String>> getPrerequisiteApks(
1000             List<TestPackage> testPackages, Set<String> abiSet) {
1001         Map<String, Set<String>> abiToApkMap = new HashMap<>();
1002         for (TestPackage testPkg : testPackages) {
1003             if (testPkg.getKnownTests().size() == 0) {
1004                 // No tests, no point in installing pre-reqs
1005                 continue;
1006             }
1007             String apkName = testPkg.mPackageDef.getTargetApkName();
1008             if (apkName == null) {
1009                 continue;
1010             }
1011             String abiName = testPkg.getAbi().getName();
1012             if (!abiSet.contains(abiName)) {
1013                 continue;
1014             }
1015 
1016             if (!abiToApkMap.containsKey(abiName)) {
1017                 abiToApkMap.put(abiName, new HashSet<String>());
1018             }
1019             abiToApkMap.get(abiName).add(apkName);
1020         }
1021         return abiToApkMap;
1022     }
1023 
1024     /**
1025      * FIXME eventually this should be removed once we get rid of CtsTestStubs, any other
1026      * prerequisite apks should be installed by the test runner
1027      *
1028      * Install the collection of test apk file names
1029      *
1030      * @param prerequisiteApks The APKs that must be installed
1031      * @throws DeviceNotAvailableException
1032      */
installPrerequisiteApks(Collection<String> prerequisiteApks, IAbi abi)1033     private void installPrerequisiteApks(Collection<String> prerequisiteApks, IAbi abi)
1034             throws DeviceNotAvailableException {
1035         if (prerequisiteApks == null) {
1036             return;
1037         }
1038         Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Installing prerequisites");
1039         for (String apkName : prerequisiteApks) {
1040             try {
1041                 File apkFile = mCtsBuild.getTestApp(apkName);
1042                 String[] options = {AbiUtils.createAbiFlag(abi.getName())};
1043                 String errorCode = getDevice().installPackage(apkFile, true, options);
1044                 if (errorCode != null) {
1045                     CLog.e("Failed to install %s. Reason: %s", apkName, errorCode);
1046                 }
1047             } catch (FileNotFoundException e) {
1048                 CLog.e("Could not find test apk %s", apkName);
1049             }
1050         }
1051     }
1052 
1053     /**
1054      * Uninstalls the collection of android package names from device.
1055      *
1056      * @param uninstallPackages The packages that must be uninstalled
1057      */
uninstallPrequisiteApks(Collection<String> uninstallPackages)1058     private void uninstallPrequisiteApks(Collection<String> uninstallPackages)
1059             throws DeviceNotAvailableException {
1060         for (String pkgName : uninstallPackages) {
1061             getDevice().uninstallPackage(pkgName);
1062         }
1063     }
1064 
1065     /**
1066      * {@inheritDoc}
1067      */
1068     @Override
split()1069     public Collection<IRemoteTest> split() {
1070         if (mShards <= 1) {
1071             return null;
1072         }
1073         checkFields();
1074 
1075         List<IRemoteTest> shardQueue = new LinkedList<>();
1076         for (int shardAssignment = 0; shardAssignment < mShards; shardAssignment++) {
1077             CtsTest ctsTest = new CtsTest(shardAssignment, mShards /* totalShards */);
1078             OptionCopier.copyOptionsNoThrow(this, ctsTest);
1079             // Set the shard count because the copy option on the previous line copies
1080             // over the mShard value
1081             ctsTest.mShards = 0;
1082             shardQueue.add(ctsTest);
1083         }
1084 
1085         return shardQueue;
1086     }
1087 
1088     /**
1089      * Runs the device info collector instrumentation on device, and forwards it to test listeners
1090      * as run metrics.
1091      * <p/>
1092      * Exposed so unit tests can mock.
1093      *
1094      * @throws DeviceNotAvailableException
1095      */
collectDeviceInfo(ITestDevice device, CtsBuildHelper ctsBuild, ITestInvocationListener listener)1096     void collectDeviceInfo(ITestDevice device, CtsBuildHelper ctsBuild,
1097             ITestInvocationListener listener) throws DeviceNotAvailableException {
1098         if (!mSkipDeviceInfo) {
1099             String abi = AbiFormatter.getDefaultAbi(device, "");
1100             DeviceInfoCollector.collectDeviceInfo(device, abi, ctsBuild.getTestCasesDir(), listener);
1101             DeviceInfoCollector.collectExtendedDeviceInfo(
1102                 device, abi, ctsBuild.getTestCasesDir(), listener, mBuildInfo);
1103         }
1104     }
1105 
1106     /**
1107      * Prepares the report log directory on host to store test metric report logs.
1108      */
prepareReportLogContainers(ITestDevice device, IBuildInfo buildInfo)1109     void prepareReportLogContainers(ITestDevice device, IBuildInfo buildInfo) {
1110         ReportLogUtil.prepareReportLogContainers(device, buildInfo);
1111     }
1112 
1113     /**
1114      * Collects the test metric report logs written out by device-side and host-side tests.
1115      */
collectReportLogs(ITestDevice device, IBuildInfo buildInfo)1116     void collectReportLogs(ITestDevice device, IBuildInfo buildInfo) {
1117         ReportLogUtil.collectReportLogs(device, buildInfo);
1118     }
1119 
1120     /**
1121      * Factory method for creating a {@link ITestPackageRepo}.
1122      * <p/>
1123      * Exposed for unit testing
1124      */
createTestCaseRepo()1125     ITestPackageRepo createTestCaseRepo() {
1126         return new TestPackageRepo(mCtsBuild.getTestCasesDir(), mIncludeKnownFailures);
1127     }
1128 
1129     /**
1130      * Factory method for creating a {@link TestPlan}.
1131      * <p/>
1132      * Exposed for unit testing
1133      */
createPlan(String planName)1134     ITestPlan createPlan(String planName) {
1135         return new TestPlan(planName, AbiUtils.getAbisSupportedByCompatibility());
1136     }
1137 
1138     /**
1139      * Gets the set of ABIs supported by both CTS and the device under test
1140      * <p/>
1141      * Exposed for unit testing
1142      * @return The set of ABIs to run the tests on
1143      * @throws DeviceNotAvailableException
1144      */
getAbis()1145     Set<String> getAbis() throws DeviceNotAvailableException {
1146         String bitness = (mForceAbi == null) ? "" : mForceAbi;
1147         Set<String> abis = new HashSet<>();
1148         for (String abi : AbiFormatter.getSupportedAbis(mDevice, bitness)) {
1149             if (AbiUtils.isAbiSupportedByCompatibility(abi)) {
1150                 abis.add(abi);
1151             }
1152         }
1153         return abis;
1154     }
1155 
1156     /**
1157      * Factory method for creating a {@link TestPlan} from a {@link PlanCreator}.
1158      * <p/>
1159      * Exposed for unit testing
1160      * @throws ConfigurationException
1161      */
createPlan(PlanCreator planCreator)1162     ITestPlan createPlan(PlanCreator planCreator)
1163             throws ConfigurationException {
1164         return planCreator.createDerivedPlan(mCtsBuild, AbiUtils.getAbisSupportedByCompatibility());
1165     }
1166 
1167     /**
1168      * Factory method for creating a {@link InputStream} from a plan xml file.
1169      * <p/>
1170      * Exposed for unit testing
1171      */
createXmlStream(File xmlFile)1172     InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
1173         return new BufferedInputStream(new FileInputStream(xmlFile));
1174     }
1175 
checkFields()1176     private void checkFields() {
1177         // for simplicity of command line usage, make --plan, --package, --test and --class mutually
1178         // exclusive
1179         boolean mutualExclusiveArgs = xor(mPlanName != null, mPackageNames.size() > 0,
1180                 mClassName != null, mContinueSessionId != null, mTestName != null);
1181 
1182         if (!mutualExclusiveArgs) {
1183             throw new IllegalArgumentException(String.format(
1184                     "Ambiguous or missing arguments. " +
1185                     "One and only one of --%s --%s(s), --%s or --%s to run can be specified",
1186                     PLAN_OPTION, PACKAGE_OPTION, CLASS_OPTION, CONTINUE_OPTION));
1187         }
1188         if (mMethodName != null && mClassName == null) {
1189             throw new IllegalArgumentException(String.format(
1190                     "Must specify --%s when --%s is used", CLASS_OPTION, METHOD_OPTION));
1191         }
1192         if (mCtsBuild == null) {
1193             throw new IllegalArgumentException("missing CTS build");
1194         }
1195     }
1196 
1197     /**
1198      * Helper method to perform exclusive or on list of boolean arguments
1199      *
1200      * @param args set of booleans on which to perform exclusive or
1201      * @return <code>true</code> if one and only one of <var>args</code> is <code>true</code>.
1202      *         Otherwise return <code>false</code>.
1203      */
xor(boolean... args)1204     private static boolean xor(boolean... args) {
1205         boolean currentVal = args[0];
1206         for (int i=1; i < args.length; i++) {
1207             if (currentVal && args[i]) {
1208                 return false;
1209             }
1210             currentVal |= args[i];
1211         }
1212         return currentVal;
1213     }
1214 
1215     /**
1216      * Forward the digest and package name to the listener as a metric
1217      *
1218      * @param listener Handles test results
1219      */
forwardPackageDetails(ITestPackageDef def, ITestInvocationListener listener)1220     private static void forwardPackageDetails(ITestPackageDef def, ITestInvocationListener listener) {
1221         Map<String, String> metrics = new HashMap<>(3);
1222         metrics.put(PACKAGE_NAME_METRIC, def.getName());
1223         metrics.put(PACKAGE_ABI_METRIC, def.getAbi().getName());
1224         metrics.put(PACKAGE_DIGEST_METRIC, def.getDigest());
1225         listener.testRunStarted(def.getId(), 0);
1226         listener.testRunEnded(0, metrics);
1227     }
1228 }
1229