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     {
mDeviceRecovery.setSleepProvider(new SleepProvider())84         mDeviceRecovery.setSleepProvider(new SleepProvider());
85     }
86 
DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests, Map<TestIdentifier, List<Map<String,String>>> testInstances)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      */
setAbi(IAbi abi)99     public void setAbi(IAbi abi) {
100         mAbi = abi;
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
setBuild(IBuildInfo buildInfo)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      */
setBuildHelper(CtsBuildHelper buildHelper)118     public void setBuildHelper(CtsBuildHelper buildHelper) {
119         mCtsBuild = buildHelper;
120     }
121 
122     /**
123      * Enable or disable raw dEQP test log collection.
124      */
setCollectLogs(boolean logData)125     public void setCollectLogs(boolean logData) {
126         mLogData = logData;
127     }
128 
129     /**
130      * {@inheritDoc}
131      */
132     @Override
setDevice(ITestDevice device)133     public void setDevice(ITestDevice device) {
134         mDevice = device;
135     }
136 
137     /**
138      * {@inheritDoc}
139      */
140     @Override
getDevice()141     public ITestDevice getDevice() {
142         return mDevice;
143     }
144 
145     /**
146      * Set recovery handler.
147      *
148      * Exposed for unit testing.
149      */
setRecovery(IRecovery deviceRecovery)150     public void setRecovery(IRecovery deviceRecovery) {
151         mDeviceRecovery = deviceRecovery;
152     }
153 
154     /**
155      * Set IRunUtil.
156      *
157      * Exposed for unit testing.
158      */
setRunUtil(IRunUtil runUtil)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 
BatchRunConfiguration(String glConfig, String rotation, String surfaceType)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          */
getId()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          */
getGlConfig()198         public String getGlConfig() {
199             return mGlConfig;
200         }
201 
202         /**
203          * Get the screen rotation used in this configuration.
204          */
getRotation()205         public String getRotation() {
206             return mRotation;
207         }
208 
209         /**
210          * Get the surface type used in this configuration.
211          */
getSurfaceType()212         public String getSurfaceType() {
213             return mSurfaceType;
214         }
215 
216         @Override
equals(Object other)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
hashCode()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 
setSink(ITestInvocationListener sink)253         public void setSink(ITestInvocationListener sink) {
254             mSink = sink;
255         }
256 
setCurrentConfig(BatchRunConfiguration runConfig)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          */
getCurrentTestId()264         public TestIdentifier getCurrentTestId() {
265             return mCurrentTestId;
266         }
267 
268         /**
269          * Forward result to sink
270          */
forwardFinalizedPendingResult(TestIdentifier testId)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          */
setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs)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          */
isPendingTestInstance(TestIdentifier testId, BatchRunConfiguration config)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          */
skipTest(TestIdentifier testId)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          */
abortTest(TestIdentifier testId, String errorMessage)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          */
handleBeginSession(Map<String, String> values)393         private void handleBeginSession(Map<String, String> values) {
394             // ignore
395         }
396 
397         /**
398          * Handles end of dEQP session.
399          */
handleEndSession(Map<String, String> values)400         private void handleEndSession(Map<String, String> values) {
401             // ignore
402         }
403 
404         /**
405          * Handles beginning of dEQP testcase.
406          */
handleBeginTestCase(Map<String, String> values)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          */
handleEndTestCase(Map<String, String> values)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          */
handleTestCaseResult(Map<String, String> values)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          */
handleTestCaseTerminate(Map<String, String> values)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          */
handleTestLogData(Map<String, String> values)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          */
handleStatus(Map<String, String> values)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          */
endBatch()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 
InstrumentationParser(TestInstanceResultListener listener)572         public InstrumentationParser(TestInstanceResultListener listener) {
573             mListener = listener;
574         }
575 
576         /**
577          * {@inheritDoc}
578          */
579         @Override
processNewLines(String[] lines)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
done()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
isCancelled()644         public boolean isCancelled() {
645             return false;
646         }
647 
648         /**
649          * Returns whether target instrumentation exited normally.
650          */
wasSuccessful()651         public boolean wasSuccessful() {
652             return mGotExitValue;
653         }
654 
655         /**
656          * Returns Instrumentation return code
657          */
getResultCode()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
processNewLines(String[] lines)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
isCancelled()699         public boolean isCancelled() {
700             return false;
701         }
702 
703         /**
704          * Returns whether target instrumentation exited normally.
705          */
wasSuccessful()706         public boolean wasSuccessful() {
707             return mGotExitValue;
708         }
709 
710         /**
711          * Returns Instrumentation return code
712          */
getResultCode()713         public int getResultCode() {
714             return mResultCode;
715         }
716 
getResultMap()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 {
sleep(int milliseconds)728         public void sleep(int milliseconds);
729     };
730 
731     private static class SleepProvider implements ISleepProvider {
sleep(int milliseconds)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          */
setSleepProvider(ISleepProvider sleepProvider)749         public void setSleepProvider(ISleepProvider sleepProvider);
750 
751         /**
752          * Sets the device IRecovery works on
753          */
setDevice(ITestDevice device)754         public void setDevice(ITestDevice device);
755 
756         /**
757          * Informs Recovery that test execution has progressed since the last recovery
758          */
onExecutionProgressed()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          */
recoverConnectionRefused()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          */
recoverComLinkKilled()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          */
setSleepProvider(ISleepProvider sleepProvider)803         public void setSleepProvider(ISleepProvider sleepProvider) {
804             mSleepProvider = sleepProvider;
805         }
806 
807         /**
808          * {@inheritDoc}
809          */
810         @Override
setDevice(ITestDevice device)811         public void setDevice(ITestDevice device) {
812             mDevice = device;
813         }
814 
815         /**
816          * {@inheritDoc}
817          */
818         @Override
onExecutionProgressed()819         public void onExecutionProgressed() {
820             mState = MachineState.WAIT;
821         }
822 
823         /**
824          * {@inheritDoc}
825          */
826         @Override
recoverConnectionRefused()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
recoverComLinkKilled()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 
waitCooldown()926         private void waitCooldown() {
927             mSleepProvider.sleep(RETRY_COOLDOWN_MS);
928         }
929 
getDeqpProcessPids()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 
killDeqpProcess()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 
recoverDevice()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 
rebootDevice()993         private void rebootDevice() throws DeviceNotAvailableException {
994             mDevice.reboot();
995         }
996     };
997 
998     /**
999      * Parse map of instance arguments to map of BatchRunConfigurations
1000      */
parseTestInstances( Collection<TestIdentifier> tests, Map<TestIdentifier, List<Map<String,String>>> testInstances)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 
parseRunConfig(Map<String,String> instanceArguments)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 
getTestRunConfigs(TestIdentifier testId)1044     private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) {
1045         return mTestInstances.get(testId);
1046     }
1047 
1048     /**
1049      * Converts dEQP testcase path to TestIdentifier.
1050      */
pathToIdentifier(String testPath)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 
getId()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      */
generateTestCaseTrieFromPaths(Collection<String> tests)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      */
generateTestCaseTrie(Collection<TestIdentifier> tests)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 
selectRunBatch()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      */
selectRunBatch(Collection<TestIdentifier> pool, BatchRunConfiguration requiredConfig)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 
getBatchNumPendingCases(TestBatch batch)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 
getBatchSizeLimitForInstability(int batchInstabilityRating)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 
getTestInstabilityRating(TestIdentifier testId)1240     private int getTestInstabilityRating(TestIdentifier testId) {
1241         if (mTestInstabilityRatings.containsKey(testId)) {
1242             return mTestInstabilityRatings.get(testId);
1243         } else {
1244             return 0;
1245         }
1246     }
1247 
recordTestInstability(TestIdentifier testId)1248     private void recordTestInstability(TestIdentifier testId) {
1249         mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
1250     }
1251 
clearTestInstability(TestIdentifier testId)1252     private void clearTestInstability(TestIdentifier testId) {
1253         mTestInstabilityRatings.put(testId, 0);
1254     }
1255 
1256     /**
1257      * Executes all tests on the device.
1258      */
runTests()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      */
runTestRunBatch(TestBatch batch)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 
isSupportedRunConfiguration(BatchRunConfiguration runConfig)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 {
AdbComLinkOpenError(String description, Throwable inner)1315         public AdbComLinkOpenError(String description, Throwable inner) {
1316             super(description, inner);
1317         }
1318     };
1319     private static final class AdbComLinkKilledError extends Exception {
AdbComLinkKilledError(String description, Throwable inner)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      */
executeShellCommandAndReadOutput(final String command, final IShellOutputReceiver receiver)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      */
executeTestRunBatch(TestBatch batch)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      */
executeTestRunBatchRun(TestBatch batch)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 
getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig)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 
getNumRemainingInstances()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      */
checkInterrupted()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      */
fakePassTestRunBatch(TestBatch batch)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      */
fakePassTests(ITestInvocationListener listener)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      */
isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)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      */
isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)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 
queryIsSupportedConfigCommandLine(String deqpCommandLine)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      */
getDeviceFeatures(ITestDevice device)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      */
queryDeviceFeatures(ITestDevice device)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 
isPortraitClassRotation(String rotation)1723     private boolean isPortraitClassRotation(String rotation) {
1724         return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
1725                 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
1726     }
1727 
isLandscapeClassRotation(String rotation)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      */
installTestApk()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      */
uninstallTestApk()1752     private void uninstallTestApk() throws DeviceNotAvailableException {
1753         getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
1754     }
1755 
1756     /**
1757      * Parse gl nature from package name
1758      */
isOpenGlEsPackage()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      */
isSupportedGles()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      */
getGlesMajorVersion()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      */
getGlesMinorVersion()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
run(ITestInvocationListener listener)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