Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2014 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 package com.drawelements.deqp.runner;
     17 
     18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
     19 import com.android.ddmlib.AdbCommandRejectedException;
     20 import com.android.ddmlib.IShellOutputReceiver;
     21 import com.android.ddmlib.MultiLineReceiver;
     22 import com.android.ddmlib.ShellCommandUnresponsiveException;
     23 import com.android.ddmlib.TimeoutException;
     24 import com.android.ddmlib.testrunner.TestIdentifier;
     25 import com.android.tradefed.build.IBuildInfo;
     26 import com.android.tradefed.config.Option;
     27 import com.android.tradefed.config.OptionClass;
     28 import com.android.tradefed.device.DeviceNotAvailableException;
     29 import com.android.tradefed.device.ITestDevice;
     30 import com.android.tradefed.log.LogUtil.CLog;
     31 import com.android.tradefed.result.ByteArrayInputStreamSource;
     32 import com.android.tradefed.result.ITestInvocationListener;
     33 import com.android.tradefed.result.LogDataType;
     34 import com.android.tradefed.testtype.IAbi;
     35 import com.android.tradefed.testtype.IAbiReceiver;
     36 import com.android.tradefed.testtype.IBuildReceiver;
     37 import com.android.tradefed.testtype.IDeviceTest;
     38 import com.android.tradefed.testtype.IRemoteTest;
     39 import com.android.tradefed.testtype.IRuntimeHintProvider;
     40 import com.android.tradefed.testtype.IShardableTest;
     41 import com.android.tradefed.testtype.IStrictShardableTest;
     42 import com.android.tradefed.testtype.ITestCollector;
     43 import com.android.tradefed.testtype.ITestFilterReceiver;
     44 import com.android.tradefed.util.AbiUtils;
     45 import com.android.tradefed.util.IRunUtil;
     46 import com.android.tradefed.util.RunInterruptedException;
     47 import com.android.tradefed.util.RunUtil;
     48 
     49 import java.io.BufferedReader;
     50 import java.io.File;
     51 import java.io.FileNotFoundException;
     52 import java.io.FileReader;
     53 import java.io.IOException;
     54 import java.io.Reader;
     55 import java.lang.reflect.InvocationTargetException;
     56 import java.lang.reflect.Method;
     57 import java.util.ArrayList;
     58 import java.util.Collection;
     59 import java.util.Collections;
     60 import java.util.HashMap;
     61 import java.util.HashSet;
     62 import java.util.Iterator;
     63 import java.util.LinkedHashMap;
     64 import java.util.LinkedHashSet;
     65 import java.util.LinkedList;
     66 import java.util.List;
     67 import java.util.Map;
     68 import java.util.Set;
     69 import java.util.concurrent.TimeUnit;
     70 import java.util.regex.Pattern;
     71 
     72 /**
     73  * Test runner for dEQP tests
     74  *
     75  * Supports running drawElements Quality Program tests found under external/deqp.
     76  */
     77 @OptionClass(alias="deqp-test-runner")
     78 public class DeqpTestRunner implements IBuildReceiver, IDeviceTest,
     79         ITestFilterReceiver, IAbiReceiver, IShardableTest, ITestCollector,
     80         IRuntimeHintProvider, IStrictShardableTest {
     81     private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk";
     82     private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp";
     83     private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log";
     84     private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped";
     85     private static final String NOT_EXECUTABLE_LOG_MESSAGE = "Abort: Test cannot be executed";
     86     private static final String CASE_LIST_FILE_NAME = "/sdcard/dEQP-TestCaseList.txt";
     87     private static final String LOG_FILE_NAME = "/sdcard/TestLog.qpa";
     88     public static final String FEATURE_LANDSCAPE = "android.hardware.screen.landscape";
     89     public static final String FEATURE_PORTRAIT = "android.hardware.screen.portrait";
     90     public static final String FEATURE_VULKAN_LEVEL = "android.hardware.vulkan.level";
     91 
     92     private static final int TESTCASE_BATCH_LIMIT = 1000;
     93     private static final int UNRESPONSIVE_CMD_TIMEOUT_MS = 10 * 60 * 1000; // 10min
     94 
     95     // !NOTE: There's a static method copyOptions() for copying options during split.
     96     // If you add state update copyOptions() as appropriate!
     97 
     98     @Option(name="deqp-package",
     99             description="Name of the deqp module used. Determines GLES version.",
    100             importance=Option.Importance.ALWAYS)
    101     private String mDeqpPackage;
    102     @Option(name="deqp-gl-config-name",
    103             description="GL render target config. See deqp documentation for syntax. ",
    104             importance=Option.Importance.NEVER)
    105     private String mConfigName = "";
    106     @Option(name="deqp-caselist-file",
    107             description="File listing the names of the cases to be run.",
    108             importance=Option.Importance.ALWAYS)
    109     private String mCaselistFile;
    110     @Option(name="deqp-screen-rotation",
    111             description="Screen orientation. Defaults to 'unspecified'",
    112             importance=Option.Importance.NEVER)
    113     private String mScreenRotation = "unspecified";
    114     @Option(name="deqp-surface-type",
    115             description="Surface type ('window', 'pbuffer', 'fbo'). Defaults to 'window'",
    116             importance=Option.Importance.NEVER)
    117     private String mSurfaceType = "window";
    118     @Option(name="deqp-config-required",
    119             description="Is current config required if API is supported? Defaults to false.",
    120             importance=Option.Importance.NEVER)
    121     private boolean mConfigRequired = false;
    122     @Option(name = "include-filter",
    123             description="Test include filter. '*' is zero or more letters. '.' has no special meaning.")
    124     private List<String> mIncludeFilters = new ArrayList<>();
    125     @Option(name = "include-filter-file",
    126             description="Load list of includes from the files given.")
    127     private List<String> mIncludeFilterFiles = new ArrayList<>();
    128     @Option(name = "exclude-filter",
    129             description="Test exclude filter. '*' is zero or more letters. '.' has no special meaning.")
    130     private List<String> mExcludeFilters = new ArrayList<>();
    131     @Option(name = "exclude-filter-file",
    132             description="Load list of excludes from the files given.")
    133     private List<String> mExcludeFilterFiles = new ArrayList<>();
    134     @Option(name = "collect-tests-only",
    135             description = "Only invoke the instrumentation to collect list of applicable test "
    136                     + "cases. All test run callbacks will be triggered, but test execution will "
    137                     + "not be actually carried out.")
    138     private boolean mCollectTestsOnly = false;
    139     @Option(name = "runtime-hint",
    140             isTimeVal = true,
    141             description="The estimated config runtime. Defaults to 200ms x num tests.")
    142     private long mRuntimeHint = -1;
    143 
    144     private Collection<TestIdentifier> mRemainingTests = null;
    145     private Map<TestIdentifier, Set<BatchRunConfiguration>> mTestInstances = null;
    146     private final TestInstanceResultListener mInstanceListerner = new TestInstanceResultListener();
    147     private final Map<TestIdentifier, Integer> mTestInstabilityRatings = new HashMap<>();
    148     private IAbi mAbi;
    149     private CompatibilityBuildHelper mBuildHelper;
    150     private boolean mLogData = false;
    151     private ITestDevice mDevice;
    152     private Set<String> mDeviceFeatures;
    153     private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
    154     private IRunUtil mRunUtil = RunUtil.getDefault();
    155     // When set will override the mCaselistFile for testing purposes.
    156     private Reader mCaselistReader = null;
    157 
    158     private IRecovery mDeviceRecovery = new Recovery(); {
    159         mDeviceRecovery.setSleepProvider(new SleepProvider());
    160     }
    161 
    162     public DeqpTestRunner() {
    163     }
    164 
    165     private DeqpTestRunner(DeqpTestRunner optionTemplate,
    166                 Map<TestIdentifier, Set<BatchRunConfiguration>> tests) {
    167         copyOptions(this, optionTemplate);
    168         mTestInstances = tests;
    169     }
    170 
    171     /**
    172      * @param abi the ABI to run the test on
    173      */
    174     @Override
    175     public void setAbi(IAbi abi) {
    176         mAbi = abi;
    177     }
    178 
    179     @Override
    180     public IAbi getAbi() {
    181         return mAbi;
    182     }
    183 
    184     /**
    185      * {@inheritDoc}
    186      */
    187     @Override
    188     public void setBuild(IBuildInfo buildInfo) {
    189         setBuildHelper(new CompatibilityBuildHelper(buildInfo));
    190     }
    191 
    192     /**
    193      * Exposed for better mockability during testing. In real use, always flows from
    194      * setBuild() called by the framework
    195      */
    196     public void setBuildHelper(CompatibilityBuildHelper helper) {
    197         mBuildHelper = helper;
    198     }
    199 
    200     /**
    201      * Enable or disable raw dEQP test log collection.
    202      */
    203     public void setCollectLogs(boolean logData) {
    204         mLogData = logData;
    205     }
    206 
    207     /**
    208      * Get the deqp-package option contents.
    209      */
    210     public String getPackageName() {
    211         return mDeqpPackage;
    212     }
    213 
    214     /**
    215      * {@inheritDoc}
    216      */
    217     @Override
    218     public void setDevice(ITestDevice device) {
    219         mDevice = device;
    220     }
    221 
    222     /**
    223      * {@inheritDoc}
    224      */
    225     @Override
    226     public ITestDevice getDevice() {
    227         return mDevice;
    228     }
    229 
    230     /**
    231      * Set recovery handler.
    232      *
    233      * Exposed for unit testing.
    234      */
    235     public void setRecovery(IRecovery deviceRecovery) {
    236         mDeviceRecovery = deviceRecovery;
    237     }
    238 
    239     /**
    240      * Set IRunUtil.
    241      *
    242      * Exposed for unit testing.
    243      */
    244     public void setRunUtil(IRunUtil runUtil) {
    245         mRunUtil = runUtil;
    246     }
    247 
    248     /**
    249      * Exposed for unit testing
    250      */
    251     public void setCaselistReader(Reader caselistReader) {
    252         mCaselistReader = caselistReader;
    253     }
    254 
    255     private static final class CapabilityQueryFailureException extends Exception {
    256     }
    257 
    258     /**
    259      * dEQP test instance listerer and invocation result forwarded
    260      */
    261     private class TestInstanceResultListener {
    262         private ITestInvocationListener mSink;
    263         private BatchRunConfiguration mRunConfig;
    264 
    265         private TestIdentifier mCurrentTestId;
    266         private boolean mGotTestResult;
    267         private String mCurrentTestLog;
    268 
    269         private class PendingResult {
    270             boolean allInstancesPassed;
    271             Map<BatchRunConfiguration, String> testLogs;
    272             Map<BatchRunConfiguration, String> errorMessages;
    273             Set<BatchRunConfiguration> remainingConfigs;
    274         }
    275 
    276         private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>();
    277 
    278         public void setSink(ITestInvocationListener sink) {
    279             mSink = sink;
    280         }
    281 
    282         public void setCurrentConfig(BatchRunConfiguration runConfig) {
    283             mRunConfig = runConfig;
    284         }
    285 
    286         /**
    287          * Get currently processed test id, or null if not currently processing a test case
    288          */
    289         public TestIdentifier getCurrentTestId() {
    290             return mCurrentTestId;
    291         }
    292 
    293         /**
    294          * Forward result to sink
    295          */
    296         private void forwardFinalizedPendingResult(TestIdentifier testId) {
    297             if (mRemainingTests.contains(testId)) {
    298                 final PendingResult result = mPendingResults.get(testId);
    299 
    300                 mPendingResults.remove(testId);
    301                 mRemainingTests.remove(testId);
    302 
    303                 // Forward results to the sink
    304                 mSink.testStarted(testId);
    305 
    306                 // Test Log
    307                 if (mLogData) {
    308                     for (Map.Entry<BatchRunConfiguration, String> entry :
    309                             result.testLogs.entrySet()) {
    310                         final ByteArrayInputStreamSource source
    311                                 = new ByteArrayInputStreamSource(entry.getValue().getBytes());
    312 
    313                         mSink.testLog(testId.getClassName() + "." + testId.getTestName() + "@"
    314                                 + entry.getKey().getId(), LogDataType.XML, source);
    315 
    316                         source.cancel();
    317                     }
    318                 }
    319 
    320                 // Error message
    321                 if (!result.allInstancesPassed) {
    322                     final StringBuilder errorLog = new StringBuilder();
    323 
    324                     for (Map.Entry<BatchRunConfiguration, String> entry :
    325                             result.errorMessages.entrySet()) {
    326                         if (errorLog.length() > 0) {
    327                             errorLog.append('\n');
    328                         }
    329                         errorLog.append(String.format("=== with config %s ===\n",
    330                                 entry.getKey().getId()));
    331                         errorLog.append(entry.getValue());
    332                     }
    333 
    334                     mSink.testFailed(testId, errorLog.toString());
    335                 }
    336 
    337                 final Map<String, String> emptyMap = Collections.emptyMap();
    338                 mSink.testEnded(testId, emptyMap);
    339             }
    340         }
    341 
    342         /**
    343          * Declare existence of a test and instances
    344          */
    345         public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) {
    346             // Test instances cannot change at runtime, ignore if we have already set this
    347             if (!mPendingResults.containsKey(testId)) {
    348                 final PendingResult pendingResult = new PendingResult();
    349                 pendingResult.allInstancesPassed = true;
    350                 pendingResult.testLogs = new LinkedHashMap<>();
    351                 pendingResult.errorMessages = new LinkedHashMap<>();
    352                 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument
    353                 mPendingResults.put(testId, pendingResult);
    354             }
    355         }
    356 
    357         /**
    358          * Query if test instance has not yet been executed
    359          */
    360         public boolean isPendingTestInstance(TestIdentifier testId,
    361                 BatchRunConfiguration config) {
    362             final PendingResult result = mPendingResults.get(testId);
    363             if (result == null) {
    364                 // test is not in the current working batch of the runner, i.e. it cannot be
    365                 // "partially" completed.
    366                 if (!mRemainingTests.contains(testId)) {
    367                     // The test has been fully executed. Not pending.
    368                     return false;
    369                 } else {
    370                     // Test has not yet been executed. Check if such instance exists
    371                     return mTestInstances.get(testId).contains(config);
    372                 }
    373             } else {
    374                 // could be partially completed, check this particular config
    375                 return result.remainingConfigs.contains(config);
    376             }
    377         }
    378 
    379         /**
    380          * Fake execution of an instance with current config
    381          */
    382         public void skipTest(TestIdentifier testId) {
    383             final PendingResult result = mPendingResults.get(testId);
    384 
    385             result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE);
    386             result.remainingConfigs.remove(mRunConfig);
    387 
    388             // Pending result finished, report result
    389             if (result.remainingConfigs.isEmpty()) {
    390                 forwardFinalizedPendingResult(testId);
    391             }
    392         }
    393 
    394         /**
    395          * Fake failure of an instance with current config
    396          */
    397         public void abortTest(TestIdentifier testId, String errorMessage) {
    398             final PendingResult result = mPendingResults.get(testId);
    399 
    400             // Mark as executed
    401             result.allInstancesPassed = false;
    402             result.errorMessages.put(mRunConfig, errorMessage);
    403             result.remainingConfigs.remove(mRunConfig);
    404 
    405             // Pending result finished, report result
    406             if (result.remainingConfigs.isEmpty()) {
    407                 forwardFinalizedPendingResult(testId);
    408             }
    409 
    410             if (testId.equals(mCurrentTestId)) {
    411                 mCurrentTestId = null;
    412             }
    413         }
    414 
    415         /**
    416          * Handles beginning of dEQP session.
    417          */
    418         private void handleBeginSession(Map<String, String> values) {
    419             // ignore
    420         }
    421 
    422         /**
    423          * Handles end of dEQP session.
    424          */
    425         private void handleEndSession(Map<String, String> values) {
    426             // ignore
    427         }
    428 
    429         /**
    430          * Handles beginning of dEQP testcase.
    431          */
    432         private void handleBeginTestCase(Map<String, String> values) {
    433             mCurrentTestId = pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath"));
    434             mCurrentTestLog = "";
    435             mGotTestResult = false;
    436 
    437             // mark instance as started
    438             if (mPendingResults.get(mCurrentTestId) != null) {
    439                 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig);
    440             } else {
    441                 CLog.w("Got unexpected start of %s", mCurrentTestId);
    442             }
    443         }
    444 
    445         /**
    446          * Handles end of dEQP testcase.
    447          */
    448         private void handleEndTestCase(Map<String, String> values) {
    449             final PendingResult result = mPendingResults.get(mCurrentTestId);
    450 
    451             if (result != null) {
    452                 if (!mGotTestResult) {
    453                     result.allInstancesPassed = false;
    454                     result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE);
    455                 }
    456 
    457                 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) {
    458                     result.testLogs.put(mRunConfig, mCurrentTestLog);
    459                 }
    460 
    461                 // Pending result finished, report result
    462                 if (result.remainingConfigs.isEmpty()) {
    463                     forwardFinalizedPendingResult(mCurrentTestId);
    464                 }
    465             } else {
    466                 CLog.w("Got unexpected end of %s", mCurrentTestId);
    467             }
    468             mCurrentTestId = null;
    469         }
    470 
    471         /**
    472          * Handles dEQP testcase result.
    473          */
    474         private void handleTestCaseResult(Map<String, String> values) {
    475             String code = values.get("dEQP-TestCaseResult-Code");
    476             String details = values.get("dEQP-TestCaseResult-Details");
    477 
    478             if (mPendingResults.get(mCurrentTestId) == null) {
    479                 CLog.w("Got unexpected result for %s", mCurrentTestId);
    480                 mGotTestResult = true;
    481                 return;
    482             }
    483 
    484             if (code.compareTo("Pass") == 0) {
    485                 mGotTestResult = true;
    486             } else if (code.compareTo("NotSupported") == 0) {
    487                 mGotTestResult = true;
    488             } else if (code.compareTo("QualityWarning") == 0) {
    489                 mGotTestResult = true;
    490             } else if (code.compareTo("CompatibilityWarning") == 0) {
    491                 mGotTestResult = true;
    492             } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0
    493                     || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0
    494                     || code.compareTo("Timeout") == 0) {
    495                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
    496                 mPendingResults.get(mCurrentTestId)
    497                         .errorMessages.put(mRunConfig, code + ": " + details);
    498                 mGotTestResult = true;
    499             } else {
    500                 String codeError = "Unknown result code: " + code;
    501                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
    502                 mPendingResults.get(mCurrentTestId)
    503                         .errorMessages.put(mRunConfig, codeError + ": " + details);
    504                 mGotTestResult = true;
    505             }
    506         }
    507 
    508         /**
    509          * Handles terminated dEQP testcase.
    510          */
    511         private void handleTestCaseTerminate(Map<String, String> values) {
    512             final PendingResult result = mPendingResults.get(mCurrentTestId);
    513 
    514             if (result != null) {
    515                 String reason = values.get("dEQP-TerminateTestCase-Reason");
    516                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
    517                 mPendingResults.get(mCurrentTestId)
    518                         .errorMessages.put(mRunConfig, "Terminated: " + reason);
    519 
    520                 // Pending result finished, report result
    521                 if (result.remainingConfigs.isEmpty()) {
    522                     forwardFinalizedPendingResult(mCurrentTestId);
    523                 }
    524             } else {
    525                 CLog.w("Got unexpected termination of %s", mCurrentTestId);
    526             }
    527 
    528             mCurrentTestId = null;
    529             mGotTestResult = true;
    530         }
    531 
    532         /**
    533          * Handles dEQP testlog data.
    534          */
    535         private void handleTestLogData(Map<String, String> values) {
    536             mCurrentTestLog = mCurrentTestLog + values.get("dEQP-TestLogData-Log");
    537         }
    538 
    539         /**
    540          * Handles new instrumentation status message.
    541          */
    542         public void handleStatus(Map<String, String> values) {
    543             String eventType = values.get("dEQP-EventType");
    544 
    545             if (eventType == null) {
    546                 return;
    547             }
    548 
    549             if (eventType.compareTo("BeginSession") == 0) {
    550                 handleBeginSession(values);
    551             } else if (eventType.compareTo("EndSession") == 0) {
    552                 handleEndSession(values);
    553             } else if (eventType.compareTo("BeginTestCase") == 0) {
    554                 handleBeginTestCase(values);
    555             } else if (eventType.compareTo("EndTestCase") == 0) {
    556                 handleEndTestCase(values);
    557             } else if (eventType.compareTo("TestCaseResult") == 0) {
    558                 handleTestCaseResult(values);
    559             } else if (eventType.compareTo("TerminateTestCase") == 0) {
    560                 handleTestCaseTerminate(values);
    561             } else if (eventType.compareTo("TestLogData") == 0) {
    562                 handleTestLogData(values);
    563             }
    564         }
    565 
    566         /**
    567          * Signal listener that batch ended and forget incomplete results.
    568          */
    569         public void endBatch() {
    570             // end open test if when stream ends
    571             if (mCurrentTestId != null) {
    572                 // Current instance was removed from remainingConfigs when case
    573                 // started. Mark current instance as pending.
    574                 if (mPendingResults.get(mCurrentTestId) != null) {
    575                     mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig);
    576                 } else {
    577                     CLog.w("Got unexpected internal state of %s", mCurrentTestId);
    578                 }
    579             }
    580             mCurrentTestId = null;
    581         }
    582     }
    583 
    584     /**
    585      * dEQP instrumentation parser
    586      */
    587     private static class InstrumentationParser extends MultiLineReceiver {
    588         private TestInstanceResultListener mListener;
    589 
    590         private Map<String, String> mValues;
    591         private String mCurrentName;
    592         private String mCurrentValue;
    593         private int mResultCode;
    594         private boolean mGotExitValue = false;
    595 
    596 
    597         public InstrumentationParser(TestInstanceResultListener listener) {
    598             mListener = listener;
    599         }
    600 
    601         /**
    602          * {@inheritDoc}
    603          */
    604         @Override
    605         public void processNewLines(String[] lines) {
    606             for (String line : lines) {
    607                 if (mValues == null) mValues = new HashMap<String, String>();
    608 
    609                 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) {
    610                     if (mCurrentName != null) {
    611                         mValues.put(mCurrentName, mCurrentValue);
    612 
    613                         mCurrentName = null;
    614                         mCurrentValue = null;
    615                     }
    616 
    617                     mListener.handleStatus(mValues);
    618                     mValues = null;
    619                 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
    620                     if (mCurrentName != null) {
    621                         mValues.put(mCurrentName, mCurrentValue);
    622 
    623                         mCurrentValue = null;
    624                         mCurrentName = null;
    625                     }
    626 
    627                     String prefix = "INSTRUMENTATION_STATUS: ";
    628                     int nameBegin = prefix.length();
    629                     int nameEnd = line.indexOf('=');
    630                     int valueBegin = nameEnd + 1;
    631 
    632                     mCurrentName = line.substring(nameBegin, nameEnd);
    633                     mCurrentValue = line.substring(valueBegin);
    634                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
    635                     try {
    636                         mResultCode = Integer.parseInt(line.substring(22));
    637                         mGotExitValue = true;
    638                     } catch (NumberFormatException ex) {
    639                         CLog.w("Instrumentation code format unexpected");
    640                     }
    641                 } else if (mCurrentValue != null) {
    642                     mCurrentValue = mCurrentValue + line;
    643                 }
    644             }
    645         }
    646 
    647         /**
    648          * {@inheritDoc}
    649          */
    650         @Override
    651         public void done() {
    652             if (mCurrentName != null) {
    653                 mValues.put(mCurrentName, mCurrentValue);
    654 
    655                 mCurrentName = null;
    656                 mCurrentValue = null;
    657             }
    658 
    659             if (mValues != null) {
    660                 mListener.handleStatus(mValues);
    661                 mValues = null;
    662             }
    663         }
    664 
    665         /**
    666          * {@inheritDoc}
    667          */
    668         @Override
    669         public boolean isCancelled() {
    670             return false;
    671         }
    672 
    673         /**
    674          * Returns whether target instrumentation exited normally.
    675          */
    676         public boolean wasSuccessful() {
    677             return mGotExitValue;
    678         }
    679 
    680         /**
    681          * Returns Instrumentation return code
    682          */
    683         public int getResultCode() {
    684             return mResultCode;
    685         }
    686     }
    687 
    688     /**
    689      * dEQP platfom query instrumentation parser
    690      */
    691     private static class PlatformQueryInstrumentationParser extends MultiLineReceiver {
    692         private Map<String,String> mResultMap = new LinkedHashMap<>();
    693         private int mResultCode;
    694         private boolean mGotExitValue = false;
    695 
    696         /**
    697          * {@inheritDoc}
    698          */
    699         @Override
    700         public void processNewLines(String[] lines) {
    701             for (String line : lines) {
    702                 if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
    703                     final String parts[] = line.substring(24).split("=",2);
    704                     if (parts.length == 2) {
    705                         mResultMap.put(parts[0], parts[1]);
    706                     } else {
    707                         CLog.w("Instrumentation status format unexpected");
    708                     }
    709                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
    710                     try {
    711                         mResultCode = Integer.parseInt(line.substring(22));
    712                         mGotExitValue = true;
    713                     } catch (NumberFormatException ex) {
    714                         CLog.w("Instrumentation code format unexpected");
    715                     }
    716                 }
    717             }
    718         }
    719 
    720         /**
    721          * {@inheritDoc}
    722          */
    723         @Override
    724         public boolean isCancelled() {
    725             return false;
    726         }
    727 
    728         /**
    729          * Returns whether target instrumentation exited normally.
    730          */
    731         public boolean wasSuccessful() {
    732             return mGotExitValue;
    733         }
    734 
    735         /**
    736          * Returns Instrumentation return code
    737          */
    738         public int getResultCode() {
    739             return mResultCode;
    740         }
    741 
    742         public Map<String,String> getResultMap() {
    743             return mResultMap;
    744         }
    745     }
    746 
    747     /**
    748      * Interface for sleeping.
    749      *
    750      * Exposed for unit testing
    751      */
    752     public static interface ISleepProvider {
    753         public void sleep(int milliseconds);
    754     }
    755 
    756     private static class SleepProvider implements ISleepProvider {
    757         @Override
    758         public void sleep(int milliseconds) {
    759             RunUtil.getDefault().sleep(milliseconds);
    760         }
    761     }
    762 
    763     /**
    764      * Interface for failure recovery.
    765      *
    766      * Exposed for unit testing
    767      */
    768     public static interface IRecovery {
    769         /**
    770          * Sets the sleep provider IRecovery works on
    771          */
    772         public void setSleepProvider(ISleepProvider sleepProvider);
    773 
    774         /**
    775          * Sets the device IRecovery works on
    776          */
    777         public void setDevice(ITestDevice device);
    778 
    779         /**
    780          * Informs Recovery that test execution has progressed since the last recovery
    781          */
    782         public void onExecutionProgressed();
    783 
    784         /**
    785          * Tries to recover device after failed refused connection.
    786          *
    787          * @throws DeviceNotAvailableException if recovery did not succeed
    788          */
    789         public void recoverConnectionRefused() throws DeviceNotAvailableException;
    790 
    791         /**
    792          * Tries to recover device after abnormal execution termination or link failure.
    793          *
    794          * @throws DeviceNotAvailableException if recovery did not succeed
    795          */
    796         public void recoverComLinkKilled() throws DeviceNotAvailableException;
    797     }
    798 
    799     /**
    800      * State machine for execution failure recovery.
    801      *
    802      * Exposed for unit testing
    803      */
    804     public static class Recovery implements IRecovery {
    805         private int RETRY_COOLDOWN_MS = 6000; // 6 seconds
    806         private int PROCESS_KILL_WAIT_MS = 1000; // 1 second
    807 
    808         private static enum MachineState {
    809             WAIT, // recover by waiting
    810             RECOVER, // recover by calling recover()
    811             REBOOT, // recover by rebooting
    812             FAIL, // cannot recover
    813         }
    814 
    815         private MachineState mState = MachineState.WAIT;
    816         private ITestDevice mDevice;
    817         private ISleepProvider mSleepProvider;
    818 
    819         private static class ProcessKillFailureException extends Exception {
    820         }
    821 
    822         /**
    823          * {@inheritDoc}
    824          */
    825         @Override
    826         public void setSleepProvider(ISleepProvider sleepProvider) {
    827             mSleepProvider = sleepProvider;
    828         }
    829 
    830         /**
    831          * {@inheritDoc}
    832          */
    833         @Override
    834         public void setDevice(ITestDevice device) {
    835             mDevice = device;
    836         }
    837 
    838         /**
    839          * {@inheritDoc}
    840          */
    841         @Override
    842         public void onExecutionProgressed() {
    843             mState = MachineState.WAIT;
    844         }
    845 
    846         /**
    847          * {@inheritDoc}
    848          */
    849         @Override
    850         public void recoverConnectionRefused() throws DeviceNotAvailableException {
    851             switch (mState) {
    852                 case WAIT: // not a valid stratedy for connection refusal, fallthrough
    853                 case RECOVER:
    854                     // First failure, just try to recover
    855                     CLog.w("ADB connection failed, trying to recover");
    856                     mState = MachineState.REBOOT; // the next step is to reboot
    857 
    858                     try {
    859                         recoverDevice();
    860                     } catch (DeviceNotAvailableException ex) {
    861                         // chain forward
    862                         recoverConnectionRefused();
    863                     }
    864                     break;
    865 
    866                 case REBOOT:
    867                     // Second failure in a row, try to reboot
    868                     CLog.w("ADB connection failed after recovery, rebooting device");
    869                     mState = MachineState.FAIL; // the next step is to fail
    870 
    871                     try {
    872                         rebootDevice();
    873                     } catch (DeviceNotAvailableException ex) {
    874                         // chain forward
    875                         recoverConnectionRefused();
    876                     }
    877                     break;
    878 
    879                 case FAIL:
    880                     // Third failure in a row, just fail
    881                     CLog.w("Cannot recover ADB connection");
    882                     throw new DeviceNotAvailableException("failed to connect after reboot",
    883                             mDevice.getSerialNumber());
    884             }
    885         }
    886 
    887         /**
    888          * {@inheritDoc}
    889          */
    890         @Override
    891         public void recoverComLinkKilled() throws DeviceNotAvailableException {
    892             switch (mState) {
    893                 case WAIT:
    894                     // First failure, just try to wait and try again
    895                     CLog.w("ADB link failed, retrying after a cooldown period");
    896                     mState = MachineState.RECOVER; // the next step is to recover the device
    897 
    898                     waitCooldown();
    899 
    900                     // even if the link to deqp on-device process was killed, the process might
    901                     // still be alive. Locate and terminate such unwanted processes.
    902                     try {
    903                         killDeqpProcess();
    904                     } catch (DeviceNotAvailableException ex) {
    905                         // chain forward
    906                         recoverComLinkKilled();
    907                     } catch (ProcessKillFailureException ex) {
    908                         // chain forward
    909                         recoverComLinkKilled();
    910                     }
    911                     break;
    912 
    913                 case RECOVER:
    914                     // Second failure, just try to recover
    915                     CLog.w("ADB link failed, trying to recover");
    916                     mState = MachineState.REBOOT; // the next step is to reboot
    917 
    918                     try {
    919                         recoverDevice();
    920                         killDeqpProcess();
    921                     } catch (DeviceNotAvailableException ex) {
    922                         // chain forward
    923                         recoverComLinkKilled();
    924                     } catch (ProcessKillFailureException ex) {
    925                         // chain forward
    926                         recoverComLinkKilled();
    927                     }
    928                     break;
    929 
    930                 case REBOOT:
    931                     // Third failure in a row, try to reboot
    932                     CLog.w("ADB link failed after recovery, rebooting device");
    933                     mState = MachineState.FAIL; // the next step is to fail
    934 
    935                     try {
    936                         rebootDevice();
    937                     } catch (DeviceNotAvailableException ex) {
    938                         // chain forward
    939                         recoverComLinkKilled();
    940                     }
    941                     break;
    942 
    943                 case FAIL:
    944                     // Fourth failure in a row, just fail
    945                     CLog.w("Cannot recover ADB connection");
    946                     throw new DeviceNotAvailableException("link killed after reboot",
    947                             mDevice.getSerialNumber());
    948             }
    949         }
    950 
    951         private void waitCooldown() {
    952             mSleepProvider.sleep(RETRY_COOLDOWN_MS);
    953         }
    954 
    955         private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException {
    956             final List<Integer> pids = new ArrayList<Integer>(2);
    957             final String processes = mDevice.executeShellCommand("ps | grep com.drawelements");
    958             final String[] lines = processes.split("(\\r|\\n)+");
    959             for (String line : lines) {
    960                 final String[] fields = line.split("\\s+");
    961                 if (fields.length < 2) {
    962                     continue;
    963                 }
    964 
    965                 try {
    966                     final int processId = Integer.parseInt(fields[1], 10);
    967                     pids.add(processId);
    968                 } catch (NumberFormatException ex) {
    969                     continue;
    970                 }
    971             }
    972             return pids;
    973         }
    974 
    975         private void killDeqpProcess() throws DeviceNotAvailableException,
    976                 ProcessKillFailureException {
    977             for (Integer processId : getDeqpProcessPids()) {
    978                 mDevice.executeShellCommand(String.format("kill -9 %d", processId));
    979             }
    980 
    981             mSleepProvider.sleep(PROCESS_KILL_WAIT_MS);
    982 
    983             // check that processes actually died
    984             if (getDeqpProcessPids().iterator().hasNext()) {
    985                 // a process is still alive, killing failed
    986                 throw new ProcessKillFailureException();
    987             }
    988         }
    989 
    990         public void recoverDevice() throws DeviceNotAvailableException {
    991             // Work around the API. We need to call recoverDevice() on the test device and
    992             // we know that mDevice is a TestDevice. However even though the recoverDevice()
    993             // method is public suggesting it should be publicly accessible, the class itself
    994             // and its super-interface (IManagedTestDevice) are package-private.
    995             final Method recoverDeviceMethod;
    996             try {
    997                 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice");
    998                 recoverDeviceMethod.setAccessible(true);
    999             } catch (NoSuchMethodException ex) {
   1000                 throw new AssertionError("Test device must have recoverDevice()");
   1001             }
   1002 
   1003             try {
   1004                 recoverDeviceMethod.invoke(mDevice);
   1005             } catch (InvocationTargetException ex) {
   1006                 if (ex.getCause() instanceof DeviceNotAvailableException) {
   1007                     throw (DeviceNotAvailableException)ex.getCause();
   1008                 } else if (ex.getCause() instanceof RuntimeException) {
   1009                     throw (RuntimeException)ex.getCause();
   1010                 } else {
   1011                     throw new AssertionError("unexpected throw", ex);
   1012                 }
   1013             } catch (IllegalAccessException ex) {
   1014                 throw new AssertionError("unexpected throw", ex);
   1015             }
   1016         }
   1017 
   1018         private void rebootDevice() throws DeviceNotAvailableException {
   1019             mDevice.reboot();
   1020         }
   1021     }
   1022 
   1023     private static Map<TestIdentifier, Set<BatchRunConfiguration>> generateTestInstances(
   1024             Reader testlist, String configName, String screenRotation, String surfaceType,
   1025             boolean required) {
   1026         // Note: This is specifically a LinkedHashMap to guarantee that tests are iterated
   1027         // in the insertion order.
   1028         final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new LinkedHashMap<>();
   1029         try {
   1030             BufferedReader testlistReader = new BufferedReader(testlist);
   1031             String testName;
   1032             while ((testName = testlistReader.readLine()) != null) {
   1033                 // Test name -> testId -> only one config -> done.
   1034                 final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>();
   1035                 BatchRunConfiguration config = new BatchRunConfiguration(configName, screenRotation, surfaceType, required);
   1036                 testInstanceSet.add(config);
   1037                 TestIdentifier test = pathToIdentifier(testName);
   1038                 instances.put(test, testInstanceSet);
   1039             }
   1040             testlistReader.close();
   1041         }
   1042         catch (IOException e)
   1043         {
   1044             throw new RuntimeException("Failure while reading the test case list for deqp: " + e.getMessage());
   1045         }
   1046 
   1047         return instances;
   1048     }
   1049 
   1050     private Set<BatchRunConfiguration> getTestRunConfigs(TestIdentifier testId) {
   1051         return mTestInstances.get(testId);
   1052     }
   1053 
   1054     /**
   1055      * Get the test instance of the runner. Exposed for testing.
   1056      */
   1057     Map<TestIdentifier, Set<BatchRunConfiguration>> getTestInstance() {
   1058         return mTestInstances;
   1059     }
   1060 
   1061     /**
   1062      * Converts dEQP testcase path to TestIdentifier.
   1063      */
   1064     private static TestIdentifier pathToIdentifier(String testPath) {
   1065         int indexOfLastDot = testPath.lastIndexOf('.');
   1066         String className = testPath.substring(0, indexOfLastDot);
   1067         String testName = testPath.substring(indexOfLastDot+1);
   1068 
   1069         return new TestIdentifier(className, testName);
   1070     }
   1071 
   1072     // \todo [2015-10-16 kalle] How unique should this be?
   1073     private String getId() {
   1074         return AbiUtils.createId(mAbi.getName(), mDeqpPackage);
   1075     }
   1076 
   1077     /**
   1078      * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute.
   1079      */
   1080     private static String generateTestCaseTrieFromPaths(Collection<String> tests) {
   1081         String result = "{";
   1082         boolean first = true;
   1083 
   1084         // Add testcases to results
   1085         for (Iterator<String> iter = tests.iterator(); iter.hasNext();) {
   1086             String test = iter.next();
   1087             String[] components = test.split("\\.");
   1088 
   1089             if (components.length == 1) {
   1090                 if (!first) {
   1091                     result = result + ",";
   1092                 }
   1093                 first = false;
   1094 
   1095                 result += components[0];
   1096                 iter.remove();
   1097             }
   1098         }
   1099 
   1100         if (!tests.isEmpty()) {
   1101             HashMap<String, ArrayList<String> > testGroups = new HashMap<>();
   1102 
   1103             // Collect all sub testgroups
   1104             for (String test : tests) {
   1105                 String[] components = test.split("\\.");
   1106                 ArrayList<String> testGroup = testGroups.get(components[0]);
   1107 
   1108                 if (testGroup == null) {
   1109                     testGroup = new ArrayList<String>();
   1110                     testGroups.put(components[0], testGroup);
   1111                 }
   1112 
   1113                 testGroup.add(test.substring(components[0].length()+1));
   1114             }
   1115 
   1116             for (String testGroup : testGroups.keySet()) {
   1117                 if (!first) {
   1118                     result = result + ",";
   1119                 }
   1120 
   1121                 first = false;
   1122                 result = result + testGroup
   1123                         + generateTestCaseTrieFromPaths(testGroups.get(testGroup));
   1124             }
   1125         }
   1126 
   1127         return result + "}";
   1128     }
   1129 
   1130     /**
   1131      * Generates testcase trie from TestIdentifiers.
   1132      */
   1133     private static String generateTestCaseTrie(Collection<TestIdentifier> tests) {
   1134         ArrayList<String> testPaths = new ArrayList<String>();
   1135 
   1136         for (TestIdentifier test : tests) {
   1137             testPaths.add(test.getClassName() + "." + test.getTestName());
   1138         }
   1139 
   1140         return generateTestCaseTrieFromPaths(testPaths);
   1141     }
   1142 
   1143     private static class TestBatch {
   1144         public BatchRunConfiguration config;
   1145         public List<TestIdentifier> tests;
   1146     }
   1147 
   1148     /**
   1149      * Creates a TestBatch from the given tests or null if not tests remaining.
   1150      *
   1151      *  @param pool List of tests to select from
   1152      *  @param requiredConfig Select only instances with pending requiredConfig, or null to select
   1153      *         any run configuration.
   1154      */
   1155     private TestBatch selectRunBatch(Collection<TestIdentifier> pool,
   1156             BatchRunConfiguration requiredConfig) {
   1157         // select one test (leading test) that is going to be executed and then pack along as many
   1158         // other compatible instances as possible.
   1159 
   1160         TestIdentifier leadingTest = null;
   1161         for (TestIdentifier test : pool) {
   1162             if (!mRemainingTests.contains(test)) {
   1163                 continue;
   1164             }
   1165             if (requiredConfig != null &&
   1166                     !mInstanceListerner.isPendingTestInstance(test, requiredConfig)) {
   1167                 continue;
   1168             }
   1169             leadingTest = test;
   1170             break;
   1171         }
   1172 
   1173         // no remaining tests?
   1174         if (leadingTest == null) {
   1175             return null;
   1176         }
   1177 
   1178         BatchRunConfiguration leadingTestConfig = null;
   1179         if (requiredConfig != null) {
   1180             leadingTestConfig = requiredConfig;
   1181         } else {
   1182             for (BatchRunConfiguration runConfig : getTestRunConfigs(leadingTest)) {
   1183                 if (mInstanceListerner.isPendingTestInstance(leadingTest, runConfig)) {
   1184                     leadingTestConfig = runConfig;
   1185                     break;
   1186                 }
   1187             }
   1188         }
   1189 
   1190         // test pending <=> test has a pending config
   1191         if (leadingTestConfig == null) {
   1192             throw new AssertionError("search postcondition failed");
   1193         }
   1194 
   1195         final int leadingInstability = getTestInstabilityRating(leadingTest);
   1196 
   1197         final TestBatch runBatch = new TestBatch();
   1198         runBatch.config = leadingTestConfig;
   1199         runBatch.tests = new ArrayList<>();
   1200         runBatch.tests.add(leadingTest);
   1201 
   1202         for (TestIdentifier test : pool) {
   1203             if (test == leadingTest) {
   1204                 // do not re-select the leading tests
   1205                 continue;
   1206             }
   1207             if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) {
   1208                 // select only compatible
   1209                 continue;
   1210             }
   1211             if (getTestInstabilityRating(test) != leadingInstability) {
   1212                 // pack along only cases in the same stability category. Packing more dangerous
   1213                 // tests along jeopardizes the stability of this run. Packing more stable tests
   1214                 // along jeopardizes their stability rating.
   1215                 continue;
   1216             }
   1217             if (runBatch.tests.size() >= getBatchSizeLimitForInstability(leadingInstability)) {
   1218                 // batch size is limited.
   1219                 break;
   1220             }
   1221             runBatch.tests.add(test);
   1222         }
   1223 
   1224         return runBatch;
   1225     }
   1226 
   1227     private int getBatchNumPendingCases(TestBatch batch) {
   1228         int numPending = 0;
   1229         for (TestIdentifier test : batch.tests) {
   1230             if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1231                 ++numPending;
   1232             }
   1233         }
   1234         return numPending;
   1235     }
   1236 
   1237     private int getBatchSizeLimitForInstability(int batchInstabilityRating) {
   1238         // reduce group size exponentially down to one
   1239         return Math.max(1, TESTCASE_BATCH_LIMIT / (1 << batchInstabilityRating));
   1240     }
   1241 
   1242     private int getTestInstabilityRating(TestIdentifier testId) {
   1243         if (mTestInstabilityRatings.containsKey(testId)) {
   1244             return mTestInstabilityRatings.get(testId);
   1245         } else {
   1246             return 0;
   1247         }
   1248     }
   1249 
   1250     private void recordTestInstability(TestIdentifier testId) {
   1251         mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
   1252     }
   1253 
   1254     private void clearTestInstability(TestIdentifier testId) {
   1255         mTestInstabilityRatings.put(testId, 0);
   1256     }
   1257 
   1258     /**
   1259      * Executes all tests on the device.
   1260      */
   1261     private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1262         for (;;) {
   1263             TestBatch batch = selectRunBatch(mRemainingTests, null);
   1264 
   1265             if (batch == null) {
   1266                 break;
   1267             }
   1268 
   1269             runTestRunBatch(batch);
   1270         }
   1271     }
   1272 
   1273     /**
   1274      * Runs a TestBatch by either faking it or executing it on a device.
   1275      */
   1276     private void runTestRunBatch(TestBatch batch) throws DeviceNotAvailableException,
   1277             CapabilityQueryFailureException {
   1278         // prepare instance listener
   1279         mInstanceListerner.setCurrentConfig(batch.config);
   1280         for (TestIdentifier test : batch.tests) {
   1281             mInstanceListerner.setTestInstances(test, getTestRunConfigs(test));
   1282         }
   1283 
   1284         // execute only if config is executable, else fake results
   1285         if (isSupportedRunConfiguration(batch.config)) {
   1286             executeTestRunBatch(batch);
   1287         } else {
   1288             if (batch.config.isRequired()) {
   1289                 fakeFailTestRunBatch(batch);
   1290             } else {
   1291                 fakePassTestRunBatch(batch);
   1292             }
   1293         }
   1294     }
   1295 
   1296     private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig)
   1297             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1298         // orientation support
   1299         if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) {
   1300             final Set<String> features = getDeviceFeatures(mDevice);
   1301 
   1302             if (isPortraitClassRotation(runConfig.getRotation()) &&
   1303                     !features.contains(FEATURE_PORTRAIT)) {
   1304                 return false;
   1305             }
   1306             if (isLandscapeClassRotation(runConfig.getRotation()) &&
   1307                     !features.contains(FEATURE_LANDSCAPE)) {
   1308                 return false;
   1309             }
   1310         }
   1311 
   1312         if (isOpenGlEsPackage()) {
   1313             // renderability support for OpenGL ES tests
   1314             return isSupportedGlesRenderConfig(runConfig);
   1315         } else {
   1316             return true;
   1317         }
   1318     }
   1319 
   1320     private static final class AdbComLinkOpenError extends Exception {
   1321         public AdbComLinkOpenError(String description, Throwable inner) {
   1322             super(description, inner);
   1323         }
   1324     }
   1325 
   1326     private static final class AdbComLinkKilledError extends Exception {
   1327         public AdbComLinkKilledError(String description, Throwable inner) {
   1328             super(description, inner);
   1329         }
   1330     }
   1331 
   1332     /**
   1333      * Executes a given command in adb shell
   1334      *
   1335      * @throws AdbComLinkOpenError if connection cannot be established.
   1336      * @throws AdbComLinkKilledError if established connection is killed prematurely.
   1337      */
   1338     private void executeShellCommandAndReadOutput(final String command,
   1339             final IShellOutputReceiver receiver)
   1340             throws AdbComLinkOpenError, AdbComLinkKilledError {
   1341         try {
   1342             mDevice.getIDevice().executeShellCommand(command, receiver,
   1343                     UNRESPONSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
   1344         } catch (TimeoutException ex) {
   1345             // Opening connection timed out
   1346             throw new AdbComLinkOpenError("opening connection timed out", ex);
   1347         } catch (AdbCommandRejectedException ex) {
   1348             // Command rejected
   1349             throw new AdbComLinkOpenError("command rejected", ex);
   1350         } catch (IOException ex) {
   1351             // shell command channel killed
   1352             throw new AdbComLinkKilledError("command link killed", ex);
   1353         } catch (ShellCommandUnresponsiveException ex) {
   1354             // shell command halted
   1355             throw new AdbComLinkKilledError("command link hung", ex);
   1356         }
   1357     }
   1358 
   1359     /**
   1360      * Executes given test batch on a device
   1361      */
   1362     private void executeTestRunBatch(TestBatch batch) throws DeviceNotAvailableException {
   1363         // attempt full run once
   1364         executeTestRunBatchRun(batch);
   1365 
   1366         // split remaining tests to two sub batches and execute both. This will terminate
   1367         // since executeTestRunBatchRun will always progress for a batch of size 1.
   1368         final ArrayList<TestIdentifier> pendingTests = new ArrayList<>();
   1369 
   1370         for (TestIdentifier test : batch.tests) {
   1371             if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1372                 pendingTests.add(test);
   1373             }
   1374         }
   1375 
   1376         final int divisorNdx = pendingTests.size() / 2;
   1377         final List<TestIdentifier> headList = pendingTests.subList(0, divisorNdx);
   1378         final List<TestIdentifier> tailList = pendingTests.subList(divisorNdx, pendingTests.size());
   1379 
   1380         // head
   1381         for (;;) {
   1382             TestBatch subBatch = selectRunBatch(headList, batch.config);
   1383 
   1384             if (subBatch == null) {
   1385                 break;
   1386             }
   1387 
   1388             executeTestRunBatch(subBatch);
   1389         }
   1390 
   1391         // tail
   1392         for (;;) {
   1393             TestBatch subBatch = selectRunBatch(tailList, batch.config);
   1394 
   1395             if (subBatch == null) {
   1396                 break;
   1397             }
   1398 
   1399             executeTestRunBatch(subBatch);
   1400         }
   1401 
   1402         if (getBatchNumPendingCases(batch) != 0) {
   1403             throw new AssertionError("executeTestRunBatch postcondition failed");
   1404         }
   1405     }
   1406 
   1407     /**
   1408      * Runs one execution pass over the given batch.
   1409      *
   1410      * Tries to run the batch. Always makes progress (executes instances or modifies stability
   1411      * scores).
   1412      */
   1413     private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException {
   1414         if (getBatchNumPendingCases(batch) != batch.tests.size()) {
   1415             throw new AssertionError("executeTestRunBatchRun precondition failed");
   1416         }
   1417 
   1418         checkInterrupted(); // throws if interrupted
   1419 
   1420         final String testCases = generateTestCaseTrie(batch.tests);
   1421 
   1422         mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
   1423         mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
   1424         mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME);
   1425 
   1426         final String instrumentationName =
   1427                 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation";
   1428 
   1429         final StringBuilder deqpCmdLine = new StringBuilder();
   1430         deqpCmdLine.append("--deqp-caselist-file=");
   1431         deqpCmdLine.append(CASE_LIST_FILE_NAME);
   1432         deqpCmdLine.append(" ");
   1433         deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config));
   1434 
   1435         // If we are not logging data, do not bother outputting the images from the test exe.
   1436         if (!mLogData) {
   1437             deqpCmdLine.append(" --deqp-log-images=disable");
   1438         }
   1439 
   1440         deqpCmdLine.append(" --deqp-watchdog=enable");
   1441 
   1442         final String command = String.format(
   1443                 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\""
   1444                     + " -e deqpLogData \"%s\" %s",
   1445                 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(),
   1446                 mLogData, instrumentationName);
   1447 
   1448         final int numRemainingInstancesBefore = getNumRemainingInstances();
   1449         final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner);
   1450         Throwable interruptingError = null;
   1451 
   1452         try {
   1453             executeShellCommandAndReadOutput(command, parser);
   1454         } catch (Throwable ex) {
   1455             interruptingError = ex;
   1456         } finally {
   1457             parser.flush();
   1458         }
   1459 
   1460         final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null ||
   1461                 getNumRemainingInstances() < numRemainingInstancesBefore;
   1462 
   1463         if (progressedSinceLastCall) {
   1464             mDeviceRecovery.onExecutionProgressed();
   1465         }
   1466 
   1467         // interrupted, try to recover
   1468         if (interruptingError != null) {
   1469             if (interruptingError instanceof AdbComLinkOpenError) {
   1470                 mDeviceRecovery.recoverConnectionRefused();
   1471             } else if (interruptingError instanceof AdbComLinkKilledError) {
   1472                 mDeviceRecovery.recoverComLinkKilled();
   1473             } else if (interruptingError instanceof RunInterruptedException) {
   1474                 // external run interruption request. Terminate immediately.
   1475                 throw (RunInterruptedException)interruptingError;
   1476             } else {
   1477                 CLog.e(interruptingError);
   1478                 throw new RuntimeException(interruptingError);
   1479             }
   1480 
   1481             // recoverXXX did not throw => recovery succeeded
   1482         } else if (!parser.wasSuccessful()) {
   1483             mDeviceRecovery.recoverComLinkKilled();
   1484             // recoverXXX did not throw => recovery succeeded
   1485         }
   1486 
   1487         // Progress guarantees.
   1488         if (batch.tests.size() == 1) {
   1489             final TestIdentifier onlyTest = batch.tests.iterator().next();
   1490             final boolean wasTestExecuted =
   1491                     !mInstanceListerner.isPendingTestInstance(onlyTest, batch.config) &&
   1492                     mInstanceListerner.getCurrentTestId() == null;
   1493             final boolean wasLinkFailure = !parser.wasSuccessful() || interruptingError != null;
   1494 
   1495             // Link failures can be caused by external events, require at least two observations
   1496             // until bailing.
   1497             if (!wasTestExecuted && (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) {
   1498                 recordTestInstability(onlyTest);
   1499                 // If we cannot finish the test, mark the case as a crash.
   1500                 //
   1501                 // If we couldn't even start the test, fail the test instance as non-executable.
   1502                 // This is required so that a consistently crashing or non-existent tests will
   1503                 // not cause futile (non-terminating) re-execution attempts.
   1504                 if (mInstanceListerner.getCurrentTestId() != null) {
   1505                     mInstanceListerner.abortTest(onlyTest, INCOMPLETE_LOG_MESSAGE);
   1506                 } else {
   1507                     mInstanceListerner.abortTest(onlyTest, NOT_EXECUTABLE_LOG_MESSAGE);
   1508                 }
   1509             } else if (wasTestExecuted) {
   1510                 clearTestInstability(onlyTest);
   1511             }
   1512         }
   1513         else
   1514         {
   1515             // Analyze results to update test stability ratings. If there is no interrupting test
   1516             // logged, increase instability rating of all remaining tests. If there is a
   1517             // interrupting test logged, increase only its instability rating.
   1518             //
   1519             // A successful run of tests clears instability rating.
   1520             if (mInstanceListerner.getCurrentTestId() == null) {
   1521                 for (TestIdentifier test : batch.tests) {
   1522                     if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1523                         recordTestInstability(test);
   1524                     } else {
   1525                         clearTestInstability(test);
   1526                     }
   1527                 }
   1528             } else {
   1529                 recordTestInstability(mInstanceListerner.getCurrentTestId());
   1530                 for (TestIdentifier test : batch.tests) {
   1531                     // \note: isPendingTestInstance is false for getCurrentTestId. Current ID is
   1532                     // considered 'running' and will be restored to 'pending' in endBatch().
   1533                     if (!test.equals(mInstanceListerner.getCurrentTestId()) &&
   1534                             !mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1535                         clearTestInstability(test);
   1536                     }
   1537                 }
   1538             }
   1539         }
   1540 
   1541         mInstanceListerner.endBatch();
   1542     }
   1543 
   1544     private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) {
   1545         final StringBuilder deqpCmdLine = new StringBuilder();
   1546         if (!runConfig.getGlConfig().isEmpty()) {
   1547             deqpCmdLine.append("--deqp-gl-config-name=");
   1548             deqpCmdLine.append(runConfig.getGlConfig());
   1549         }
   1550         if (!runConfig.getRotation().isEmpty()) {
   1551             if (deqpCmdLine.length() != 0) {
   1552                 deqpCmdLine.append(" ");
   1553             }
   1554             deqpCmdLine.append("--deqp-screen-rotation=");
   1555             deqpCmdLine.append(runConfig.getRotation());
   1556         }
   1557         if (!runConfig.getSurfaceType().isEmpty()) {
   1558             if (deqpCmdLine.length() != 0) {
   1559                 deqpCmdLine.append(" ");
   1560             }
   1561             deqpCmdLine.append("--deqp-surface-type=");
   1562             deqpCmdLine.append(runConfig.getSurfaceType());
   1563         }
   1564         return deqpCmdLine.toString();
   1565     }
   1566 
   1567     private int getNumRemainingInstances() {
   1568         int retVal = 0;
   1569         for (TestIdentifier testId : mRemainingTests) {
   1570             // If case is in current working set, sum only not yet executed instances.
   1571             // If case is not in current working set, sum all instances (since they are not yet
   1572             // executed).
   1573             if (mInstanceListerner.mPendingResults.containsKey(testId)) {
   1574                 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size();
   1575             } else {
   1576                 retVal += mTestInstances.get(testId).size();
   1577             }
   1578         }
   1579         return retVal;
   1580     }
   1581 
   1582     /**
   1583      * Checks if this execution has been marked as interrupted and throws if it has.
   1584      */
   1585     private void checkInterrupted() throws RunInterruptedException {
   1586         // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly
   1587         // by sleeping a value <= 0.
   1588         mRunUtil.sleep(0);
   1589     }
   1590 
   1591     /**
   1592      * Pass given batch tests without running it
   1593      */
   1594     private void fakePassTestRunBatch(TestBatch batch) {
   1595         for (TestIdentifier test : batch.tests) {
   1596             CLog.d("Marking '%s' invocation in config '%s' as passed without running", test.toString(),
   1597                     batch.config.getId());
   1598             mInstanceListerner.skipTest(test);
   1599         }
   1600     }
   1601 
   1602     /**
   1603      * Fail given batch tests without running it
   1604      */
   1605     private void fakeFailTestRunBatch(TestBatch batch) {
   1606         for (TestIdentifier test : batch.tests) {
   1607             CLog.d("Marking '%s' invocation in config '%s' as failed without running", test.toString(),
   1608                     batch.config.getId());
   1609             mInstanceListerner.abortTest(test, "Required config not supported");
   1610         }
   1611     }
   1612 
   1613     /**
   1614      * Pass all remaining tests without running them
   1615      */
   1616     private void fakePassTests(ITestInvocationListener listener) {
   1617         Map <String, String> emptyMap = Collections.emptyMap();
   1618         for (TestIdentifier test : mRemainingTests) {
   1619             CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString());
   1620             listener.testStarted(test);
   1621             listener.testEnded(test, emptyMap);
   1622         }
   1623         mRemainingTests.clear();
   1624     }
   1625 
   1626     /**
   1627      * Check if device supports Vulkan.
   1628      */
   1629     private boolean isSupportedVulkan ()
   1630             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1631         final Set<String> features = getDeviceFeatures(mDevice);
   1632 
   1633         for (String feature : features) {
   1634             if (feature.startsWith(FEATURE_VULKAN_LEVEL)) {
   1635                 return true;
   1636             }
   1637         }
   1638 
   1639         return false;
   1640     }
   1641 
   1642     /**
   1643      * Check if device supports OpenGL ES version.
   1644      */
   1645     private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion,
   1646             int requiredMinorVersion) throws DeviceNotAvailableException {
   1647         String roOpenglesVersion = device.getProperty("ro.opengles.version");
   1648 
   1649         if (roOpenglesVersion == null)
   1650             return false;
   1651 
   1652         int intValue = Integer.parseInt(roOpenglesVersion);
   1653 
   1654         int majorVersion = ((intValue & 0xffff0000) >> 16);
   1655         int minorVersion = (intValue & 0xffff);
   1656 
   1657         return (majorVersion > requiredMajorVersion)
   1658                 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion);
   1659     }
   1660 
   1661     /**
   1662      * Query if rendertarget is supported
   1663      */
   1664     private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
   1665             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1666         // query if configuration is supported
   1667         final StringBuilder configCommandLine =
   1668                 new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
   1669         if (configCommandLine.length() != 0) {
   1670             configCommandLine.append(" ");
   1671         }
   1672         configCommandLine.append("--deqp-gl-major-version=");
   1673         configCommandLine.append(getGlesMajorVersion());
   1674         configCommandLine.append(" --deqp-gl-minor-version=");
   1675         configCommandLine.append(getGlesMinorVersion());
   1676 
   1677         final String commandLine = configCommandLine.toString();
   1678 
   1679         // check for cached result first
   1680         if (mConfigQuerySupportCache.containsKey(commandLine)) {
   1681             return mConfigQuerySupportCache.get(commandLine);
   1682         }
   1683 
   1684         final boolean supported = queryIsSupportedConfigCommandLine(commandLine);
   1685         mConfigQuerySupportCache.put(commandLine, supported);
   1686         return supported;
   1687     }
   1688 
   1689     private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
   1690             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1691         final String instrumentationName =
   1692                 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
   1693         final String command = String.format(
   1694                 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
   1695                     + " %s",
   1696                 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName);
   1697 
   1698         final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser();
   1699         mDevice.executeShellCommand(command, parser);
   1700         parser.flush();
   1701 
   1702         if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
   1703                 parser.getResultMap().containsKey("Supported")) {
   1704             if ("Yes".equals(parser.getResultMap().get("Supported"))) {
   1705                 return true;
   1706             } else if ("No".equals(parser.getResultMap().get("Supported"))) {
   1707                 return false;
   1708             } else {
   1709                 CLog.e("Capability query did not return a result");
   1710                 throw new CapabilityQueryFailureException();
   1711             }
   1712         } else if (parser.wasSuccessful()) {
   1713             CLog.e("Failed to run capability query. Code: %d, Result: %s",
   1714                     parser.getResultCode(), parser.getResultMap().toString());
   1715             throw new CapabilityQueryFailureException();
   1716         } else {
   1717             CLog.e("Failed to run capability query");
   1718             throw new CapabilityQueryFailureException();
   1719         }
   1720     }
   1721 
   1722     /**
   1723      * Return feature set supported by the device
   1724      */
   1725     private Set<String> getDeviceFeatures(ITestDevice device)
   1726             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1727         if (mDeviceFeatures == null) {
   1728             mDeviceFeatures = queryDeviceFeatures(device);
   1729         }
   1730         return mDeviceFeatures;
   1731     }
   1732 
   1733     /**
   1734      * Query feature set supported by the device
   1735      */
   1736     private static Set<String> queryDeviceFeatures(ITestDevice device)
   1737             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1738         // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
   1739         // TODO: Move this logic to ITestDevice.
   1740         String command = "pm list features";
   1741         String commandOutput = device.executeShellCommand(command);
   1742 
   1743         // Extract the id of the new user.
   1744         HashSet<String> availableFeatures = new HashSet<>();
   1745         for (String feature: commandOutput.split("\\s+")) {
   1746             // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
   1747             String[] tokens = feature.split(":");
   1748             if (tokens.length < 2 || !"feature".equals(tokens[0])) {
   1749                 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]);
   1750                 throw new CapabilityQueryFailureException();
   1751             }
   1752             availableFeatures.add(tokens[1]);
   1753         }
   1754         return availableFeatures;
   1755     }
   1756 
   1757     private boolean isPortraitClassRotation(String rotation) {
   1758         return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
   1759                 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
   1760     }
   1761 
   1762     private boolean isLandscapeClassRotation(String rotation) {
   1763         return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
   1764                 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
   1765     }
   1766 
   1767     /**
   1768      * Install dEQP OnDevice Package
   1769      */
   1770     private void installTestApk() throws DeviceNotAvailableException {
   1771         try {
   1772             File apkFile = new File(mBuildHelper.getTestsDir(), DEQP_ONDEVICE_APK);
   1773             String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
   1774             String errorCode = getDevice().installPackage(apkFile, true, options);
   1775             if (errorCode != null) {
   1776                 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode);
   1777             }
   1778         } catch (FileNotFoundException e) {
   1779             CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK);
   1780         }
   1781     }
   1782 
   1783     /**
   1784      * Uninstall dEQP OnDevice Package
   1785      */
   1786     private void uninstallTestApk() throws DeviceNotAvailableException {
   1787         getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
   1788     }
   1789 
   1790     /**
   1791      * Parse gl nature from package name
   1792      */
   1793     private boolean isOpenGlEsPackage() {
   1794         if ("dEQP-GLES2".equals(mDeqpPackage) || "dEQP-GLES3".equals(mDeqpPackage) ||
   1795                 "dEQP-GLES31".equals(mDeqpPackage)) {
   1796             return true;
   1797         } else if ("dEQP-EGL".equals(mDeqpPackage) ||
   1798                 "dEQP-VK".equals(mDeqpPackage)) {
   1799             return false;
   1800         } else {
   1801             throw new IllegalStateException("dEQP runner was created with illegal name");
   1802         }
   1803     }
   1804 
   1805     /**
   1806      * Parse vulkan nature from package name
   1807      */
   1808     private boolean isVulkanPackage() {
   1809         if ("dEQP-GLES2".equals(mDeqpPackage) || "dEQP-GLES3".equals(mDeqpPackage) ||
   1810                 "dEQP-GLES31".equals(mDeqpPackage) || "dEQP-EGL".equals(mDeqpPackage)) {
   1811             return false;
   1812         } else if ("dEQP-VK".equals(mDeqpPackage)) {
   1813             return true;
   1814         } else {
   1815             throw new IllegalStateException("dEQP runner was created with illegal name");
   1816         }
   1817     }
   1818 
   1819     /**
   1820      * Check GL support (based on package name)
   1821      */
   1822     private boolean isSupportedGles() throws DeviceNotAvailableException {
   1823         return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion());
   1824     }
   1825 
   1826     /**
   1827      * Get GL major version (based on package name)
   1828      */
   1829     private int getGlesMajorVersion() {
   1830         if ("dEQP-GLES2".equals(mDeqpPackage)) {
   1831             return 2;
   1832         } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
   1833             return 3;
   1834         } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
   1835             return 3;
   1836         } else {
   1837             throw new IllegalStateException("getGlesMajorVersion called for non gles pkg");
   1838         }
   1839     }
   1840 
   1841     /**
   1842      * Get GL minor version (based on package name)
   1843      */
   1844     private int getGlesMinorVersion() {
   1845         if ("dEQP-GLES2".equals(mDeqpPackage)) {
   1846             return 0;
   1847         } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
   1848             return 0;
   1849         } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
   1850             return 1;
   1851         } else {
   1852             throw new IllegalStateException("getGlesMinorVersion called for non gles pkg");
   1853         }
   1854     }
   1855 
   1856     private static List<Pattern> getPatternFilters(List<String> filters) {
   1857         List<Pattern> patterns = new ArrayList<Pattern>();
   1858         for (String filter : filters) {
   1859             if (filter.contains("*")) {
   1860                 patterns.add(Pattern.compile(filter.replace(".","\\.").replace("*",".*")));
   1861             }
   1862         }
   1863         return patterns;
   1864     }
   1865 
   1866     private static Set<String> getNonPatternFilters(List<String> filters) {
   1867         Set<String> nonPatternFilters = new HashSet<String>();
   1868         for (String filter : filters) {
   1869             if (!filter.contains("*")) {
   1870                 // Deqp usesly only dots for separating between parts of the names
   1871                 // Convert last dot to hash if needed.
   1872                 if (!filter.contains("#")) {
   1873                     int lastSeparator = filter.lastIndexOf('.');
   1874                     String filterWithHash = filter.substring(0, lastSeparator) + "#" +
   1875                         filter.substring(lastSeparator + 1, filter.length());
   1876                     nonPatternFilters.add(filterWithHash);
   1877                 }
   1878                 else {
   1879                     nonPatternFilters.add(filter);
   1880                 }
   1881             }
   1882         }
   1883         return nonPatternFilters;
   1884     }
   1885 
   1886     private static boolean matchesAny(TestIdentifier test, List<Pattern> patterns) {
   1887         for (Pattern pattern : patterns) {
   1888             if (pattern.matcher(test.toString()).matches()) {
   1889                 return true;
   1890             }
   1891         }
   1892         return false;
   1893     }
   1894 
   1895     /**
   1896      * Filter tests with the option of filtering by pattern.
   1897      *
   1898      * '*' is 0 or more characters.
   1899      * '.' is interpreted verbatim.
   1900      */
   1901     private static void filterTests(Map<TestIdentifier, Set<BatchRunConfiguration>> tests,
   1902                                     List<String> includeFilters,
   1903                                     List<String> excludeFilters) {
   1904         // We could filter faster by building the test case tree.
   1905         // Let's see if this is fast enough.
   1906         Set<String> includeStrings = getNonPatternFilters(includeFilters);
   1907         Set<String> excludeStrings = getNonPatternFilters(excludeFilters);
   1908         List<Pattern> includePatterns = getPatternFilters(includeFilters);
   1909         List<Pattern> excludePatterns = getPatternFilters(excludeFilters);
   1910 
   1911         List<TestIdentifier> testList = new ArrayList<>(tests.keySet());
   1912         for (TestIdentifier test : testList) {
   1913             if (excludeStrings.contains(test.toString())) {
   1914                 tests.remove(test); // remove test if explicitly excluded
   1915                 continue;
   1916             }
   1917             boolean includesExist = !includeStrings.isEmpty() || !includePatterns.isEmpty();
   1918             boolean testIsIncluded = includeStrings.contains(test.toString())
   1919                     || matchesAny(test, includePatterns);
   1920             if ((includesExist && !testIsIncluded) || matchesAny(test, excludePatterns)) {
   1921                 // if this test isn't included and other tests are,
   1922                 // or if test matches exclude pattern, exclude test
   1923                 tests.remove(test);
   1924             }
   1925         }
   1926     }
   1927 
   1928     /**
   1929      * Read a list of filters from a file.
   1930      *
   1931      * Note: Filters can be numerous so we prefer, for performance
   1932      * reasons, to add directly to the target list instead of using
   1933      * intermediate return value.
   1934      */
   1935     static private void readFilterFile(List<String> filterList, File file) throws FileNotFoundException {
   1936         if (!file.canRead()) {
   1937             CLog.e("Failed to read filter file '%s'", file.getPath());
   1938             throw new FileNotFoundException();
   1939         }
   1940         try (Reader plainReader = new FileReader(file);
   1941              BufferedReader reader = new BufferedReader(plainReader)) {
   1942             String filter = "";
   1943             while ((filter = reader.readLine()) != null) {
   1944                 // TOOD: Sanity check filter
   1945                 filterList.add(filter);
   1946             }
   1947             // Rely on try block to autoclose
   1948         }
   1949         catch (IOException e)
   1950         {
   1951             throw new RuntimeException("Failed to read filter list file '" + file.getPath() + "': " +
   1952                      e.getMessage());
   1953         }
   1954     }
   1955 
   1956     /**
   1957      * Prints filters into debug log stream, limiting to 20 entries.
   1958      */
   1959     static private void printFilters(List<String> filters) {
   1960         int numPrinted = 0;
   1961         for (String filter : filters) {
   1962             CLog.d("    %s", filter);
   1963             if (++numPrinted == 20) {
   1964                 CLog.d("    ... AND %d others", filters.size() - numPrinted);
   1965                 break;
   1966             }
   1967         }
   1968     }
   1969 
   1970     /**
   1971      * Loads tests into mTestInstances based on the options. Assumes
   1972      * that no tests have been loaded for this instance before.
   1973      */
   1974     private void loadTests() {
   1975         if (mTestInstances != null) throw new AssertionError("Re-load of tests not supported");
   1976 
   1977         try {
   1978             Reader reader = mCaselistReader;
   1979             if (reader == null) {
   1980                 File testlist = new File(mBuildHelper.getTestsDir(), mCaselistFile);
   1981                 if (!testlist.isFile()) {
   1982                     throw new FileNotFoundException();
   1983                 }
   1984                 reader = new FileReader(testlist);
   1985             }
   1986             mTestInstances = generateTestInstances(reader, mConfigName, mScreenRotation, mSurfaceType, mConfigRequired);
   1987             mCaselistReader = null;
   1988             reader.close();
   1989         }
   1990         catch (FileNotFoundException e) {
   1991             throw new RuntimeException("Cannot read deqp test list file: "  + mCaselistFile);
   1992         }
   1993         catch (IOException e) {
   1994             CLog.w("Failed to close test list reader.");
   1995         }
   1996 
   1997         try
   1998         {
   1999             for (String filterFile : mIncludeFilterFiles) {
   2000                 CLog.d("Read include filter file '%s'", filterFile);
   2001                 File file = new File(mBuildHelper.getTestsDir(), filterFile);
   2002                 readFilterFile(mIncludeFilters, file);
   2003             }
   2004             for (String filterFile : mExcludeFilterFiles) {
   2005                 CLog.d("Read exclude filter file '%s'", filterFile);
   2006                 File file = new File(mBuildHelper.getTestsDir(), filterFile);
   2007                 readFilterFile(mExcludeFilters, file);
   2008             }
   2009         }
   2010         catch (FileNotFoundException e) {
   2011             throw new RuntimeException("Cannot read deqp filter list file:" + e.getMessage());
   2012         }
   2013 
   2014         CLog.d("Include filters:");
   2015         printFilters(mIncludeFilters);
   2016         CLog.d("Exclude filters:");
   2017         printFilters(mExcludeFilters);
   2018 
   2019         long originalTestCount = mTestInstances.size();
   2020         CLog.i("Num tests before filtering: %d", originalTestCount);
   2021         if ((!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) && originalTestCount > 0) {
   2022             filterTests(mTestInstances, mIncludeFilters, mExcludeFilters);
   2023 
   2024             // Update runtime estimation hint.
   2025             if (mRuntimeHint != -1) {
   2026                 mRuntimeHint = (mRuntimeHint * mTestInstances.size()) / originalTestCount;
   2027             }
   2028         }
   2029         CLog.i("Num tests after filtering: %d", mTestInstances.size());
   2030     }
   2031 
   2032     /**
   2033      * {@inheritDoc}
   2034      */
   2035     @Override
   2036     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
   2037         final Map<String, String> emptyMap = Collections.emptyMap();
   2038         // If sharded, split() will load the tests.
   2039         if (mTestInstances == null) {
   2040             loadTests();
   2041         }
   2042 
   2043         mRemainingTests = new LinkedList<>(mTestInstances.keySet());
   2044         long startTime = System.currentTimeMillis();
   2045         listener.testRunStarted(getId(), mRemainingTests.size());
   2046 
   2047         try {
   2048             if (mRemainingTests.isEmpty()) {
   2049                 CLog.d("No tests to run.");
   2050                 return;
   2051             }
   2052             final boolean isSupportedApi = (isOpenGlEsPackage() && isSupportedGles())
   2053                                             || (isVulkanPackage() && isSupportedVulkan())
   2054                                             || (!isOpenGlEsPackage() && !isVulkanPackage());
   2055 
   2056             if (!isSupportedApi || mCollectTestsOnly) {
   2057                 // Pass all tests if OpenGL ES version is not supported or we are collecting
   2058                 // the names of the tests only
   2059                 fakePassTests(listener);
   2060             } else if (!mRemainingTests.isEmpty()) {
   2061                 // Make sure there is no pre-existing package form earlier interrupted test run.
   2062                 uninstallTestApk();
   2063                 installTestApk();
   2064 
   2065                 mInstanceListerner.setSink(listener);
   2066                 mDeviceRecovery.setDevice(mDevice);
   2067                 runTests();
   2068 
   2069                 uninstallTestApk();
   2070             }
   2071         } catch (CapabilityQueryFailureException ex) {
   2072             // Platform is not behaving correctly, for example crashing when trying to create
   2073             // a window. Instead of silenty failing, signal failure by leaving the rest of the
   2074             // test cases in "NotExecuted" state
   2075             CLog.e("Capability query failed - leaving tests unexecuted.");
   2076             uninstallTestApk();
   2077         } finally {
   2078             listener.testRunEnded(System.currentTimeMillis() - startTime, emptyMap);
   2079         }
   2080     }
   2081 
   2082    /**
   2083      * {@inheritDoc}
   2084      */
   2085     @Override
   2086     public void addIncludeFilter(String filter) {
   2087         mIncludeFilters.add(filter);
   2088     }
   2089 
   2090     /**
   2091      * {@inheritDoc}
   2092      */
   2093     @Override
   2094     public void addAllIncludeFilters(Set<String> filters) {
   2095         mIncludeFilters.addAll(filters);
   2096     }
   2097 
   2098     /**
   2099      * {@inheritDoc}
   2100      */
   2101     @Override
   2102     public void addExcludeFilter(String filter) {
   2103         mExcludeFilters.add(filter);
   2104     }
   2105 
   2106     /**
   2107      * {@inheritDoc}
   2108      */
   2109     @Override
   2110     public void addAllExcludeFilters(Set<String> filters) {
   2111         mExcludeFilters.addAll(filters);
   2112     }
   2113 
   2114     /**
   2115      * {@inheritDoc}
   2116      */
   2117     @Override
   2118     public void setCollectTestsOnly(boolean collectTests) {
   2119         mCollectTestsOnly = collectTests;
   2120     }
   2121 
   2122     private static void copyOptions(DeqpTestRunner destination, DeqpTestRunner source) {
   2123         destination.mDeqpPackage = source.mDeqpPackage;
   2124         destination.mConfigName = source.mConfigName;
   2125         destination.mCaselistFile = source.mCaselistFile;
   2126         destination.mScreenRotation = source.mScreenRotation;
   2127         destination.mSurfaceType = source.mSurfaceType;
   2128         destination.mConfigRequired = source.mConfigRequired;
   2129         destination.mIncludeFilters = new ArrayList<>(source.mIncludeFilters);
   2130         destination.mIncludeFilterFiles = new ArrayList<>(source.mIncludeFilterFiles);
   2131         destination.mExcludeFilters = new ArrayList<>(source.mExcludeFilters);
   2132         destination.mExcludeFilterFiles = new ArrayList<>(source.mExcludeFilterFiles);
   2133         destination.mAbi = source.mAbi;
   2134         destination.mLogData = source.mLogData;
   2135         destination.mCollectTestsOnly = source.mCollectTestsOnly;
   2136     }
   2137 
   2138     /**
   2139      * Helper to update the RuntimeHint of the tests after being sharded.
   2140      */
   2141     private void updateRuntimeHint(long originalSize, Collection<IRemoteTest> runners) {
   2142         if (originalSize > 0) {
   2143             long fullRuntimeMs = getRuntimeHint();
   2144             for (IRemoteTest remote: runners) {
   2145                 DeqpTestRunner runner = (DeqpTestRunner)remote;
   2146                 long shardRuntime = (fullRuntimeMs * runner.mTestInstances.size()) / originalSize;
   2147                 runner.mRuntimeHint = shardRuntime;
   2148             }
   2149         }
   2150     }
   2151 
   2152     /**
   2153      * {@inheritDoc}
   2154      */
   2155     @Override
   2156     public Collection<IRemoteTest> split() {
   2157         if (mTestInstances != null) {
   2158             throw new AssertionError("Re-splitting or splitting running instance?");
   2159         }
   2160         // \todo [2015-11-23 kalle] If we split to batches at shard level, we could
   2161         // basically get rid of batching. Except that sharding is optional?
   2162 
   2163         // Assume that tests have not been yet loaded.
   2164         loadTests();
   2165 
   2166         Collection<IRemoteTest> runners = new ArrayList<>();
   2167         // NOTE: Use linked hash map to keep the insertion order in iteration
   2168         Map<TestIdentifier, Set<BatchRunConfiguration>> currentSet = new LinkedHashMap<>();
   2169         Map<TestIdentifier, Set<BatchRunConfiguration>> iterationSet = this.mTestInstances;
   2170 
   2171         if (iterationSet.keySet().isEmpty()) {
   2172             CLog.i("Cannot split deqp tests, no tests to run");
   2173             return null;
   2174         }
   2175 
   2176         // Go through tests, split
   2177         for (TestIdentifier test: iterationSet.keySet()) {
   2178             currentSet.put(test, iterationSet.get(test));
   2179             if (currentSet.size() >= TESTCASE_BATCH_LIMIT) {
   2180                 runners.add(new DeqpTestRunner(this, currentSet));
   2181                 // NOTE: Use linked hash map to keep the insertion order in iteration
   2182                 currentSet = new LinkedHashMap<>();
   2183             }
   2184         }
   2185         runners.add(new DeqpTestRunner(this, currentSet));
   2186 
   2187         // Compute new runtime hints
   2188         updateRuntimeHint(iterationSet.size(), runners);
   2189         CLog.i("Split deqp tests into %d shards", runners.size());
   2190         return runners;
   2191     }
   2192 
   2193     /**
   2194      * This sharding should be deterministic for the same input and independent.
   2195      * Through this API, each shard could be executed on different machine.
   2196      */
   2197     @Override
   2198     public IRemoteTest getTestShard(int shardCount, int shardIndex) {
   2199         // TODO: refactor getTestshard and split to share some logic.
   2200         if (mTestInstances == null) {
   2201             loadTests();
   2202         }
   2203 
   2204         List<IRemoteTest> runners = new ArrayList<>();
   2205         // NOTE: Use linked hash map to keep the insertion order in iteration
   2206         Map<TestIdentifier, Set<BatchRunConfiguration>> currentSet = new LinkedHashMap<>();
   2207         Map<TestIdentifier, Set<BatchRunConfiguration>> iterationSet = this.mTestInstances;
   2208 
   2209         int batchLimit = iterationSet.keySet().size() / shardCount;
   2210         int i = 1;
   2211         // Go through tests, split
   2212         for (TestIdentifier test: iterationSet.keySet()) {
   2213             currentSet.put(test, iterationSet.get(test));
   2214             if (currentSet.size() >= batchLimit && i < shardCount) {
   2215                 runners.add(new DeqpTestRunner(this, currentSet));
   2216                 i++;
   2217                 // NOTE: Use linked hash map to keep the insertion order in iteration
   2218                 currentSet = new LinkedHashMap<>();
   2219             }
   2220         }
   2221         runners.add(new DeqpTestRunner(this, currentSet));
   2222 
   2223         // Compute new runtime hints
   2224         updateRuntimeHint(iterationSet.size(), runners);
   2225 
   2226         // If too many shards were requested, we complete with placeholder.
   2227         if (runners.size() < shardCount) {
   2228             for (int j = runners.size(); j < shardCount; j++) {
   2229                 runners.add(new DeqpTestRunner(this,
   2230                         new LinkedHashMap<TestIdentifier, Set<BatchRunConfiguration>>()));
   2231             }
   2232         }
   2233 
   2234         CLog.i("Split deqp tests into %d shards, return shard: %s", runners.size(), shardIndex);
   2235         return runners.get(shardIndex);
   2236     }
   2237 
   2238     /**
   2239      * {@inheritDoc}
   2240      */
   2241     @Override
   2242     public long getRuntimeHint() {
   2243         if (mRuntimeHint != -1) {
   2244             return mRuntimeHint;
   2245         }
   2246         if (mTestInstances == null) {
   2247             loadTests();
   2248         }
   2249         // Tests normally take something like ~100ms. Some take a
   2250         // second. Let's guess 200ms per test.
   2251         return 200 * mTestInstances.size();
   2252     }
   2253 }
   2254