Home | History | Annotate | Download | only in testtype
      1 package com.android.cts.tradefed.testtype;
      2 
      3 import com.android.cts.tradefed.build.CtsBuildHelper;
      4 import com.android.cts.util.AbiUtils;
      5 import com.android.ddmlib.AdbCommandRejectedException;
      6 import com.android.ddmlib.IShellOutputReceiver;
      7 import com.android.ddmlib.MultiLineReceiver;
      8 import com.android.ddmlib.ShellCommandUnresponsiveException;
      9 import com.android.ddmlib.TimeoutException;
     10 import com.android.ddmlib.testrunner.TestIdentifier;
     11 import com.android.tradefed.build.IBuildInfo;
     12 import com.android.tradefed.device.DeviceNotAvailableException;
     13 import com.android.tradefed.device.ITestDevice;
     14 import com.android.tradefed.log.LogUtil.CLog;
     15 import com.android.tradefed.result.ByteArrayInputStreamSource;
     16 import com.android.tradefed.result.ITestInvocationListener;
     17 import com.android.tradefed.result.LogDataType;
     18 import com.android.tradefed.testtype.IAbi;
     19 import com.android.tradefed.testtype.IBuildReceiver;
     20 import com.android.tradefed.testtype.IDeviceTest;
     21 import com.android.tradefed.testtype.IRemoteTest;
     22 import com.android.tradefed.util.IRunUtil;
     23 import com.android.tradefed.util.RunInterruptedException;
     24 import com.android.tradefed.util.RunUtil;
     25 
     26 import java.io.File;
     27 import java.io.FileNotFoundException;
     28 import java.io.IOException;
     29 import java.lang.reflect.Method;
     30 import java.lang.reflect.InvocationTargetException;
     31 import java.util.ArrayList;
     32 import java.util.Collection;
     33 import java.util.Collections;
     34 import java.util.HashMap;
     35 import java.util.HashSet;
     36 import java.util.Iterator;
     37 import java.util.LinkedHashMap;
     38 import java.util.LinkedHashSet;
     39 import java.util.LinkedList;
     40 import java.util.List;
     41 import java.util.Map;
     42 import java.util.Set;
     43 import java.util.concurrent.TimeUnit;
     44 
     45 /**
     46  * Test runner for dEQP tests
     47  *
     48  * Supports running drawElements Quality Program tests found under external/deqp.
     49  */
     50 public class DeqpTestRunner implements IBuildReceiver, IDeviceTest, IRemoteTest {
     51 
     52     private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk";
     53     private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp";
     54     private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log";
     55     private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped";
     56     private static final String NOT_EXECUTABLE_LOG_MESSAGE = "Abort: Test cannot be executed";
     57     private static final String CASE_LIST_FILE_NAME = "/sdcard/dEQP-TestCaseList.txt";
     58     private static final String LOG_FILE_NAME = "/sdcard/TestLog.qpa";
     59     public static final String FEATURE_LANDSCAPE = "android.hardware.screen.landscape";
     60     public static final String FEATURE_PORTRAIT = "android.hardware.screen.portrait";
     61 
     62     private static final int TESTCASE_BATCH_LIMIT = 1000;
     63     private static final BatchRunConfiguration DEFAULT_CONFIG =
     64         new BatchRunConfiguration("rgba8888d24s8", "unspecified", "window");
     65 
     66     private static final int UNRESPOSIVE_CMD_TIMEOUT_MS = 60000; // one minute
     67 
     68     private final String mPackageName;
     69     private final String mName;
     70     private final Collection<TestIdentifier> mRemainingTests;
     71     private final Map<TestIdentifier, Set<BatchRunConfiguration>> mTestInstances;
     72     private final TestInstanceResultListener mInstanceListerner = new TestInstanceResultListener();
     73     private final Map<TestIdentifier, Integer> mTestInstabilityRatings;
     74     private IAbi mAbi;
     75     private CtsBuildHelper mCtsBuild;
     76     private boolean mLogData = false;
     77     private ITestDevice mDevice;
     78     private Set<String> mDeviceFeatures;
     79     private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
     80     private IRunUtil mRunUtil = RunUtil.getDefault();
     81 
     82     private IRecovery mDeviceRecovery = new Recovery();
     83     {
     84         mDeviceRecovery.setSleepProvider(new SleepProvider());
     85     }
     86 
     87     public DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests,
     88             Map<TestIdentifier, List<Map<String,String>>> testInstances) {
     89         mPackageName = packageName;
     90         mName = name;
     91         mRemainingTests = new LinkedList<>(tests); // avoid modifying arguments
     92         mTestInstances = parseTestInstances(tests, testInstances);
     93         mTestInstabilityRatings = new HashMap<>();
     94     }
     95 
     96     /**
     97      * @param abi the ABI to run the test on
     98      */
     99     public void setAbi(IAbi abi) {
    100         mAbi = abi;
    101     }
    102 
    103     /**
    104      * {@inheritDoc}
    105      */
    106     @Override
    107     public void setBuild(IBuildInfo buildInfo) {
    108         mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
    109     }
    110 
    111     /**
    112      * Set the CTS build container.
    113      * <p/>
    114      * Exposed so unit tests can mock the provided build.
    115      *
    116      * @param buildHelper
    117      */
    118     public void setBuildHelper(CtsBuildHelper buildHelper) {
    119         mCtsBuild = buildHelper;
    120     }
    121 
    122     /**
    123      * Enable or disable raw dEQP test log collection.
    124      */
    125     public void setCollectLogs(boolean logData) {
    126         mLogData = logData;
    127     }
    128 
    129     /**
    130      * {@inheritDoc}
    131      */
    132     @Override
    133     public void setDevice(ITestDevice device) {
    134         mDevice = device;
    135     }
    136 
    137     /**
    138      * {@inheritDoc}
    139      */
    140     @Override
    141     public ITestDevice getDevice() {
    142         return mDevice;
    143     }
    144 
    145     /**
    146      * Set recovery handler.
    147      *
    148      * Exposed for unit testing.
    149      */
    150     public void setRecovery(IRecovery deviceRecovery) {
    151         mDeviceRecovery = deviceRecovery;
    152     }
    153 
    154     /**
    155      * Set IRunUtil.
    156      *
    157      * Exposed for unit testing.
    158      */
    159     public void setRunUtil(IRunUtil runUtil) {
    160         mRunUtil = runUtil;
    161     }
    162 
    163     private static final class CapabilityQueryFailureException extends Exception {
    164     };
    165 
    166     /**
    167      * Test configuration of dEPQ test instance execution.
    168      * Exposed for unit testing
    169      */
    170     public static final class BatchRunConfiguration {
    171         public static final String ROTATION_UNSPECIFIED = "unspecified";
    172         public static final String ROTATION_PORTRAIT = "0";
    173         public static final String ROTATION_LANDSCAPE = "90";
    174         public static final String ROTATION_REVERSE_PORTRAIT = "180";
    175         public static final String ROTATION_REVERSE_LANDSCAPE = "270";
    176 
    177         private final String mGlConfig;
    178         private final String mRotation;
    179         private final String mSurfaceType;
    180 
    181         public BatchRunConfiguration(String glConfig, String rotation, String surfaceType) {
    182             mGlConfig = glConfig;
    183             mRotation = rotation;
    184             mSurfaceType = surfaceType;
    185         }
    186 
    187         /**
    188          * Get string that uniquely identifies this config
    189          */
    190         public String getId() {
    191             return String.format("{glformat=%s,rotation=%s,surfacetype=%s}",
    192                     mGlConfig, mRotation, mSurfaceType);
    193         }
    194 
    195         /**
    196          * Get the GL config used in this configuration.
    197          */
    198         public String getGlConfig() {
    199             return mGlConfig;
    200         }
    201 
    202         /**
    203          * Get the screen rotation used in this configuration.
    204          */
    205         public String getRotation() {
    206             return mRotation;
    207         }
    208 
    209         /**
    210          * Get the surface type used in this configuration.
    211          */
    212         public String getSurfaceType() {
    213             return mSurfaceType;
    214         }
    215 
    216         @Override
    217         public boolean equals(Object other) {
    218             if (other == null) {
    219                 return false;
    220             } else if (!(other instanceof BatchRunConfiguration)) {
    221                 return false;
    222             } else {
    223                 return getId().equals(((BatchRunConfiguration)other).getId());
    224             }
    225         }
    226 
    227         @Override
    228         public int hashCode() {
    229             return getId().hashCode();
    230         }
    231     }
    232 
    233     /**
    234      * dEQP test instance listerer and invocation result forwarded
    235      */
    236     private class TestInstanceResultListener {
    237         private ITestInvocationListener mSink;
    238         private BatchRunConfiguration mRunConfig;
    239 
    240         private TestIdentifier mCurrentTestId;
    241         private boolean mGotTestResult;
    242         private String mCurrentTestLog;
    243 
    244         private class PendingResult
    245         {
    246             boolean allInstancesPassed;
    247             Map<BatchRunConfiguration, String> testLogs;
    248             Map<BatchRunConfiguration, String> errorMessages;
    249             Set<BatchRunConfiguration> remainingConfigs;
    250         };
    251         private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>();
    252 
    253         public void setSink(ITestInvocationListener sink) {
    254             mSink = sink;
    255         }
    256 
    257         public void setCurrentConfig(BatchRunConfiguration runConfig) {
    258             mRunConfig = runConfig;
    259         }
    260 
    261         /**
    262          * Get currently processed test id, or null if not currently processing a test case
    263          */
    264         public TestIdentifier getCurrentTestId() {
    265             return mCurrentTestId;
    266         }
    267 
    268         /**
    269          * Forward result to sink
    270          */
    271         private void forwardFinalizedPendingResult(TestIdentifier testId) {
    272             if (mRemainingTests.contains(testId)) {
    273                 final PendingResult result = mPendingResults.get(testId);
    274 
    275                 mPendingResults.remove(testId);
    276                 mRemainingTests.remove(testId);
    277 
    278                 // Forward results to the sink
    279                 mSink.testStarted(testId);
    280 
    281                 // Test Log
    282                 if (mLogData) {
    283                     for (Map.Entry<BatchRunConfiguration, String> entry :
    284                             result.testLogs.entrySet()) {
    285                         final ByteArrayInputStreamSource source
    286                                 = new ByteArrayInputStreamSource(entry.getValue().getBytes());
    287 
    288                         mSink.testLog(testId.getClassName() + "." + testId.getTestName() + "@"
    289                                 + entry.getKey().getId(), LogDataType.XML, source);
    290 
    291                         source.cancel();
    292                     }
    293                 }
    294 
    295                 // Error message
    296                 if (!result.allInstancesPassed) {
    297                     final StringBuilder errorLog = new StringBuilder();
    298 
    299                     for (Map.Entry<BatchRunConfiguration, String> entry :
    300                             result.errorMessages.entrySet()) {
    301                         if (errorLog.length() > 0) {
    302                             errorLog.append('\n');
    303                         }
    304                         errorLog.append(String.format("=== with config %s ===\n",
    305                                 entry.getKey().getId()));
    306                         errorLog.append(entry.getValue());
    307                     }
    308 
    309                     mSink.testFailed(testId, errorLog.toString());
    310                 }
    311 
    312                 final Map<String, String> emptyMap = Collections.emptyMap();
    313                 mSink.testEnded(testId, emptyMap);
    314             }
    315         }
    316 
    317         /**
    318          * Declare existence of a test and instances
    319          */
    320         public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) {
    321             // Test instances cannot change at runtime, ignore if we have already set this
    322             if (!mPendingResults.containsKey(testId)) {
    323                 final PendingResult pendingResult = new PendingResult();
    324                 pendingResult.allInstancesPassed = true;
    325                 pendingResult.testLogs = new LinkedHashMap<>();
    326                 pendingResult.errorMessages = new LinkedHashMap<>();
    327                 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument
    328                 mPendingResults.put(testId, pendingResult);
    329             }
    330         }
    331 
    332         /**
    333          * Query if test instance has not yet been executed
    334          */
    335         public boolean isPendingTestInstance(TestIdentifier testId,
    336                 BatchRunConfiguration config) {
    337             final PendingResult result = mPendingResults.get(testId);
    338             if (result == null) {
    339                 // test is not in the current working batch of the runner, i.e. it cannot be
    340                 // "partially" completed.
    341                 if (!mRemainingTests.contains(testId)) {
    342                     // The test has been fully executed. Not pending.
    343                     return false;
    344                 } else {
    345                     // Test has not yet been executed. Check if such instance exists
    346                     return mTestInstances.get(testId).contains(config);
    347                 }
    348             } else {
    349                 // could be partially completed, check this particular config
    350                 return result.remainingConfigs.contains(config);
    351             }
    352         }
    353 
    354         /**
    355          * Fake execution of an instance with current config
    356          */
    357         public void skipTest(TestIdentifier testId) {
    358             final PendingResult result = mPendingResults.get(testId);
    359 
    360             result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE);
    361             result.remainingConfigs.remove(mRunConfig);
    362 
    363             // Pending result finished, report result
    364             if (result.remainingConfigs.isEmpty()) {
    365                 forwardFinalizedPendingResult(testId);
    366             }
    367         }
    368 
    369         /**
    370          * Fake failure of an instance with current config
    371          */
    372         public void abortTest(TestIdentifier testId, String errorMessage) {
    373             final PendingResult result = mPendingResults.get(testId);
    374 
    375             // Mark as executed
    376             result.allInstancesPassed = false;
    377             result.errorMessages.put(mRunConfig, errorMessage);
    378             result.remainingConfigs.remove(mRunConfig);
    379 
    380             // Pending result finished, report result
    381             if (result.remainingConfigs.isEmpty()) {
    382                 forwardFinalizedPendingResult(testId);
    383             }
    384 
    385             if (testId.equals(mCurrentTestId)) {
    386                 mCurrentTestId = null;
    387             }
    388         }
    389 
    390         /**
    391          * Handles beginning of dEQP session.
    392          */
    393         private void handleBeginSession(Map<String, String> values) {
    394             // ignore
    395         }
    396 
    397         /**
    398          * Handles end of dEQP session.
    399          */
    400         private void handleEndSession(Map<String, String> values) {
    401             // ignore
    402         }
    403 
    404         /**
    405          * Handles beginning of dEQP testcase.
    406          */
    407         private void handleBeginTestCase(Map<String, String> values) {
    408             mCurrentTestId = pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath"));
    409             mCurrentTestLog = "";
    410             mGotTestResult = false;
    411 
    412             // mark instance as started
    413             if (mPendingResults.get(mCurrentTestId) != null) {
    414                 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig);
    415             } else {
    416                 CLog.w("Got unexpected start of %s", mCurrentTestId);
    417             }
    418         }
    419 
    420         /**
    421          * Handles end of dEQP testcase.
    422          */
    423         private void handleEndTestCase(Map<String, String> values) {
    424             final PendingResult result = mPendingResults.get(mCurrentTestId);
    425 
    426             if (result != null) {
    427                 if (!mGotTestResult) {
    428                     result.allInstancesPassed = false;
    429                     result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE);
    430                 }
    431 
    432                 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) {
    433                     result.testLogs.put(mRunConfig, mCurrentTestLog);
    434                 }
    435 
    436                 // Pending result finished, report result
    437                 if (result.remainingConfigs.isEmpty()) {
    438                     forwardFinalizedPendingResult(mCurrentTestId);
    439                 }
    440             } else {
    441                 CLog.w("Got unexpected end of %s", mCurrentTestId);
    442             }
    443             mCurrentTestId = null;
    444         }
    445 
    446         /**
    447          * Handles dEQP testcase result.
    448          */
    449         private void handleTestCaseResult(Map<String, String> values) {
    450             String code = values.get("dEQP-TestCaseResult-Code");
    451             String details = values.get("dEQP-TestCaseResult-Details");
    452 
    453             if (mPendingResults.get(mCurrentTestId) == null) {
    454                 CLog.w("Got unexpected result for %s", mCurrentTestId);
    455                 mGotTestResult = true;
    456                 return;
    457             }
    458 
    459             if (code.compareTo("Pass") == 0) {
    460                 mGotTestResult = true;
    461             } else if (code.compareTo("NotSupported") == 0) {
    462                 mGotTestResult = true;
    463             } else if (code.compareTo("QualityWarning") == 0) {
    464                 mGotTestResult = true;
    465             } else if (code.compareTo("CompatibilityWarning") == 0) {
    466                 mGotTestResult = true;
    467             } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0
    468                     || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0
    469                     || code.compareTo("Timeout") == 0) {
    470                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
    471                 mPendingResults.get(mCurrentTestId)
    472                         .errorMessages.put(mRunConfig, code + ": " + details);
    473                 mGotTestResult = true;
    474             } else {
    475                 String codeError = "Unknown result code: " + code;
    476                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
    477                 mPendingResults.get(mCurrentTestId)
    478                         .errorMessages.put(mRunConfig, codeError + ": " + details);
    479                 mGotTestResult = true;
    480             }
    481         }
    482 
    483         /**
    484          * Handles terminated dEQP testcase.
    485          */
    486         private void handleTestCaseTerminate(Map<String, String> values) {
    487             final PendingResult result = mPendingResults.get(mCurrentTestId);
    488 
    489             if (result != null) {
    490                 String reason = values.get("dEQP-TerminateTestCase-Reason");
    491                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
    492                 mPendingResults.get(mCurrentTestId)
    493                         .errorMessages.put(mRunConfig, "Terminated: " + reason);
    494 
    495                 // Pending result finished, report result
    496                 if (result.remainingConfigs.isEmpty()) {
    497                     forwardFinalizedPendingResult(mCurrentTestId);
    498                 }
    499             } else {
    500                 CLog.w("Got unexpected termination of %s", mCurrentTestId);
    501             }
    502 
    503             mCurrentTestId = null;
    504             mGotTestResult = true;
    505         }
    506 
    507         /**
    508          * Handles dEQP testlog data.
    509          */
    510         private void handleTestLogData(Map<String, String> values) {
    511             mCurrentTestLog = mCurrentTestLog + values.get("dEQP-TestLogData-Log");
    512         }
    513 
    514         /**
    515          * Handles new instrumentation status message.
    516          */
    517         public void handleStatus(Map<String, String> values) {
    518             String eventType = values.get("dEQP-EventType");
    519 
    520             if (eventType == null) {
    521                 return;
    522             }
    523 
    524             if (eventType.compareTo("BeginSession") == 0) {
    525                 handleBeginSession(values);
    526             } else if (eventType.compareTo("EndSession") == 0) {
    527                 handleEndSession(values);
    528             } else if (eventType.compareTo("BeginTestCase") == 0) {
    529                 handleBeginTestCase(values);
    530             } else if (eventType.compareTo("EndTestCase") == 0) {
    531                 handleEndTestCase(values);
    532             } else if (eventType.compareTo("TestCaseResult") == 0) {
    533                 handleTestCaseResult(values);
    534             } else if (eventType.compareTo("TerminateTestCase") == 0) {
    535                 handleTestCaseTerminate(values);
    536             } else if (eventType.compareTo("TestLogData") == 0) {
    537                 handleTestLogData(values);
    538             }
    539         }
    540 
    541         /**
    542          * Signal listener that batch ended and forget incomplete results.
    543          */
    544         public void endBatch() {
    545             // end open test if when stream ends
    546             if (mCurrentTestId != null) {
    547                 // Current instance was removed from remainingConfigs when case
    548                 // started. Mark current instance as pending.
    549                 if (mPendingResults.get(mCurrentTestId) != null) {
    550                     mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig);
    551                 } else {
    552                     CLog.w("Got unexpected internal state of %s", mCurrentTestId);
    553                 }
    554             }
    555             mCurrentTestId = null;
    556         }
    557     }
    558 
    559     /**
    560      * dEQP instrumentation parser
    561      */
    562     private static class InstrumentationParser extends MultiLineReceiver {
    563         private TestInstanceResultListener mListener;
    564 
    565         private Map<String, String> mValues;
    566         private String mCurrentName;
    567         private String mCurrentValue;
    568         private int mResultCode;
    569         private boolean mGotExitValue = false;
    570 
    571 
    572         public InstrumentationParser(TestInstanceResultListener listener) {
    573             mListener = listener;
    574         }
    575 
    576         /**
    577          * {@inheritDoc}
    578          */
    579         @Override
    580         public void processNewLines(String[] lines) {
    581             for (String line : lines) {
    582                 if (mValues == null) mValues = new HashMap<String, String>();
    583 
    584                 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) {
    585                     if (mCurrentName != null) {
    586                         mValues.put(mCurrentName, mCurrentValue);
    587 
    588                         mCurrentName = null;
    589                         mCurrentValue = null;
    590                     }
    591 
    592                     mListener.handleStatus(mValues);
    593                     mValues = null;
    594                 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
    595                     if (mCurrentName != null) {
    596                         mValues.put(mCurrentName, mCurrentValue);
    597 
    598                         mCurrentValue = null;
    599                         mCurrentName = null;
    600                     }
    601 
    602                     String prefix = "INSTRUMENTATION_STATUS: ";
    603                     int nameBegin = prefix.length();
    604                     int nameEnd = line.indexOf('=');
    605                     int valueBegin = nameEnd + 1;
    606 
    607                     mCurrentName = line.substring(nameBegin, nameEnd);
    608                     mCurrentValue = line.substring(valueBegin);
    609                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
    610                     try {
    611                         mResultCode = Integer.parseInt(line.substring(22));
    612                         mGotExitValue = true;
    613                     } catch (NumberFormatException ex) {
    614                         CLog.w("Instrumentation code format unexpected");
    615                     }
    616                 } else if (mCurrentValue != null) {
    617                     mCurrentValue = mCurrentValue + line;
    618                 }
    619             }
    620         }
    621 
    622         /**
    623          * {@inheritDoc}
    624          */
    625         @Override
    626         public void done() {
    627             if (mCurrentName != null) {
    628                 mValues.put(mCurrentName, mCurrentValue);
    629 
    630                 mCurrentName = null;
    631                 mCurrentValue = null;
    632             }
    633 
    634             if (mValues != null) {
    635                 mListener.handleStatus(mValues);
    636                 mValues = null;
    637             }
    638         }
    639 
    640         /**
    641          * {@inheritDoc}
    642          */
    643         @Override
    644         public boolean isCancelled() {
    645             return false;
    646         }
    647 
    648         /**
    649          * Returns whether target instrumentation exited normally.
    650          */
    651         public boolean wasSuccessful() {
    652             return mGotExitValue;
    653         }
    654 
    655         /**
    656          * Returns Instrumentation return code
    657          */
    658         public int getResultCode() {
    659             return mResultCode;
    660         }
    661     }
    662 
    663     /**
    664      * dEQP platfom query instrumentation parser
    665      */
    666     private static class PlatformQueryInstrumentationParser extends MultiLineReceiver {
    667         private Map<String,String> mResultMap = new LinkedHashMap<>();
    668         private int mResultCode;
    669         private boolean mGotExitValue = false;
    670 
    671         /**
    672          * {@inheritDoc}
    673          */
    674         @Override
    675         public void processNewLines(String[] lines) {
    676             for (String line : lines) {
    677                 if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
    678                     final String parts[] = line.substring(24).split("=",2);
    679                     if (parts.length == 2) {
    680                         mResultMap.put(parts[0], parts[1]);
    681                     } else {
    682                         CLog.w("Instrumentation status format unexpected");
    683                     }
    684                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
    685                     try {
    686                         mResultCode = Integer.parseInt(line.substring(22));
    687                         mGotExitValue = true;
    688                     } catch (NumberFormatException ex) {
    689                         CLog.w("Instrumentation code format unexpected");
    690                     }
    691                 }
    692             }
    693         }
    694 
    695         /**
    696          * {@inheritDoc}
    697          */
    698         @Override
    699         public boolean isCancelled() {
    700             return false;
    701         }
    702 
    703         /**
    704          * Returns whether target instrumentation exited normally.
    705          */
    706         public boolean wasSuccessful() {
    707             return mGotExitValue;
    708         }
    709 
    710         /**
    711          * Returns Instrumentation return code
    712          */
    713         public int getResultCode() {
    714             return mResultCode;
    715         }
    716 
    717         public Map<String,String> getResultMap() {
    718             return mResultMap;
    719         }
    720     }
    721 
    722     /**
    723      * Interface for sleeping.
    724      *
    725      * Exposed for unit testing
    726      */
    727     public static interface ISleepProvider {
    728         public void sleep(int milliseconds);
    729     };
    730 
    731     private static class SleepProvider implements ISleepProvider {
    732         public void sleep(int milliseconds) {
    733             try {
    734                 Thread.sleep(milliseconds);
    735             } catch (InterruptedException ex) {
    736             }
    737         }
    738     };
    739 
    740     /**
    741      * Interface for failure recovery.
    742      *
    743      * Exposed for unit testing
    744      */
    745     public static interface IRecovery {
    746         /**
    747          * Sets the sleep provider IRecovery works on
    748          */
    749         public void setSleepProvider(ISleepProvider sleepProvider);
    750 
    751         /**
    752          * Sets the device IRecovery works on
    753          */
    754         public void setDevice(ITestDevice device);
    755 
    756         /**
    757          * Informs Recovery that test execution has progressed since the last recovery
    758          */
    759         public void onExecutionProgressed();
    760 
    761         /**
    762          * Tries to recover device after failed refused connection.
    763          *
    764          * @throws DeviceNotAvailableException if recovery did not succeed
    765          */
    766         public void recoverConnectionRefused() throws DeviceNotAvailableException;
    767 
    768         /**
    769          * Tries to recover device after abnormal execution termination or link failure.
    770          *
    771          * @param progressedSinceLastCall true if test execution has progressed since last call
    772          * @throws DeviceNotAvailableException if recovery did not succeed
    773          */
    774         public void recoverComLinkKilled() throws DeviceNotAvailableException;
    775     };
    776 
    777     /**
    778      * State machine for execution failure recovery.
    779      *
    780      * Exposed for unit testing
    781      */
    782     public static class Recovery implements IRecovery {
    783         private int RETRY_COOLDOWN_MS = 6000; // 6 seconds
    784         private int PROCESS_KILL_WAIT_MS = 1000; // 1 second
    785 
    786         private static enum MachineState {
    787             WAIT, // recover by waiting
    788             RECOVER, // recover by calling recover()
    789             REBOOT, // recover by rebooting
    790             FAIL, // cannot recover
    791         };
    792 
    793         private MachineState mState = MachineState.WAIT;
    794         private ITestDevice mDevice;
    795         private ISleepProvider mSleepProvider;
    796 
    797         private static class ProcessKillFailureException extends Exception {
    798         }
    799 
    800         /**
    801          * {@inheritDoc}
    802          */
    803         public void setSleepProvider(ISleepProvider sleepProvider) {
    804             mSleepProvider = sleepProvider;
    805         }
    806 
    807         /**
    808          * {@inheritDoc}
    809          */
    810         @Override
    811         public void setDevice(ITestDevice device) {
    812             mDevice = device;
    813         }
    814 
    815         /**
    816          * {@inheritDoc}
    817          */
    818         @Override
    819         public void onExecutionProgressed() {
    820             mState = MachineState.WAIT;
    821         }
    822 
    823         /**
    824          * {@inheritDoc}
    825          */
    826         @Override
    827         public void recoverConnectionRefused() throws DeviceNotAvailableException {
    828             switch (mState) {
    829                 case WAIT: // not a valid stratedy for connection refusal, fallthrough
    830                 case RECOVER:
    831                     // First failure, just try to recover
    832                     CLog.w("ADB connection failed, trying to recover");
    833                     mState = MachineState.REBOOT; // the next step is to reboot
    834 
    835                     try {
    836                         recoverDevice();
    837                     } catch (DeviceNotAvailableException ex) {
    838                         // chain forward
    839                         recoverConnectionRefused();
    840                     }
    841                     break;
    842 
    843                 case REBOOT:
    844                     // Second failure in a row, try to reboot
    845                     CLog.w("ADB connection failed after recovery, rebooting device");
    846                     mState = MachineState.FAIL; // the next step is to fail
    847 
    848                     try {
    849                         rebootDevice();
    850                     } catch (DeviceNotAvailableException ex) {
    851                         // chain forward
    852                         recoverConnectionRefused();
    853                     }
    854                     break;
    855 
    856                 case FAIL:
    857                     // Third failure in a row, just fail
    858                     CLog.w("Cannot recover ADB connection");
    859                     throw new DeviceNotAvailableException("failed to connect after reboot");
    860             }
    861         }
    862 
    863         /**
    864          * {@inheritDoc}
    865          */
    866         @Override
    867         public void recoverComLinkKilled() throws DeviceNotAvailableException {
    868             switch (mState) {
    869                 case WAIT:
    870                     // First failure, just try to wait and try again
    871                     CLog.w("ADB link failed, retrying after a cooldown period");
    872                     mState = MachineState.RECOVER; // the next step is to recover the device
    873 
    874                     waitCooldown();
    875 
    876                     // even if the link to deqp on-device process was killed, the process might
    877                     // still be alive. Locate and terminate such unwanted processes.
    878                     try {
    879                         killDeqpProcess();
    880                     } catch (DeviceNotAvailableException ex) {
    881                         // chain forward
    882                         recoverComLinkKilled();
    883                     } catch (ProcessKillFailureException ex) {
    884                         // chain forward
    885                         recoverComLinkKilled();
    886                     }
    887                     break;
    888 
    889                 case RECOVER:
    890                     // Second failure, just try to recover
    891                     CLog.w("ADB link failed, trying to recover");
    892                     mState = MachineState.REBOOT; // the next step is to reboot
    893 
    894                     try {
    895                         recoverDevice();
    896                         killDeqpProcess();
    897                     } catch (DeviceNotAvailableException ex) {
    898                         // chain forward
    899                         recoverComLinkKilled();
    900                     } catch (ProcessKillFailureException ex) {
    901                         // chain forward
    902                         recoverComLinkKilled();
    903                     }
    904                     break;
    905 
    906                 case REBOOT:
    907                     // Third failure in a row, try to reboot
    908                     CLog.w("ADB link failed after recovery, rebooting device");
    909                     mState = MachineState.FAIL; // the next step is to fail
    910 
    911                     try {
    912                         rebootDevice();
    913                     } catch (DeviceNotAvailableException ex) {
    914                         // chain forward
    915                         recoverComLinkKilled();
    916                     }
    917                     break;
    918 
    919                 case FAIL:
    920                     // Fourth failure in a row, just fail
    921                     CLog.w("Cannot recover ADB connection");
    922                     throw new DeviceNotAvailableException("link killed after reboot");
    923             }
    924         }
    925 
    926         private void waitCooldown() {
    927             mSleepProvider.sleep(RETRY_COOLDOWN_MS);
    928         }
    929 
    930         private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException {
    931             final List<Integer> pids = new ArrayList<Integer>(2);
    932             final String processes = mDevice.executeShellCommand("ps | grep com.drawelements");
    933             final String[] lines = processes.split("(\\r|\\n)+");
    934             for (String line : lines) {
    935                 final String[] fields = line.split("\\s+");
    936                 if (fields.length < 2) {
    937                     continue;
    938                 }
    939 
    940                 try {
    941                     final int processId = Integer.parseInt(fields[1], 10);
    942                     pids.add(processId);
    943                 } catch (NumberFormatException ex) {
    944                     continue;
    945                 }
    946             }
    947             return pids;
    948         }
    949 
    950         private void killDeqpProcess() throws DeviceNotAvailableException,
    951                 ProcessKillFailureException {
    952             for (Integer processId : getDeqpProcessPids()) {
    953                 mDevice.executeShellCommand(String.format("kill -9 %d", processId));
    954             }
    955 
    956             mSleepProvider.sleep(PROCESS_KILL_WAIT_MS);
    957 
    958             // check that processes actually died
    959             if (getDeqpProcessPids().iterator().hasNext()) {
    960                 // a process is still alive, killing failed
    961                 throw new ProcessKillFailureException();
    962             }
    963         }
    964 
    965         public void recoverDevice() throws DeviceNotAvailableException {
    966             // Work around the API. We need to call recoverDevice() on the test device and
    967             // we know that mDevice is a TestDevice. However even though the recoverDevice()
    968             // method is public suggesting it should be publicly accessible, the class itself
    969             // and its super-interface (IManagedTestDevice) are package-private.
    970             final Method recoverDeviceMethod;
    971             try {
    972                 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice");
    973                 recoverDeviceMethod.setAccessible(true);
    974             } catch (NoSuchMethodException ex) {
    975                 throw new AssertionError("Test device must have recoverDevice()");
    976             }
    977 
    978             try {
    979                 recoverDeviceMethod.invoke(mDevice);
    980             } catch (InvocationTargetException ex) {
    981                 if (ex.getCause() instanceof DeviceNotAvailableException) {
    982                     throw (DeviceNotAvailableException)ex.getCause();
    983                 } else if (ex.getCause() instanceof RuntimeException) {
    984                     throw (RuntimeException)ex.getCause();
    985                 } else {
    986                     throw new AssertionError("unexpected throw", ex);
    987                 }
    988             } catch (IllegalAccessException ex) {
    989                 throw new AssertionError("unexpected throw", ex);
    990             }
    991         }
    992 
    993         private void rebootDevice() throws DeviceNotAvailableException {
    994             mDevice.reboot();
    995         }
    996     };
    997 
    998     /**
    999      * Parse map of instance arguments to map of BatchRunConfigurations
   1000      */
   1001     private static Map<TestIdentifier, Set<BatchRunConfiguration>> parseTestInstances(
   1002             Collection<TestIdentifier> tests,
   1003             Map<TestIdentifier, List<Map<String,String>>> testInstances) {
   1004         final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new HashMap<>();
   1005         for (final TestIdentifier test : tests) {
   1006             final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>();
   1007             if (testInstances.get(test).isEmpty()) {
   1008                 // no instances defined, use default
   1009                 testInstanceSet.add(DEFAULT_CONFIG);
   1010             } else {
   1011                 for (Map<String, String> instanceArgs : testInstances.get(test)) {
   1012                     testInstanceSet.add(parseRunConfig(instanceArgs));
   1013                 }
   1014             }
   1015             instances.put(test, testInstanceSet);
   1016         }
   1017         return instances;
   1018     }
   1019 
   1020     private static BatchRunConfiguration parseRunConfig(Map<String,String> instanceArguments) {
   1021         final String glConfig;
   1022         final String rotation;
   1023         final String surfaceType;
   1024 
   1025         if (instanceArguments.containsKey("glconfig")) {
   1026             glConfig = instanceArguments.get("glconfig");
   1027         } else {
   1028             glConfig = DEFAULT_CONFIG.getGlConfig();
   1029         }
   1030         if (instanceArguments.containsKey("rotation")) {
   1031             rotation = instanceArguments.get("rotation");
   1032         } else {
   1033             rotation = DEFAULT_CONFIG.getRotation();
   1034         }
   1035         if (instanceArguments.containsKey("surfaceType")) {
   1036             surfaceType = instanceArguments.get("surfaceType");
   1037         } else {
   1038             surfaceType = DEFAULT_CONFIG.getSurfaceType();
   1039         }
   1040 
   1041         return new BatchRunConfiguration(glConfig, rotation, surfaceType);
   1042     }
   1043 
   1044     private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) {
   1045         return mTestInstances.get(testId);
   1046     }
   1047 
   1048     /**
   1049      * Converts dEQP testcase path to TestIdentifier.
   1050      */
   1051     private static TestIdentifier pathToIdentifier(String testPath) {
   1052         String[] components = testPath.split("\\.");
   1053         String name = components[components.length - 1];
   1054         String className = null;
   1055 
   1056         for (int i = 0; i < components.length - 1; i++) {
   1057             if (className == null) {
   1058                 className = components[i];
   1059             } else {
   1060                 className = className + "." + components[i];
   1061             }
   1062         }
   1063 
   1064         return new TestIdentifier(className, name);
   1065     }
   1066 
   1067     private String getId() {
   1068         return AbiUtils.createId(mAbi.getName(), mPackageName);
   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 TestIdentifiers.
   1126      */
   1127     private static String generateTestCaseTrie(Collection<TestIdentifier> tests) {
   1128         ArrayList<String> testPaths = new ArrayList<String>();
   1129 
   1130         for (TestIdentifier 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<TestIdentifier> tests;
   1140     }
   1141 
   1142     private TestBatch selectRunBatch() {
   1143         return selectRunBatch(mRemainingTests, null);
   1144     }
   1145 
   1146     /**
   1147      * Creates a TestBatch from the given tests or null if not tests remaining.
   1148      *
   1149      *  @param pool List of tests to select from
   1150      *  @param requiredConfig Select only instances with pending requiredConfig, or null to select
   1151      *         any run configuration.
   1152      */
   1153     private TestBatch selectRunBatch(Collection<TestIdentifier> pool,
   1154             BatchRunConfiguration requiredConfig) {
   1155         // select one test (leading test) that is going to be executed and then pack along as many
   1156         // other compatible instances as possible.
   1157 
   1158         TestIdentifier leadingTest = null;
   1159         for (TestIdentifier test : pool) {
   1160             if (!mRemainingTests.contains(test)) {
   1161                 continue;
   1162             }
   1163             if (requiredConfig != null &&
   1164                     !mInstanceListerner.isPendingTestInstance(test, requiredConfig)) {
   1165                 continue;
   1166             }
   1167             leadingTest = test;
   1168             break;
   1169         }
   1170 
   1171         // no remaining tests?
   1172         if (leadingTest == null) {
   1173             return null;
   1174         }
   1175 
   1176         BatchRunConfiguration leadingTestConfig = null;
   1177         if (requiredConfig != null) {
   1178             leadingTestConfig = requiredConfig;
   1179         } else {
   1180             for (BatchRunConfiguration runConfig : getTestRunConfigs(leadingTest)) {
   1181                 if (mInstanceListerner.isPendingTestInstance(leadingTest, runConfig)) {
   1182                     leadingTestConfig = runConfig;
   1183                     break;
   1184                 }
   1185             }
   1186         }
   1187 
   1188         // test pending <=> test has a pending config
   1189         if (leadingTestConfig == null) {
   1190             throw new AssertionError("search postcondition failed");
   1191         }
   1192 
   1193         final int leadingInstability = getTestInstabilityRating(leadingTest);
   1194 
   1195         final TestBatch runBatch = new TestBatch();
   1196         runBatch.config = leadingTestConfig;
   1197         runBatch.tests = new ArrayList<>();
   1198         runBatch.tests.add(leadingTest);
   1199 
   1200         for (TestIdentifier test : pool) {
   1201             if (test == leadingTest) {
   1202                 // do not re-select the leading tests
   1203                 continue;
   1204             }
   1205             if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) {
   1206                 // select only compatible
   1207                 continue;
   1208             }
   1209             if (getTestInstabilityRating(test) != leadingInstability) {
   1210                 // pack along only cases in the same stability category. Packing more dangerous
   1211                 // tests along jeopardizes the stability of this run. Packing more stable tests
   1212                 // along jeopardizes their stability rating.
   1213                 continue;
   1214             }
   1215             if (runBatch.tests.size() >= getBatchSizeLimitForInstability(leadingInstability)) {
   1216                 // batch size is limited.
   1217                 break;
   1218             }
   1219             runBatch.tests.add(test);
   1220         }
   1221 
   1222         return runBatch;
   1223     }
   1224 
   1225     private int getBatchNumPendingCases(TestBatch batch) {
   1226         int numPending = 0;
   1227         for (TestIdentifier test : batch.tests) {
   1228             if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1229                 ++numPending;
   1230             }
   1231         }
   1232         return numPending;
   1233     }
   1234 
   1235     private int getBatchSizeLimitForInstability(int batchInstabilityRating) {
   1236         // reduce group size exponentially down to one
   1237         return Math.max(1, TESTCASE_BATCH_LIMIT / (1 << batchInstabilityRating));
   1238     }
   1239 
   1240     private int getTestInstabilityRating(TestIdentifier testId) {
   1241         if (mTestInstabilityRatings.containsKey(testId)) {
   1242             return mTestInstabilityRatings.get(testId);
   1243         } else {
   1244             return 0;
   1245         }
   1246     }
   1247 
   1248     private void recordTestInstability(TestIdentifier testId) {
   1249         mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
   1250     }
   1251 
   1252     private void clearTestInstability(TestIdentifier testId) {
   1253         mTestInstabilityRatings.put(testId, 0);
   1254     }
   1255 
   1256     /**
   1257      * Executes all tests on the device.
   1258      */
   1259     private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1260         for (;;) {
   1261             TestBatch batch = selectRunBatch();
   1262 
   1263             if (batch == null) {
   1264                 break;
   1265             }
   1266 
   1267             runTestRunBatch(batch);
   1268         }
   1269     }
   1270 
   1271     /**
   1272      * Runs a TestBatch by either faking it or executing it on a device.
   1273      */
   1274     private void runTestRunBatch(TestBatch batch) throws DeviceNotAvailableException,
   1275             CapabilityQueryFailureException {
   1276         // prepare instance listener
   1277         mInstanceListerner.setCurrentConfig(batch.config);
   1278         for (TestIdentifier test : batch.tests) {
   1279             mInstanceListerner.setTestInstances(test, getTestRunConfigs(test));
   1280         }
   1281 
   1282         // execute only if config is executable, else fake results
   1283         if (isSupportedRunConfiguration(batch.config)) {
   1284             executeTestRunBatch(batch);
   1285         } else {
   1286             fakePassTestRunBatch(batch);
   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     private static final class AdbComLinkKilledError extends Exception {
   1320         public AdbComLinkKilledError(String description, Throwable inner) {
   1321             super(description, inner);
   1322         }
   1323     };
   1324 
   1325     /**
   1326      * Executes a given command in adb shell
   1327      *
   1328      * @throws AdbComLinkOpenError if connection cannot be established.
   1329      * @throws AdbComLinkKilledError if established connection is killed prematurely.
   1330      */
   1331     private void executeShellCommandAndReadOutput(final String command,
   1332             final IShellOutputReceiver receiver)
   1333             throws AdbComLinkOpenError, AdbComLinkKilledError {
   1334         try {
   1335             mDevice.getIDevice().executeShellCommand(command, receiver,
   1336                     UNRESPOSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
   1337         } catch (TimeoutException ex) {
   1338             // Opening connection timed out
   1339             throw new AdbComLinkOpenError("opening connection timed out", ex);
   1340         } catch (AdbCommandRejectedException ex) {
   1341             // Command rejected
   1342             throw new AdbComLinkOpenError("command rejected", ex);
   1343         } catch (IOException ex) {
   1344             // shell command channel killed
   1345             throw new AdbComLinkKilledError("command link killed", ex);
   1346         } catch (ShellCommandUnresponsiveException ex) {
   1347             // shell command halted
   1348             throw new AdbComLinkKilledError("command link hung", ex);
   1349         }
   1350     }
   1351 
   1352     /**
   1353      * Executes given test batch on a device
   1354      */
   1355     private void executeTestRunBatch(TestBatch batch) throws DeviceNotAvailableException {
   1356         // attempt full run once
   1357         executeTestRunBatchRun(batch);
   1358 
   1359         // split remaining tests to two sub batches and execute both. This will terminate
   1360         // since executeTestRunBatchRun will always progress for a batch of size 1.
   1361         final ArrayList<TestIdentifier> pendingTests = new ArrayList<>();
   1362 
   1363         for (TestIdentifier test : batch.tests) {
   1364             if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1365                 pendingTests.add(test);
   1366             }
   1367         }
   1368 
   1369         final int divisorNdx = pendingTests.size() / 2;
   1370         final List<TestIdentifier> headList = pendingTests.subList(0, divisorNdx);
   1371         final List<TestIdentifier> tailList = pendingTests.subList(divisorNdx, pendingTests.size());
   1372 
   1373         // head
   1374         for (;;) {
   1375             TestBatch subBatch = selectRunBatch(headList, batch.config);
   1376 
   1377             if (subBatch == null) {
   1378                 break;
   1379             }
   1380 
   1381             executeTestRunBatch(subBatch);
   1382         }
   1383 
   1384         // tail
   1385         for (;;) {
   1386             TestBatch subBatch = selectRunBatch(tailList, batch.config);
   1387 
   1388             if (subBatch == null) {
   1389                 break;
   1390             }
   1391 
   1392             executeTestRunBatch(subBatch);
   1393         }
   1394 
   1395         if (getBatchNumPendingCases(batch) != 0) {
   1396             throw new AssertionError("executeTestRunBatch postcondition failed");
   1397         }
   1398     }
   1399 
   1400     /**
   1401      * Runs one execution pass over the given batch.
   1402      *
   1403      * Tries to run the batch. Always makes progress (executes instances or modifies stability
   1404      * scores).
   1405      */
   1406     private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException {
   1407         if (getBatchNumPendingCases(batch) != batch.tests.size()) {
   1408             throw new AssertionError("executeTestRunBatchRun precondition failed");
   1409         }
   1410 
   1411         checkInterrupted(); // throws if interrupted
   1412 
   1413         final String testCases = generateTestCaseTrie(batch.tests);
   1414 
   1415         mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
   1416         mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
   1417         mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME);
   1418 
   1419         final String instrumentationName =
   1420                 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation";
   1421 
   1422         final StringBuilder deqpCmdLine = new StringBuilder();
   1423         deqpCmdLine.append("--deqp-caselist-file=");
   1424         deqpCmdLine.append(CASE_LIST_FILE_NAME);
   1425         deqpCmdLine.append(" ");
   1426         deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config));
   1427 
   1428         // If we are not logging data, do not bother outputting the images from the test exe.
   1429         if (!mLogData) {
   1430             deqpCmdLine.append(" --deqp-log-images=disable");
   1431         }
   1432 
   1433         deqpCmdLine.append(" --deqp-watchdog=enable");
   1434 
   1435         final String command = String.format(
   1436                 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\""
   1437                     + " -e deqpLogData \"%s\" %s",
   1438                 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(),
   1439                 mLogData, instrumentationName);
   1440 
   1441         final int numRemainingInstancesBefore = getNumRemainingInstances();
   1442         final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner);
   1443         Throwable interruptingError = null;
   1444 
   1445         try {
   1446             executeShellCommandAndReadOutput(command, parser);
   1447         } catch (Throwable ex) {
   1448             interruptingError = ex;
   1449         } finally {
   1450             parser.flush();
   1451         }
   1452 
   1453         final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null ||
   1454                 getNumRemainingInstances() < numRemainingInstancesBefore;
   1455 
   1456         if (progressedSinceLastCall) {
   1457             mDeviceRecovery.onExecutionProgressed();
   1458         }
   1459 
   1460         // interrupted, try to recover
   1461         if (interruptingError != null) {
   1462             if (interruptingError instanceof AdbComLinkOpenError) {
   1463                 mDeviceRecovery.recoverConnectionRefused();
   1464             } else if (interruptingError instanceof AdbComLinkKilledError) {
   1465                 mDeviceRecovery.recoverComLinkKilled();
   1466             } else if (interruptingError instanceof RunInterruptedException) {
   1467                 // external run interruption request. Terminate immediately.
   1468                 throw (RunInterruptedException)interruptingError;
   1469             } else {
   1470                 CLog.e(interruptingError);
   1471                 throw new RuntimeException(interruptingError);
   1472             }
   1473 
   1474             // recoverXXX did not throw => recovery succeeded
   1475         } else if (!parser.wasSuccessful()) {
   1476             mDeviceRecovery.recoverComLinkKilled();
   1477             // recoverXXX did not throw => recovery succeeded
   1478         }
   1479 
   1480         // Progress guarantees.
   1481         if (batch.tests.size() == 1) {
   1482             final TestIdentifier onlyTest = batch.tests.iterator().next();
   1483             final boolean wasTestExecuted =
   1484                     !mInstanceListerner.isPendingTestInstance(onlyTest, batch.config) &&
   1485                     mInstanceListerner.getCurrentTestId() == null;
   1486             final boolean wasLinkFailure = !parser.wasSuccessful() || interruptingError != null;
   1487 
   1488             // Link failures can be caused by external events, require at least two observations
   1489             // until bailing.
   1490             if (!wasTestExecuted && (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) {
   1491                 recordTestInstability(onlyTest);
   1492                 // If we cannot finish the test, mark the case as a crash.
   1493                 //
   1494                 // If we couldn't even start the test, fail the test instance as non-executable.
   1495                 // This is required so that a consistently crashing or non-existent tests will
   1496                 // not cause futile (non-terminating) re-execution attempts.
   1497                 if (mInstanceListerner.getCurrentTestId() != null) {
   1498                     mInstanceListerner.abortTest(onlyTest, INCOMPLETE_LOG_MESSAGE);
   1499                 } else {
   1500                     mInstanceListerner.abortTest(onlyTest, NOT_EXECUTABLE_LOG_MESSAGE);
   1501                 }
   1502             } else if (wasTestExecuted) {
   1503                 clearTestInstability(onlyTest);
   1504             }
   1505         }
   1506         else
   1507         {
   1508             // Analyze results to update test stability ratings. If there is no interrupting test
   1509             // logged, increase instability rating of all remaining tests. If there is a
   1510             // interrupting test logged, increase only its instability rating.
   1511             //
   1512             // A successful run of tests clears instability rating.
   1513             if (mInstanceListerner.getCurrentTestId() == null) {
   1514                 for (TestIdentifier test : batch.tests) {
   1515                     if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1516                         recordTestInstability(test);
   1517                     } else {
   1518                         clearTestInstability(test);
   1519                     }
   1520                 }
   1521             } else {
   1522                 recordTestInstability(mInstanceListerner.getCurrentTestId());
   1523                 for (TestIdentifier test : batch.tests) {
   1524                     // \note: isPendingTestInstance is false for getCurrentTestId. Current ID is
   1525                     // considered 'running' and will be restored to 'pending' in endBatch().
   1526                     if (!test.equals(mInstanceListerner.getCurrentTestId()) &&
   1527                             !mInstanceListerner.isPendingTestInstance(test, batch.config)) {
   1528                         clearTestInstability(test);
   1529                     }
   1530                 }
   1531             }
   1532         }
   1533 
   1534         mInstanceListerner.endBatch();
   1535     }
   1536 
   1537     private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) {
   1538         final StringBuilder deqpCmdLine = new StringBuilder();
   1539         if (!runConfig.getGlConfig().isEmpty()) {
   1540             deqpCmdLine.append("--deqp-gl-config-name=");
   1541             deqpCmdLine.append(runConfig.getGlConfig());
   1542         }
   1543         if (!runConfig.getRotation().isEmpty()) {
   1544             if (deqpCmdLine.length() != 0) {
   1545                 deqpCmdLine.append(" ");
   1546             }
   1547             deqpCmdLine.append("--deqp-screen-rotation=");
   1548             deqpCmdLine.append(runConfig.getRotation());
   1549         }
   1550         if (!runConfig.getSurfaceType().isEmpty()) {
   1551             if (deqpCmdLine.length() != 0) {
   1552                 deqpCmdLine.append(" ");
   1553             }
   1554             deqpCmdLine.append("--deqp-surface-type=");
   1555             deqpCmdLine.append(runConfig.getSurfaceType());
   1556         }
   1557         return deqpCmdLine.toString();
   1558     }
   1559 
   1560     private int getNumRemainingInstances() {
   1561         int retVal = 0;
   1562         for (TestIdentifier testId : mRemainingTests) {
   1563             // If case is in current working set, sum only not yet executed instances.
   1564             // If case is not in current working set, sum all instances (since they are not yet
   1565             // executed).
   1566             if (mInstanceListerner.mPendingResults.containsKey(testId)) {
   1567                 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size();
   1568             } else {
   1569                 retVal += mTestInstances.get(testId).size();
   1570             }
   1571         }
   1572         return retVal;
   1573     }
   1574 
   1575     /**
   1576      * Checks if this execution has been marked as interrupted and throws if it has.
   1577      */
   1578     private void checkInterrupted() throws RunInterruptedException {
   1579         // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly
   1580         // by sleeping a value <= 0.
   1581         mRunUtil.sleep(0);
   1582     }
   1583 
   1584     /**
   1585      * Pass given batch tests without running it
   1586      */
   1587     private void fakePassTestRunBatch(TestBatch batch) {
   1588         for (TestIdentifier test : batch.tests) {
   1589             CLog.d("Skipping test '%s' invocation in config '%s'", test.toString(),
   1590                     batch.config.getId());
   1591             mInstanceListerner.skipTest(test);
   1592         }
   1593     }
   1594 
   1595     /**
   1596      * Pass all remaining tests without running them
   1597      */
   1598     private void fakePassTests(ITestInvocationListener listener) {
   1599         Map <String, String> emptyMap = Collections.emptyMap();
   1600         for (TestIdentifier test : mRemainingTests) {
   1601             CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString());
   1602             listener.testStarted(test);
   1603             listener.testEnded(test, emptyMap);
   1604         }
   1605         mRemainingTests.clear();
   1606     }
   1607 
   1608     /**
   1609      * Check if device supports OpenGL ES version.
   1610      */
   1611     private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion,
   1612             int requiredMinorVersion) throws DeviceNotAvailableException {
   1613         String roOpenglesVersion = device.getProperty("ro.opengles.version");
   1614 
   1615         if (roOpenglesVersion == null)
   1616             return false;
   1617 
   1618         int intValue = Integer.parseInt(roOpenglesVersion);
   1619 
   1620         int majorVersion = ((intValue & 0xffff0000) >> 16);
   1621         int minorVersion = (intValue & 0xffff);
   1622 
   1623         return (majorVersion > requiredMajorVersion)
   1624                 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion);
   1625     }
   1626 
   1627     /**
   1628      * Query if rendertarget is supported
   1629      */
   1630     private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
   1631             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1632         // query if configuration is supported
   1633         final StringBuilder configCommandLine =
   1634                 new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
   1635         if (configCommandLine.length() != 0) {
   1636             configCommandLine.append(" ");
   1637         }
   1638         configCommandLine.append("--deqp-gl-major-version=");
   1639         configCommandLine.append(getGlesMajorVersion());
   1640         configCommandLine.append(" --deqp-gl-minor-version=");
   1641         configCommandLine.append(getGlesMinorVersion());
   1642 
   1643         final String commandLine = configCommandLine.toString();
   1644 
   1645         // check for cached result first
   1646         if (mConfigQuerySupportCache.containsKey(commandLine)) {
   1647             return mConfigQuerySupportCache.get(commandLine);
   1648         }
   1649 
   1650         final boolean supported = queryIsSupportedConfigCommandLine(commandLine);
   1651         mConfigQuerySupportCache.put(commandLine, supported);
   1652         return supported;
   1653     }
   1654 
   1655     private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
   1656             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1657         final String instrumentationName =
   1658                 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
   1659         final String command = String.format(
   1660                 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
   1661                     + " %s",
   1662                 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName);
   1663 
   1664         final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser();
   1665         mDevice.executeShellCommand(command, parser);
   1666         parser.flush();
   1667 
   1668         if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
   1669                 parser.getResultMap().containsKey("Supported")) {
   1670             if ("Yes".equals(parser.getResultMap().get("Supported"))) {
   1671                 return true;
   1672             } else if ("No".equals(parser.getResultMap().get("Supported"))) {
   1673                 return false;
   1674             } else {
   1675                 CLog.e("Capability query did not return a result");
   1676                 throw new CapabilityQueryFailureException();
   1677             }
   1678         } else if (parser.wasSuccessful()) {
   1679             CLog.e("Failed to run capability query. Code: %d, Result: %s",
   1680                     parser.getResultCode(), parser.getResultMap().toString());
   1681             throw new CapabilityQueryFailureException();
   1682         } else {
   1683             CLog.e("Failed to run capability query");
   1684             throw new CapabilityQueryFailureException();
   1685         }
   1686     }
   1687 
   1688     /**
   1689      * Return feature set supported by the device
   1690      */
   1691     private Set<String> getDeviceFeatures(ITestDevice device)
   1692             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1693         if (mDeviceFeatures == null) {
   1694             mDeviceFeatures = queryDeviceFeatures(device);
   1695         }
   1696         return mDeviceFeatures;
   1697     }
   1698 
   1699     /**
   1700      * Query feature set supported by the device
   1701      */
   1702     private static Set<String> queryDeviceFeatures(ITestDevice device)
   1703             throws DeviceNotAvailableException, CapabilityQueryFailureException {
   1704         // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
   1705         // TODO: Move this logic to ITestDevice.
   1706         String command = "pm list features";
   1707         String commandOutput = device.executeShellCommand(command);
   1708 
   1709         // Extract the id of the new user.
   1710         HashSet<String> availableFeatures = new HashSet<>();
   1711         for (String feature: commandOutput.split("\\s+")) {
   1712             // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
   1713             String[] tokens = feature.split(":");
   1714             if (tokens.length < 2 || !"feature".equals(tokens[0])) {
   1715                 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]);
   1716                 throw new CapabilityQueryFailureException();
   1717             }
   1718             availableFeatures.add(tokens[1]);
   1719         }
   1720         return availableFeatures;
   1721     }
   1722 
   1723     private boolean isPortraitClassRotation(String rotation) {
   1724         return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
   1725                 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
   1726     }
   1727 
   1728     private boolean isLandscapeClassRotation(String rotation) {
   1729         return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
   1730                 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
   1731     }
   1732 
   1733     /**
   1734      * Install dEQP OnDevice Package
   1735      */
   1736     private void installTestApk() throws DeviceNotAvailableException {
   1737         try {
   1738             File apkFile = mCtsBuild.getTestApp(DEQP_ONDEVICE_APK);
   1739             String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
   1740             String errorCode = getDevice().installPackage(apkFile, true, options);
   1741             if (errorCode != null) {
   1742                 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode);
   1743             }
   1744         } catch (FileNotFoundException e) {
   1745             CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK);
   1746         }
   1747     }
   1748 
   1749     /**
   1750      * Uninstall dEQP OnDevice Package
   1751      */
   1752     private void uninstallTestApk() throws DeviceNotAvailableException {
   1753         getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
   1754     }
   1755 
   1756     /**
   1757      * Parse gl nature from package name
   1758      */
   1759     private boolean isOpenGlEsPackage() {
   1760         if ("dEQP-GLES2".equals(mName) || "dEQP-GLES3".equals(mName) ||
   1761                 "dEQP-GLES31".equals(mName)) {
   1762             return true;
   1763         } else if ("dEQP-EGL".equals(mName)) {
   1764             return false;
   1765         } else {
   1766             throw new IllegalStateException("dEQP runner was created with illegal name");
   1767         }
   1768     }
   1769 
   1770     /**
   1771      * Check GL support (based on package name)
   1772      */
   1773     private boolean isSupportedGles() throws DeviceNotAvailableException {
   1774         return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion());
   1775     }
   1776 
   1777     /**
   1778      * Get GL major version (based on package name)
   1779      */
   1780     private int getGlesMajorVersion() throws DeviceNotAvailableException {
   1781         if ("dEQP-GLES2".equals(mName)) {
   1782             return 2;
   1783         } else if ("dEQP-GLES3".equals(mName)) {
   1784             return 3;
   1785         } else if ("dEQP-GLES31".equals(mName)) {
   1786             return 3;
   1787         } else {
   1788             throw new IllegalStateException("getGlesMajorVersion called for non gles pkg");
   1789         }
   1790     }
   1791 
   1792     /**
   1793      * Get GL minor version (based on package name)
   1794      */
   1795     private int getGlesMinorVersion() throws DeviceNotAvailableException {
   1796         if ("dEQP-GLES2".equals(mName)) {
   1797             return 0;
   1798         } else if ("dEQP-GLES3".equals(mName)) {
   1799             return 0;
   1800         } else if ("dEQP-GLES31".equals(mName)) {
   1801             return 1;
   1802         } else {
   1803             throw new IllegalStateException("getGlesMinorVersion called for non gles pkg");
   1804         }
   1805     }
   1806 
   1807     /**
   1808      * {@inheritDoc}
   1809      */
   1810     @Override
   1811     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
   1812         final Map<String, String> emptyMap = Collections.emptyMap();
   1813         final boolean isSupportedApi = !isOpenGlEsPackage() || isSupportedGles();
   1814 
   1815         listener.testRunStarted(getId(), mRemainingTests.size());
   1816 
   1817         try {
   1818             if (isSupportedApi) {
   1819                 // Make sure there is no pre-existing package form earlier interrupted test run.
   1820                 uninstallTestApk();
   1821                 installTestApk();
   1822 
   1823                 mInstanceListerner.setSink(listener);
   1824                 mDeviceRecovery.setDevice(mDevice);
   1825                 runTests();
   1826 
   1827                 uninstallTestApk();
   1828             } else {
   1829                 // Pass all tests if OpenGL ES version is not supported
   1830                 fakePassTests(listener);
   1831             }
   1832         } catch (CapabilityQueryFailureException ex) {
   1833             // Platform is not behaving correctly, for example crashing when trying to create
   1834             // a window. Instead of silenty failing, signal failure by leaving the rest of the
   1835             // test cases in "NotExecuted" state
   1836             uninstallTestApk();
   1837         }
   1838 
   1839         listener.testRunEnded(0, emptyMap);
   1840     }
   1841 }
   1842