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