1 package com.android.cts.tradefed.testtype;
2 
3 import com.android.compatibility.common.util.AbiUtils;
4 import com.android.cts.tradefed.build.CtsBuildHelper;
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 = 10*60*1000; // ten minutes
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             boolean allInstancesPassed;
246             Map<BatchRunConfiguration, String> testLogs;
247             Map<BatchRunConfiguration, String> errorMessages;
248             Set<BatchRunConfiguration> remainingConfigs;
249         }
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             } else {
315                 CLog.w("Finalization for non-pending case %s", testId);
316             }
317         }
318 
319         /**
320          * Declare existence of a test and instances
321          */
setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs)322         public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) {
323             // Test instances cannot change at runtime, ignore if we have already set this
324             if (!mPendingResults.containsKey(testId)) {
325                 final PendingResult pendingResult = new PendingResult();
326                 pendingResult.allInstancesPassed = true;
327                 pendingResult.testLogs = new LinkedHashMap<>();
328                 pendingResult.errorMessages = new LinkedHashMap<>();
329                 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument
330                 mPendingResults.put(testId, pendingResult);
331             }
332         }
333 
334         /**
335          * Query if test instance has not yet been executed
336          */
isPendingTestInstance(TestIdentifier testId, BatchRunConfiguration config)337         public boolean isPendingTestInstance(TestIdentifier testId,
338                 BatchRunConfiguration config) {
339             final PendingResult result = mPendingResults.get(testId);
340             if (result == null) {
341                 // test is not in the current working batch of the runner, i.e. it cannot be
342                 // "partially" completed.
343                 if (!mRemainingTests.contains(testId)) {
344                     // The test has been fully executed. Not pending.
345                     return false;
346                 } else {
347                     // Test has not yet been executed. Check if such instance exists
348                     return mTestInstances.get(testId).contains(config);
349                 }
350             } else {
351                 // could be partially completed, check this particular config
352                 return result.remainingConfigs.contains(config);
353             }
354         }
355 
356         /**
357          * Fake execution of an instance with current config
358          */
skipTest(TestIdentifier testId)359         public void skipTest(TestIdentifier testId) {
360             final PendingResult result = mPendingResults.get(testId);
361 
362             result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE);
363             result.remainingConfigs.remove(mRunConfig);
364 
365             // Pending result finished, report result
366             if (result.remainingConfigs.isEmpty()) {
367                 forwardFinalizedPendingResult(testId);
368             }
369         }
370 
371         /**
372          * Fake failure of an instance with current config
373          */
abortTest(TestIdentifier testId, String errorMessage)374         public void abortTest(TestIdentifier testId, String errorMessage) {
375             final PendingResult result = mPendingResults.get(testId);
376 
377             CLog.i("Test %s aborted with message %s", testId, errorMessage);
378 
379             // Mark as executed
380             result.allInstancesPassed = false;
381             result.errorMessages.put(mRunConfig, errorMessage);
382             result.remainingConfigs.remove(mRunConfig);
383 
384             // Pending result finished, report result
385             if (result.remainingConfigs.isEmpty()) {
386                 forwardFinalizedPendingResult(testId);
387             }
388 
389             if (testId.equals(mCurrentTestId)) {
390                 mCurrentTestId = null;
391             }
392         }
393 
394         /**
395          * Handles beginning of dEQP session.
396          */
handleBeginSession(Map<String, String> values)397         private boolean handleBeginSession(Map<String, String> values) {
398             // ignore
399             return true;
400         }
401 
402         /**
403          * Handle session info
404          */
handleSessionInfo(Map<String, String> values)405         private boolean handleSessionInfo(Map<String, String> values) {
406             // ignore
407             return true;
408         }
409 
410         /**
411          * Handles end of dEQP session.
412          */
handleEndSession(Map<String, String> values)413         private boolean handleEndSession(Map<String, String> values) {
414             // ignore
415             return true;
416         }
417 
418         /**
419          * Handles beginning of dEQP testcase.
420          */
handleBeginTestCase(Map<String, String> values)421         private boolean handleBeginTestCase(Map<String, String> values) {
422             String casePath = values.get("dEQP-BeginTestCase-TestCasePath");
423 
424             if (mCurrentTestId != null) {
425                     CLog.w("Got unexpected start of %s, so aborting", mCurrentTestId);
426                     abortTest(mCurrentTestId, INCOMPLETE_LOG_MESSAGE);
427                     mCurrentTestId = null;
428             }
429 
430             mCurrentTestLog = "";
431             mGotTestResult = false;
432 
433             if (casePath == null) {
434                 CLog.w("Got null case path for test case begin event. Current test ID: %s", mCurrentTestId);
435                 mCurrentTestId = null;
436                 return false;
437             }
438 
439             mCurrentTestId = pathToIdentifier(casePath);
440 
441             if (mPendingResults.get(mCurrentTestId) == null) {
442                 CLog.w("Got unexpected start of %s", mCurrentTestId);
443             }
444             return true;
445         }
446 
447         /**
448          * Handles end of dEQP testcase.
449          */
handleEndTestCase(Map<String, String> values)450         private boolean handleEndTestCase(Map<String, String> values) {
451             final PendingResult result = mPendingResults.get(mCurrentTestId);
452 
453             if (result != null) {
454                 if (!mGotTestResult) {
455                     result.allInstancesPassed = false;
456                     result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE);
457                     CLog.i("Test %s failed as it ended before receiving result.", mCurrentTestId);
458                 }
459                 result.remainingConfigs.remove(mRunConfig);
460 
461                 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) {
462                     result.testLogs.put(mRunConfig, mCurrentTestLog);
463                 }
464 
465                 // Pending result finished, report result
466                 if (result.remainingConfigs.isEmpty()) {
467                     forwardFinalizedPendingResult(mCurrentTestId);
468                 }
469             } else {
470                 CLog.w("Got unexpected end of %s", mCurrentTestId);
471             }
472             mCurrentTestId = null;
473             return true;
474         }
475 
476         /**
477          * Handles dEQP testcase result.
478          */
handleTestCaseResult(Map<String, String> values)479         private boolean handleTestCaseResult(Map<String, String> values) {
480             String code = values.get("dEQP-TestCaseResult-Code");
481             if (code == null) {
482                 return false;
483             }
484 
485             String details = values.get("dEQP-TestCaseResult-Details");
486 
487             if (mPendingResults.get(mCurrentTestId) == null) {
488                 CLog.w("Got unexpected result for %s", mCurrentTestId);
489                 mGotTestResult = true;
490                 return true;
491             }
492 
493             if (code.compareTo("Pass") == 0) {
494                 mGotTestResult = true;
495             } else if (code.compareTo("NotSupported") == 0) {
496                 mGotTestResult = true;
497             } else if (code.compareTo("QualityWarning") == 0) {
498                 mGotTestResult = true;
499             } else if (code.compareTo("CompatibilityWarning") == 0) {
500                 mGotTestResult = true;
501             } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0
502                     || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0
503                     || code.compareTo("Timeout") == 0) {
504                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
505                 mPendingResults.get(mCurrentTestId)
506                         .errorMessages.put(mRunConfig, code + ": " + details);
507                 mGotTestResult = true;
508             } else {
509                 String codeError = "Unknown result code: " + code;
510                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
511                 mPendingResults.get(mCurrentTestId)
512                         .errorMessages.put(mRunConfig, codeError + ": " + details);
513                 mGotTestResult = true;
514                 CLog.e("Got invalid result code '%s' for test %s", code, mCurrentTestId);
515             }
516             return true;
517         }
518 
519         /**
520          * Handles terminated dEQP testcase.
521          */
handleTestCaseTerminate(Map<String, String> values)522         private boolean handleTestCaseTerminate(Map<String, String> values) {
523             final PendingResult result = mPendingResults.get(mCurrentTestId);
524 
525             if (result != null) {
526                 String reason = values.get("dEQP-TerminateTestCase-Reason");
527                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
528                 mPendingResults.get(mCurrentTestId)
529                         .errorMessages.put(mRunConfig, "Terminated: " + reason);
530                 result.remainingConfigs.remove(mRunConfig);
531 
532                 // Pending result finished, report result
533                 if (result.remainingConfigs.isEmpty()) {
534                     forwardFinalizedPendingResult(mCurrentTestId);
535                 }
536             } else {
537                 CLog.w("Got unexpected termination of %s", mCurrentTestId);
538             }
539 
540             mCurrentTestId = null;
541             mGotTestResult = true;
542             return true;
543         }
544 
545         /**
546          * Handles dEQP testlog data.
547          */
handleTestLogData(Map<String, String> values)548         private boolean handleTestLogData(Map<String, String> values) {
549             String newLog = values.get("dEQP-TestLogData-Log");
550             if (newLog == null) {
551                 return false;
552             }
553             mCurrentTestLog = mCurrentTestLog + newLog;
554             return true;
555         }
556 
557         /**
558          * Handles new instrumentation status message.
559          * @return true if handled correctly, false if missing values.
560          */
handleStatus(Map<String, String> values)561         public boolean handleStatus(Map<String, String> values) {
562             String eventType = values.get("dEQP-EventType");
563 
564             if (eventType == null) {
565                 // Not an event, but some other line
566                 return true;
567             }
568 
569             if (eventType.compareTo("BeginSession") == 0) {
570                 return handleBeginSession(values);
571             } else if (eventType.compareTo("SessionInfo") == 0) {
572                 return handleSessionInfo(values);
573             } else if (eventType.compareTo("EndSession") == 0) {
574                 return handleEndSession(values);
575             } else if (eventType.compareTo("BeginTestCase") == 0) {
576                 return handleBeginTestCase(values);
577             } else if (eventType.compareTo("EndTestCase") == 0) {
578                 return handleEndTestCase(values);
579             } else if (eventType.compareTo("TestCaseResult") == 0) {
580                 return handleTestCaseResult(values);
581             } else if (eventType.compareTo("TerminateTestCase") == 0) {
582                 return handleTestCaseTerminate(values);
583             } else if (eventType.compareTo("TestLogData") == 0) {
584                 return handleTestLogData(values);
585             }
586             CLog.e("Unknown event type (%s)", eventType);
587             return false;
588         }
589 
590         /**
591          * Signal listener that batch ended and forget incomplete results.
592          */
endBatch()593         public void endBatch() {
594             // end open test if when stream ends
595             if (mCurrentTestId != null) {
596                 // Current instance was removed from remainingConfigs when case
597                 // started. Mark current instance as pending.
598                 CLog.i("Batch ended with test '%s' current", mCurrentTestId);
599                 if (mPendingResults.get(mCurrentTestId) != null) {
600                     mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig);
601                 } else {
602                     CLog.w("Got unexpected internal state of %s", mCurrentTestId);
603                 }
604             }
605             mCurrentTestId = null;
606         }
607     }
608 
609     /**
610      * dEQP instrumentation parser
611      */
612     private static class InstrumentationParser extends MultiLineReceiver {
613         private TestInstanceResultListener mListener;
614 
615         private Map<String, String> mValues;
616         private String mCurrentName;
617         private String mCurrentValue;
618         private int mResultCode;
619         private boolean mGotExitValue = false;
620         private boolean mParseSuccessful = true;
621 
622 
InstrumentationParser(TestInstanceResultListener listener)623         public InstrumentationParser(TestInstanceResultListener listener) {
624             mListener = listener;
625         }
626 
627         /**
628          * {@inheritDoc}
629          */
630         @Override
processNewLines(String[] lines)631         public void processNewLines(String[] lines) {
632             for (String line : lines) {
633                 if (mValues == null) mValues = new HashMap<String, String>();
634 
635                 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) {
636                     if (mCurrentName != null) {
637                         mValues.put(mCurrentName, mCurrentValue);
638 
639                         mCurrentName = null;
640                         mCurrentValue = null;
641                     }
642 
643                     mParseSuccessful &= mListener.handleStatus(mValues);
644                     mValues = null;
645                 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
646                     if (mCurrentName != null) {
647                         mValues.put(mCurrentName, mCurrentValue);
648 
649                         mCurrentValue = null;
650                         mCurrentName = null;
651                     }
652 
653                     String prefix = "INSTRUMENTATION_STATUS: ";
654                     int nameBegin = prefix.length();
655                     int nameEnd = line.indexOf('=');
656                     if (nameEnd < 0) {
657                         CLog.e("Line does not contain value. Logcat interrupted? (%s)", line);
658                         mCurrentValue = null;
659                         mCurrentName = null;
660                         mParseSuccessful = false;
661                         return;
662                     } else {
663                         int valueBegin = nameEnd + 1;
664                         mCurrentName = line.substring(nameBegin, nameEnd);
665                         mCurrentValue = line.substring(valueBegin);
666                     }
667                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
668                     try {
669                         mResultCode = Integer.parseInt(line.substring(22));
670                         mGotExitValue = true;
671                     } catch (NumberFormatException ex) {
672                         CLog.e("Instrumentation code format unexpected");
673                         mParseSuccessful = false;
674                         return;
675                     }
676                 } else if (mCurrentValue != null) {
677                     mCurrentValue = mCurrentValue + line;
678                 }
679             }
680         }
681 
682         /**
683          * {@inheritDoc}
684          */
685         @Override
done()686         public void done() {
687             if (mCurrentName != null) {
688                 mValues.put(mCurrentName, mCurrentValue);
689 
690                 mCurrentName = null;
691                 mCurrentValue = null;
692             }
693 
694             if (mValues != null) {
695                 mParseSuccessful &= mListener.handleStatus(mValues);
696                 mValues = null;
697             }
698         }
699 
700         /**
701          * {@inheritDoc}
702          */
703         @Override
isCancelled()704         public boolean isCancelled() {
705             return false;
706         }
707 
708         /**
709          * Returns whether target instrumentation exited normally.
710          */
wasSuccessful()711         public boolean wasSuccessful() {
712             return mGotExitValue && mParseSuccessful;
713         }
714 
715         /**
716          * Returns Instrumentation return code
717          */
getResultCode()718         public int getResultCode() {
719             return mResultCode;
720         }
721     }
722 
723     /**
724      * dEQP platfom query instrumentation parser
725      */
726     private static class PlatformQueryInstrumentationParser extends MultiLineReceiver {
727         private Map<String,String> mResultMap = new LinkedHashMap<>();
728         private int mResultCode;
729         private boolean mGotExitValue = false;
730 
731         /**
732          * {@inheritDoc}
733          */
734         @Override
processNewLines(String[] lines)735         public void processNewLines(String[] lines) {
736             for (String line : lines) {
737                 if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
738                     final String parts[] = line.substring(24).split("=",2);
739                     if (parts.length == 2) {
740                         mResultMap.put(parts[0], parts[1]);
741                     } else {
742                         CLog.w("Instrumentation status format unexpected");
743                     }
744                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
745                     try {
746                         mResultCode = Integer.parseInt(line.substring(22));
747                         mGotExitValue = true;
748                     } catch (NumberFormatException ex) {
749                         CLog.w("Instrumentation code format unexpected");
750                     }
751                 }
752             }
753         }
754 
755         /**
756          * {@inheritDoc}
757          */
758         @Override
isCancelled()759         public boolean isCancelled() {
760             return false;
761         }
762 
763         /**
764          * Returns whether target instrumentation exited normally.
765          */
wasSuccessful()766         public boolean wasSuccessful() {
767             return mGotExitValue;
768         }
769 
770         /**
771          * Returns Instrumentation return code
772          */
getResultCode()773         public int getResultCode() {
774             return mResultCode;
775         }
776 
getResultMap()777         public Map<String,String> getResultMap() {
778             return mResultMap;
779         }
780     }
781 
782     /**
783      * Interface for sleeping.
784      *
785      * Exposed for unit testing
786      */
787     public static interface ISleepProvider {
sleep(int milliseconds)788         public void sleep(int milliseconds);
789     }
790 
791     private static class SleepProvider implements ISleepProvider {
sleep(int milliseconds)792         public void sleep(int milliseconds) {
793             try {
794                 Thread.sleep(milliseconds);
795             } catch (InterruptedException ex) {
796             }
797         }
798     }
799 
800     /**
801      * Interface for failure recovery.
802      *
803      * Exposed for unit testing
804      */
805     public static interface IRecovery {
806         /**
807          * Sets the sleep provider IRecovery works on
808          */
setSleepProvider(ISleepProvider sleepProvider)809         public void setSleepProvider(ISleepProvider sleepProvider);
810 
811         /**
812          * Sets the device IRecovery works on
813          */
setDevice(ITestDevice device)814         public void setDevice(ITestDevice device);
815 
816         /**
817          * Informs Recovery that test execution has progressed since the last recovery
818          */
onExecutionProgressed()819         public void onExecutionProgressed();
820 
821         /**
822          * Tries to recover device after failed refused connection.
823          *
824          * @throws DeviceNotAvailableException if recovery did not succeed
825          */
recoverConnectionRefused()826         public void recoverConnectionRefused() throws DeviceNotAvailableException;
827 
828         /**
829          * Tries to recover device after abnormal execution termination or link failure.
830          *
831          * @param progressedSinceLastCall true if test execution has progressed since last call
832          * @throws DeviceNotAvailableException if recovery did not succeed
833          */
recoverComLinkKilled()834         public void recoverComLinkKilled() throws DeviceNotAvailableException;
835     };
836 
837     /**
838      * State machine for execution failure recovery.
839      *
840      * Exposed for unit testing
841      */
842     public static class Recovery implements IRecovery {
843         private int RETRY_COOLDOWN_MS = 6000; // 6 seconds
844         private int PROCESS_KILL_WAIT_MS = 1000; // 1 second
845 
846         private static enum MachineState {
847             WAIT, // recover by waiting
848             RECOVER, // recover by calling recover()
849             REBOOT, // recover by rebooting
850             FAIL, // cannot recover
851         };
852 
853         private MachineState mState = MachineState.WAIT;
854         private ITestDevice mDevice;
855         private ISleepProvider mSleepProvider;
856 
857         private static class ProcessKillFailureException extends Exception {
858         }
859 
860         /**
861          * {@inheritDoc}
862          */
setSleepProvider(ISleepProvider sleepProvider)863         public void setSleepProvider(ISleepProvider sleepProvider) {
864             mSleepProvider = sleepProvider;
865         }
866 
867         /**
868          * {@inheritDoc}
869          */
870         @Override
setDevice(ITestDevice device)871         public void setDevice(ITestDevice device) {
872             mDevice = device;
873         }
874 
875         /**
876          * {@inheritDoc}
877          */
878         @Override
onExecutionProgressed()879         public void onExecutionProgressed() {
880             mState = MachineState.WAIT;
881         }
882 
883         /**
884          * {@inheritDoc}
885          */
886         @Override
recoverConnectionRefused()887         public void recoverConnectionRefused() throws DeviceNotAvailableException {
888             switch (mState) {
889                 case WAIT: // not a valid stratedy for connection refusal, fallthrough
890                 case RECOVER:
891                     // First failure, just try to recover
892                     CLog.w("ADB connection failed, trying to recover");
893                     mState = MachineState.REBOOT; // the next step is to reboot
894 
895                     try {
896                         recoverDevice();
897                     } catch (DeviceNotAvailableException ex) {
898                         // chain forward
899                         recoverConnectionRefused();
900                     }
901                     break;
902 
903                 case REBOOT:
904                     // Second failure in a row, try to reboot
905                     CLog.w("ADB connection failed after recovery, rebooting device");
906                     mState = MachineState.FAIL; // the next step is to fail
907 
908                     try {
909                         rebootDevice();
910                     } catch (DeviceNotAvailableException ex) {
911                         // chain forward
912                         recoverConnectionRefused();
913                     }
914                     break;
915 
916                 case FAIL:
917                     // Third failure in a row, just fail
918                     CLog.w("Cannot recover ADB connection");
919                     throw new DeviceNotAvailableException("failed to connect after reboot");
920             }
921         }
922 
923         /**
924          * {@inheritDoc}
925          */
926         @Override
recoverComLinkKilled()927         public void recoverComLinkKilled() throws DeviceNotAvailableException {
928             switch (mState) {
929                 case WAIT:
930                     // First failure, just try to wait and try again
931                     CLog.w("ADB link failed, retrying after a cooldown period");
932                     mState = MachineState.RECOVER; // the next step is to recover the device
933 
934                     waitCooldown();
935 
936                     // even if the link to deqp on-device process was killed, the process might
937                     // still be alive. Locate and terminate such unwanted processes.
938                     try {
939                         killDeqpProcess();
940                     } catch (DeviceNotAvailableException ex) {
941                         // chain forward
942                         recoverComLinkKilled();
943                     } catch (ProcessKillFailureException ex) {
944                         // chain forward
945                         recoverComLinkKilled();
946                     }
947                     break;
948 
949                 case RECOVER:
950                     // Second failure, just try to recover
951                     CLog.w("ADB link failed, trying to recover");
952                     mState = MachineState.REBOOT; // the next step is to reboot
953 
954                     try {
955                         recoverDevice();
956                         killDeqpProcess();
957                     } catch (DeviceNotAvailableException ex) {
958                         // chain forward
959                         recoverComLinkKilled();
960                     } catch (ProcessKillFailureException ex) {
961                         // chain forward
962                         recoverComLinkKilled();
963                     }
964                     break;
965 
966                 case REBOOT:
967                     // Third failure in a row, try to reboot
968                     CLog.w("ADB link failed after recovery, rebooting device");
969                     mState = MachineState.FAIL; // the next step is to fail
970 
971                     try {
972                         rebootDevice();
973                     } catch (DeviceNotAvailableException ex) {
974                         // chain forward
975                         recoverComLinkKilled();
976                     }
977                     break;
978 
979                 case FAIL:
980                     // Fourth failure in a row, just fail
981                     CLog.w("Cannot recover ADB connection");
982                     throw new DeviceNotAvailableException("link killed after reboot");
983             }
984         }
985 
waitCooldown()986         private void waitCooldown() {
987             mSleepProvider.sleep(RETRY_COOLDOWN_MS);
988         }
989 
getDeqpProcessPids()990         private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException {
991             final List<Integer> pids = new ArrayList<Integer>(2);
992             final String processes = mDevice.executeShellCommand("ps | grep com.drawelements");
993             final String[] lines = processes.split("(\\r|\\n)+");
994             for (String line : lines) {
995                 final String[] fields = line.split("\\s+");
996                 if (fields.length < 2) {
997                     continue;
998                 }
999 
1000                 try {
1001                     final int processId = Integer.parseInt(fields[1], 10);
1002                     pids.add(processId);
1003                 } catch (NumberFormatException ex) {
1004                     continue;
1005                 }
1006             }
1007             return pids;
1008         }
1009 
killDeqpProcess()1010         private void killDeqpProcess() throws DeviceNotAvailableException,
1011                 ProcessKillFailureException {
1012             for (Integer processId : getDeqpProcessPids()) {
1013                 CLog.i("Killing deqp device process with ID %d", processId);
1014                 mDevice.executeShellCommand(String.format("kill -9 %d", processId));
1015             }
1016 
1017             mSleepProvider.sleep(PROCESS_KILL_WAIT_MS);
1018 
1019             // check that processes actually died
1020             if (getDeqpProcessPids().iterator().hasNext()) {
1021                 // a process is still alive, killing failed
1022                 CLog.w("Failed to kill all deqp processes on device");
1023                 throw new ProcessKillFailureException();
1024             }
1025         }
1026 
recoverDevice()1027         public void recoverDevice() throws DeviceNotAvailableException {
1028             // Work around the API. We need to call recoverDevice() on the test device and
1029             // we know that mDevice is a TestDevice. However even though the recoverDevice()
1030             // method is public suggesting it should be publicly accessible, the class itself
1031             // and its super-interface (IManagedTestDevice) are package-private.
1032             final Method recoverDeviceMethod;
1033             try {
1034                 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice");
1035                 recoverDeviceMethod.setAccessible(true);
1036             } catch (NoSuchMethodException ex) {
1037                 throw new AssertionError("Test device must have recoverDevice()");
1038             }
1039 
1040             try {
1041                 recoverDeviceMethod.invoke(mDevice);
1042             } catch (InvocationTargetException ex) {
1043                 if (ex.getCause() instanceof DeviceNotAvailableException) {
1044                     throw (DeviceNotAvailableException)ex.getCause();
1045                 } else if (ex.getCause() instanceof RuntimeException) {
1046                     throw (RuntimeException)ex.getCause();
1047                 } else {
1048                     throw new AssertionError("unexpected throw", ex);
1049                 }
1050             } catch (IllegalAccessException ex) {
1051                 throw new AssertionError("unexpected throw", ex);
1052             }
1053         }
1054 
rebootDevice()1055         private void rebootDevice() throws DeviceNotAvailableException {
1056             mDevice.reboot();
1057         }
1058     }
1059 
1060     /**
1061      * Parse map of instance arguments to map of BatchRunConfigurations
1062      */
parseTestInstances( Collection<TestIdentifier> tests, Map<TestIdentifier, List<Map<String,String>>> testInstances)1063     private static Map<TestIdentifier, Set<BatchRunConfiguration>> parseTestInstances(
1064             Collection<TestIdentifier> tests,
1065             Map<TestIdentifier, List<Map<String,String>>> testInstances) {
1066         final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new HashMap<>();
1067         for (final TestIdentifier test : tests) {
1068             final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>();
1069             if (testInstances.get(test).isEmpty()) {
1070                 // no instances defined, use default
1071                 testInstanceSet.add(DEFAULT_CONFIG);
1072             } else {
1073                 for (Map<String, String> instanceArgs : testInstances.get(test)) {
1074                     testInstanceSet.add(parseRunConfig(instanceArgs));
1075                 }
1076             }
1077             instances.put(test, testInstanceSet);
1078         }
1079         return instances;
1080     }
1081 
parseRunConfig(Map<String,String> instanceArguments)1082     private static BatchRunConfiguration parseRunConfig(Map<String,String> instanceArguments) {
1083         final String glConfig;
1084         final String rotation;
1085         final String surfaceType;
1086 
1087         if (instanceArguments.containsKey("glconfig")) {
1088             glConfig = instanceArguments.get("glconfig");
1089         } else {
1090             glConfig = DEFAULT_CONFIG.getGlConfig();
1091         }
1092         if (instanceArguments.containsKey("rotation")) {
1093             rotation = instanceArguments.get("rotation");
1094         } else {
1095             rotation = DEFAULT_CONFIG.getRotation();
1096         }
1097         if (instanceArguments.containsKey("surfaceType")) {
1098             surfaceType = instanceArguments.get("surfaceType");
1099         } else {
1100             surfaceType = DEFAULT_CONFIG.getSurfaceType();
1101         }
1102 
1103         return new BatchRunConfiguration(glConfig, rotation, surfaceType);
1104     }
1105 
getTestRunConfigs(TestIdentifier testId)1106     private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) {
1107         return mTestInstances.get(testId);
1108     }
1109 
1110     /**
1111      * Converts dEQP testcase path to TestIdentifier.
1112      */
pathToIdentifier(String testPath)1113     private static TestIdentifier pathToIdentifier(String testPath) {
1114         String[] components = testPath.split("\\.");
1115         String name = components[components.length - 1];
1116         String className = null;
1117 
1118         for (int i = 0; i < components.length - 1; i++) {
1119             if (className == null) {
1120                 className = components[i];
1121             } else {
1122                 className = className + "." + components[i];
1123             }
1124         }
1125 
1126         return new TestIdentifier(className, name);
1127     }
1128 
getId()1129     private String getId() {
1130         return AbiUtils.createId(mAbi.getName(), mPackageName);
1131     }
1132 
1133     /**
1134      * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute.
1135      */
generateTestCaseTrieFromPaths(Collection<String> tests)1136     private static String generateTestCaseTrieFromPaths(Collection<String> tests) {
1137         String result = "{";
1138         boolean first = true;
1139 
1140         // Add testcases to results
1141         for (Iterator<String> iter = tests.iterator(); iter.hasNext();) {
1142             String test = iter.next();
1143             String[] components = test.split("\\.");
1144 
1145             if (components.length == 1) {
1146                 if (!first) {
1147                     result = result + ",";
1148                 }
1149                 first = false;
1150 
1151                 result += components[0];
1152                 iter.remove();
1153             }
1154         }
1155 
1156         if (!tests.isEmpty()) {
1157             HashMap<String, ArrayList<String> > testGroups = new HashMap<>();
1158 
1159             // Collect all sub testgroups
1160             for (String test : tests) {
1161                 String[] components = test.split("\\.");
1162                 ArrayList<String> testGroup = testGroups.get(components[0]);
1163 
1164                 if (testGroup == null) {
1165                     testGroup = new ArrayList<String>();
1166                     testGroups.put(components[0], testGroup);
1167                 }
1168 
1169                 testGroup.add(test.substring(components[0].length()+1));
1170             }
1171 
1172             for (String testGroup : testGroups.keySet()) {
1173                 if (!first) {
1174                     result = result + ",";
1175                 }
1176 
1177                 first = false;
1178                 result = result + testGroup
1179                         + generateTestCaseTrieFromPaths(testGroups.get(testGroup));
1180             }
1181         }
1182 
1183         return result + "}";
1184     }
1185 
1186     /**
1187      * Generates testcase trie from TestIdentifiers.
1188      */
generateTestCaseTrie(Collection<TestIdentifier> tests)1189     private static String generateTestCaseTrie(Collection<TestIdentifier> tests) {
1190         ArrayList<String> testPaths = new ArrayList<String>();
1191 
1192         for (TestIdentifier test : tests) {
1193             testPaths.add(test.getClassName() + "." + test.getTestName());
1194         }
1195 
1196         return generateTestCaseTrieFromPaths(testPaths);
1197     }
1198 
1199     private static class TestBatch {
1200         public BatchRunConfiguration config;
1201         public List<TestIdentifier> tests;
1202     }
1203 
selectRunBatch()1204     private TestBatch selectRunBatch() {
1205         return selectRunBatch(mRemainingTests, null);
1206     }
1207 
1208     /**
1209      * Creates a TestBatch from the given tests or null if not tests remaining.
1210      *
1211      *  @param pool List of tests to select from
1212      *  @param requiredConfig Select only instances with pending requiredConfig, or null to select
1213      *         any run configuration.
1214      */
selectRunBatch(Collection<TestIdentifier> pool, BatchRunConfiguration requiredConfig)1215     private TestBatch selectRunBatch(Collection<TestIdentifier> pool,
1216             BatchRunConfiguration requiredConfig) {
1217         // select one test (leading test) that is going to be executed and then pack along as many
1218         // other compatible instances as possible.
1219 
1220         TestIdentifier leadingTest = null;
1221         for (TestIdentifier test : pool) {
1222             if (!mRemainingTests.contains(test)) {
1223                 continue;
1224             }
1225             if (requiredConfig != null &&
1226                     !mInstanceListerner.isPendingTestInstance(test, requiredConfig)) {
1227                 continue;
1228             }
1229             leadingTest = test;
1230             break;
1231         }
1232 
1233         // no remaining tests?
1234         if (leadingTest == null) {
1235             return null;
1236         }
1237 
1238         BatchRunConfiguration leadingTestConfig = null;
1239         if (requiredConfig != null) {
1240             leadingTestConfig = requiredConfig;
1241         } else {
1242             for (BatchRunConfiguration runConfig : getTestRunConfigs(leadingTest)) {
1243                 if (mInstanceListerner.isPendingTestInstance(leadingTest, runConfig)) {
1244                     leadingTestConfig = runConfig;
1245                     break;
1246                 }
1247             }
1248         }
1249 
1250         // test pending <=> test has a pending config
1251         if (leadingTestConfig == null) {
1252             throw new AssertionError("search postcondition failed");
1253         }
1254 
1255         final int leadingInstability = getTestInstabilityRating(leadingTest);
1256 
1257         final TestBatch runBatch = new TestBatch();
1258         runBatch.config = leadingTestConfig;
1259         runBatch.tests = new ArrayList<>();
1260         runBatch.tests.add(leadingTest);
1261 
1262         for (TestIdentifier test : pool) {
1263             if (test == leadingTest) {
1264                 // do not re-select the leading tests
1265                 continue;
1266             }
1267             if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) {
1268                 // select only compatible
1269                 continue;
1270             }
1271             if (getTestInstabilityRating(test) != leadingInstability) {
1272                 // pack along only cases in the same stability category. Packing more dangerous
1273                 // tests along jeopardizes the stability of this run. Packing more stable tests
1274                 // along jeopardizes their stability rating.
1275                 continue;
1276             }
1277             if (runBatch.tests.size() >= getBatchSizeLimitForInstability(leadingInstability)) {
1278                 // batch size is limited.
1279                 break;
1280             }
1281             runBatch.tests.add(test);
1282         }
1283 
1284         return runBatch;
1285     }
1286 
getBatchNumPendingCases(TestBatch batch)1287     private int getBatchNumPendingCases(TestBatch batch) {
1288         int numPending = 0;
1289         for (TestIdentifier test : batch.tests) {
1290             if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1291                 ++numPending;
1292             }
1293         }
1294         return numPending;
1295     }
1296 
getBatchSizeLimitForInstability(int batchInstabilityRating)1297     private int getBatchSizeLimitForInstability(int batchInstabilityRating) {
1298         // reduce group size exponentially down to one
1299         return Math.max(1, TESTCASE_BATCH_LIMIT / (1 << batchInstabilityRating));
1300     }
1301 
getTestInstabilityRating(TestIdentifier testId)1302     private int getTestInstabilityRating(TestIdentifier testId) {
1303         if (mTestInstabilityRatings.containsKey(testId)) {
1304             return mTestInstabilityRatings.get(testId);
1305         } else {
1306             return 0;
1307         }
1308     }
1309 
recordTestInstability(TestIdentifier testId)1310     private void recordTestInstability(TestIdentifier testId) {
1311         mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
1312     }
1313 
clearTestInstability(TestIdentifier testId)1314     private void clearTestInstability(TestIdentifier testId) {
1315         mTestInstabilityRatings.put(testId, 0);
1316     }
1317 
1318     /**
1319      * Executes all tests on the device.
1320      */
runTests()1321     private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException {
1322         for (;;) {
1323             TestBatch batch = selectRunBatch();
1324 
1325             if (batch == null) {
1326                 break;
1327             }
1328 
1329             runTestRunBatch(batch);
1330         }
1331     }
1332 
1333     /**
1334      * Runs a TestBatch by either faking it or executing it on a device.
1335      */
runTestRunBatch(TestBatch batch)1336     private void runTestRunBatch(TestBatch batch) throws DeviceNotAvailableException,
1337             CapabilityQueryFailureException {
1338         // prepare instance listener
1339         mInstanceListerner.setCurrentConfig(batch.config);
1340         for (TestIdentifier test : batch.tests) {
1341             mInstanceListerner.setTestInstances(test, getTestRunConfigs(test));
1342         }
1343 
1344         // execute only if config is executable, else fake results
1345         if (isSupportedRunConfiguration(batch.config)) {
1346             executeTestRunBatch(batch);
1347         } else {
1348             fakePassTestRunBatch(batch);
1349         }
1350     }
1351 
isSupportedRunConfiguration(BatchRunConfiguration runConfig)1352     private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig)
1353             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1354         // orientation support
1355         if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) {
1356             final Set<String> features = getDeviceFeatures(mDevice);
1357 
1358             if (isPortraitClassRotation(runConfig.getRotation()) &&
1359                     !features.contains(FEATURE_PORTRAIT)) {
1360                 return false;
1361             }
1362             if (isLandscapeClassRotation(runConfig.getRotation()) &&
1363                     !features.contains(FEATURE_LANDSCAPE)) {
1364                 return false;
1365             }
1366         }
1367 
1368         if (isOpenGlEsPackage()) {
1369             // renderability support for OpenGL ES tests
1370             return isSupportedGlesRenderConfig(runConfig);
1371         } else {
1372             return true;
1373         }
1374     }
1375 
1376     private static final class AdbComLinkOpenError extends Exception {
AdbComLinkOpenError(String description, Throwable inner)1377         public AdbComLinkOpenError(String description, Throwable inner) {
1378             super(description, inner);
1379         }
1380     }
1381 
1382     private static final class AdbComLinkKilledError extends Exception {
AdbComLinkKilledError(String description, Throwable inner)1383         public AdbComLinkKilledError(String description, Throwable inner) {
1384             super(description, inner);
1385         }
1386     }
1387 
1388     /**
1389      * Executes a given command in adb shell
1390      *
1391      * @throws AdbComLinkOpenError if connection cannot be established.
1392      * @throws AdbComLinkKilledError if established connection is killed prematurely.
1393      */
executeShellCommandAndReadOutput(final String command, final IShellOutputReceiver receiver)1394     private void executeShellCommandAndReadOutput(final String command,
1395             final IShellOutputReceiver receiver)
1396             throws AdbComLinkOpenError, AdbComLinkKilledError {
1397         try {
1398             mDevice.getIDevice().executeShellCommand(command, receiver,
1399                     UNRESPOSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1400         } catch (TimeoutException ex) {
1401             // Opening connection timed out
1402             CLog.e("Opening connection timed out for command: '%s'", command);
1403             throw new AdbComLinkOpenError("opening connection timed out", ex);
1404         } catch (AdbCommandRejectedException ex) {
1405             // Command rejected
1406             CLog.e("Device rejected command: '%s'", command);
1407             throw new AdbComLinkOpenError("command rejected", ex);
1408         } catch (IOException ex) {
1409             // shell command channel killed
1410             CLog.e("Channel died for command: '%s'", command);
1411             throw new AdbComLinkKilledError("command link killed", ex);
1412         } catch (ShellCommandUnresponsiveException ex) {
1413             // shell command halted
1414             CLog.e("No output from command in %d ms: '%s'", UNRESPOSIVE_CMD_TIMEOUT_MS, command);
1415             throw new AdbComLinkKilledError("command link hung", ex);
1416         }
1417     }
1418 
1419     /**
1420      * Executes given test batch on a device
1421      */
executeTestRunBatch(TestBatch batch)1422     private void executeTestRunBatch(TestBatch batch) throws DeviceNotAvailableException {
1423         // attempt full run once
1424         executeTestRunBatchRun(batch);
1425 
1426         // split remaining tests to two sub batches and execute both. This will terminate
1427         // since executeTestRunBatchRun will always progress for a batch of size 1.
1428         final ArrayList<TestIdentifier> pendingTests = new ArrayList<>();
1429 
1430         for (TestIdentifier test : batch.tests) {
1431             if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1432                 pendingTests.add(test);
1433             }
1434         }
1435 
1436         final int divisorNdx = pendingTests.size() / 2;
1437         final List<TestIdentifier> headList = pendingTests.subList(0, divisorNdx);
1438         final List<TestIdentifier> tailList = pendingTests.subList(divisorNdx, pendingTests.size());
1439 
1440         // head
1441         for (;;) {
1442             TestBatch subBatch = selectRunBatch(headList, batch.config);
1443 
1444             if (subBatch == null) {
1445                 break;
1446             }
1447 
1448             executeTestRunBatch(subBatch);
1449         }
1450 
1451         // tail
1452         for (;;) {
1453             TestBatch subBatch = selectRunBatch(tailList, batch.config);
1454 
1455             if (subBatch == null) {
1456                 break;
1457             }
1458 
1459             executeTestRunBatch(subBatch);
1460         }
1461 
1462         if (getBatchNumPendingCases(batch) != 0) {
1463             throw new AssertionError("executeTestRunBatch postcondition failed");
1464         }
1465     }
1466 
1467     /**
1468      * Runs one execution pass over the given batch.
1469      *
1470      * Tries to run the batch. Always makes progress (executes instances or modifies stability
1471      * scores).
1472      */
executeTestRunBatchRun(TestBatch batch)1473     private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException {
1474         if (getBatchNumPendingCases(batch) != batch.tests.size()) {
1475             throw new AssertionError("executeTestRunBatchRun precondition failed");
1476         }
1477 
1478         checkInterrupted(); // throws if interrupted
1479 
1480         final String testCases = generateTestCaseTrie(batch.tests);
1481 
1482         mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
1483         mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
1484         mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME);
1485 
1486         final String instrumentationName =
1487                 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation";
1488 
1489         final StringBuilder deqpCmdLine = new StringBuilder();
1490         deqpCmdLine.append("--deqp-caselist-file=");
1491         deqpCmdLine.append(CASE_LIST_FILE_NAME);
1492         deqpCmdLine.append(" ");
1493         deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config));
1494 
1495         // If we are not logging data, do not bother outputting the images from the test exe.
1496         if (!mLogData) {
1497             deqpCmdLine.append(" --deqp-log-images=disable");
1498         }
1499 
1500         deqpCmdLine.append(" --deqp-watchdog=enable");
1501 
1502         final String command = String.format(
1503                 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\""
1504                     + " -e deqpLogData \"%s\" %s",
1505                 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(),
1506                 mLogData, instrumentationName);
1507 
1508         final int numRemainingInstancesBefore = getNumRemainingInstances();
1509         final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner);
1510         Throwable interruptingError = null;
1511 
1512         try {
1513             CLog.d("Running command '%s'", command);
1514             executeShellCommandAndReadOutput(command, parser);
1515             parser.flush();
1516         } catch (Throwable ex) {
1517             CLog.w("Instrumented call threw '%s'", ex.getMessage());
1518             interruptingError = ex;
1519         }
1520 
1521         final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null ||
1522                 getNumRemainingInstances() < numRemainingInstancesBefore;
1523 
1524         if (progressedSinceLastCall) {
1525             mDeviceRecovery.onExecutionProgressed();
1526         }
1527 
1528         // interrupted, try to recover
1529         if (interruptingError != null) {
1530             if (interruptingError instanceof AdbComLinkOpenError) {
1531                 CLog.i("Recovering from comm link error");
1532                 mDeviceRecovery.recoverConnectionRefused();
1533             } else if (interruptingError instanceof AdbComLinkKilledError) {
1534                 CLog.i("Recovering from comm link killed");
1535                 mDeviceRecovery.recoverComLinkKilled();
1536             } else if (interruptingError instanceof RunInterruptedException) {
1537                 // external run interruption request. Terminate immediately.
1538                 CLog.i("Run termination requested. Throwing forward.");
1539                 throw (RunInterruptedException)interruptingError;
1540             } else {
1541                 CLog.e(interruptingError);
1542                 throw new RuntimeException(interruptingError);
1543             }
1544 
1545             // recoverXXX did not throw => recovery succeeded
1546         } else if (!parser.wasSuccessful()) {
1547             CLog.i("Parse not successful. Will attempt comm link recovery.");
1548             mDeviceRecovery.recoverComLinkKilled();
1549             // recoverXXX did not throw => recovery succeeded
1550         }
1551 
1552         // Progress guarantees.
1553         if (batch.tests.size() == 1) {
1554             final TestIdentifier onlyTest = batch.tests.iterator().next();
1555             final boolean wasTestExecuted =
1556                     !mInstanceListerner.isPendingTestInstance(onlyTest, batch.config) &&
1557                     mInstanceListerner.getCurrentTestId() == null;
1558             final boolean wasLinkFailure = !parser.wasSuccessful() || interruptingError != null;
1559 
1560             // Link failures can be caused by external events, require at least two observations
1561             // until bailing.
1562             if (!wasTestExecuted && (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) {
1563                 recordTestInstability(onlyTest);
1564                 // If we cannot finish the test, mark the case as a crash.
1565                 //
1566                 // If we couldn't even start the test, fail the test instance as non-executable.
1567                 // This is required so that a consistently crashing or non-existent tests will
1568                 // not cause futile (non-terminating) re-execution attempts.
1569                 if (mInstanceListerner.getCurrentTestId() != null) {
1570                     CLog.w("Test '%s' started, but not completed", onlyTest);
1571                     mInstanceListerner.abortTest(onlyTest, INCOMPLETE_LOG_MESSAGE);
1572                 } else {
1573                     CLog.w("Test '%s' could not start", onlyTest);
1574                     mInstanceListerner.abortTest(onlyTest, NOT_EXECUTABLE_LOG_MESSAGE);
1575                 }
1576             } else if (wasTestExecuted) {
1577                 clearTestInstability(onlyTest);
1578             }
1579         }
1580         else
1581         {
1582             // Analyze results to update test stability ratings. If there is no interrupting test
1583             // logged, increase instability rating of all remaining tests. If there is a
1584             // interrupting test logged, increase only its instability rating.
1585             //
1586             // A successful run of tests clears instability rating.
1587             if (mInstanceListerner.getCurrentTestId() == null) {
1588                 for (TestIdentifier test : batch.tests) {
1589                     if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1590                         recordTestInstability(test);
1591                     } else {
1592                         clearTestInstability(test);
1593                     }
1594                 }
1595             } else {
1596                 recordTestInstability(mInstanceListerner.getCurrentTestId());
1597                 for (TestIdentifier test : batch.tests) {
1598                     // \note: isPendingTestInstance is false for getCurrentTestId. Current ID is
1599                     // considered 'running' and will be restored to 'pending' in endBatch().
1600                     if (!test.equals(mInstanceListerner.getCurrentTestId()) &&
1601                             !mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1602                         clearTestInstability(test);
1603                     }
1604                 }
1605             }
1606         }
1607 
1608         mInstanceListerner.endBatch();
1609     }
1610 
getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig)1611     private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) {
1612         final StringBuilder deqpCmdLine = new StringBuilder();
1613         if (!runConfig.getGlConfig().isEmpty()) {
1614             deqpCmdLine.append("--deqp-gl-config-name=");
1615             deqpCmdLine.append(runConfig.getGlConfig());
1616         }
1617         if (!runConfig.getRotation().isEmpty()) {
1618             if (deqpCmdLine.length() != 0) {
1619                 deqpCmdLine.append(" ");
1620             }
1621             deqpCmdLine.append("--deqp-screen-rotation=");
1622             deqpCmdLine.append(runConfig.getRotation());
1623         }
1624         if (!runConfig.getSurfaceType().isEmpty()) {
1625             if (deqpCmdLine.length() != 0) {
1626                 deqpCmdLine.append(" ");
1627             }
1628             deqpCmdLine.append("--deqp-surface-type=");
1629             deqpCmdLine.append(runConfig.getSurfaceType());
1630         }
1631         return deqpCmdLine.toString();
1632     }
1633 
getNumRemainingInstances()1634     private int getNumRemainingInstances() {
1635         int retVal = 0;
1636         for (TestIdentifier testId : mRemainingTests) {
1637             // If case is in current working set, sum only not yet executed instances.
1638             // If case is not in current working set, sum all instances (since they are not yet
1639             // executed).
1640             if (mInstanceListerner.mPendingResults.containsKey(testId)) {
1641                 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size();
1642             } else {
1643                 retVal += mTestInstances.get(testId).size();
1644             }
1645         }
1646         return retVal;
1647     }
1648 
1649     /**
1650      * Checks if this execution has been marked as interrupted and throws if it has.
1651      */
checkInterrupted()1652     private void checkInterrupted() throws RunInterruptedException {
1653         // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly
1654         // by sleeping a value <= 0.
1655         mRunUtil.sleep(0);
1656     }
1657 
1658     /**
1659      * Pass given batch tests without running it
1660      */
fakePassTestRunBatch(TestBatch batch)1661     private void fakePassTestRunBatch(TestBatch batch) {
1662         for (TestIdentifier test : batch.tests) {
1663             CLog.d("Skipping test '%s' invocation in config '%s'", test.toString(),
1664                     batch.config.getId());
1665             mInstanceListerner.skipTest(test);
1666         }
1667     }
1668 
1669     /**
1670      * Pass all remaining tests without running them
1671      */
fakePassTests(ITestInvocationListener listener)1672     private void fakePassTests(ITestInvocationListener listener) {
1673         Map <String, String> emptyMap = Collections.emptyMap();
1674         for (TestIdentifier test : mRemainingTests) {
1675             CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString());
1676             listener.testStarted(test);
1677             listener.testEnded(test, emptyMap);
1678         }
1679         mRemainingTests.clear();
1680     }
1681 
1682     /**
1683      * Check if device supports OpenGL ES version.
1684      */
isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)1685     private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion,
1686             int requiredMinorVersion) throws DeviceNotAvailableException {
1687         String roOpenglesVersion = device.getProperty("ro.opengles.version");
1688 
1689         if (roOpenglesVersion == null)
1690             return false;
1691 
1692         int intValue = Integer.parseInt(roOpenglesVersion);
1693 
1694         int majorVersion = ((intValue & 0xffff0000) >> 16);
1695         int minorVersion = (intValue & 0xffff);
1696 
1697         return (majorVersion > requiredMajorVersion)
1698                 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion);
1699     }
1700 
1701     /**
1702      * Query if rendertarget is supported
1703      */
isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)1704     private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
1705             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1706         // query if configuration is supported
1707         final StringBuilder configCommandLine =
1708                 new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
1709         if (configCommandLine.length() != 0) {
1710             configCommandLine.append(" ");
1711         }
1712         configCommandLine.append("--deqp-gl-major-version=");
1713         configCommandLine.append(getGlesMajorVersion());
1714         configCommandLine.append(" --deqp-gl-minor-version=");
1715         configCommandLine.append(getGlesMinorVersion());
1716 
1717         final String commandLine = configCommandLine.toString();
1718 
1719         // check for cached result first
1720         if (mConfigQuerySupportCache.containsKey(commandLine)) {
1721             return mConfigQuerySupportCache.get(commandLine);
1722         }
1723 
1724         final boolean supported = queryIsSupportedConfigCommandLine(commandLine);
1725         mConfigQuerySupportCache.put(commandLine, supported);
1726         return supported;
1727     }
1728 
queryIsSupportedConfigCommandLine(String deqpCommandLine)1729     private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
1730             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1731         final String instrumentationName =
1732                 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
1733         final String command = String.format(
1734                 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
1735                     + " %s",
1736                 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName);
1737 
1738         final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser();
1739         mDevice.executeShellCommand(command, parser);
1740         parser.flush();
1741 
1742         if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
1743                 parser.getResultMap().containsKey("Supported")) {
1744             if ("Yes".equals(parser.getResultMap().get("Supported"))) {
1745                 return true;
1746             } else if ("No".equals(parser.getResultMap().get("Supported"))) {
1747                 return false;
1748             } else {
1749                 CLog.e("Capability query did not return a result");
1750                 throw new CapabilityQueryFailureException();
1751             }
1752         } else if (parser.wasSuccessful()) {
1753             CLog.e("Failed to run capability query. Code: %d, Result: %s",
1754                     parser.getResultCode(), parser.getResultMap().toString());
1755             throw new CapabilityQueryFailureException();
1756         } else {
1757             CLog.e("Failed to run capability query");
1758             throw new CapabilityQueryFailureException();
1759         }
1760     }
1761 
1762     /**
1763      * Return feature set supported by the device
1764      */
getDeviceFeatures(ITestDevice device)1765     private Set<String> getDeviceFeatures(ITestDevice device)
1766             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1767         if (mDeviceFeatures == null) {
1768             mDeviceFeatures = queryDeviceFeatures(device);
1769         }
1770         return mDeviceFeatures;
1771     }
1772 
1773     /**
1774      * Query feature set supported by the device
1775      */
queryDeviceFeatures(ITestDevice device)1776     private static Set<String> queryDeviceFeatures(ITestDevice device)
1777             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1778         // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
1779         // TODO: Move this logic to ITestDevice.
1780         String command = "pm list features";
1781         String commandOutput = device.executeShellCommand(command);
1782 
1783         // Extract the id of the new user.
1784         HashSet<String> availableFeatures = new HashSet<>();
1785         for (String feature: commandOutput.split("\\s+")) {
1786             // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
1787             String[] tokens = feature.split(":");
1788             if (tokens.length < 2 || !"feature".equals(tokens[0])) {
1789                 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]);
1790                 throw new CapabilityQueryFailureException();
1791             }
1792             availableFeatures.add(tokens[1]);
1793         }
1794         return availableFeatures;
1795     }
1796 
isPortraitClassRotation(String rotation)1797     private boolean isPortraitClassRotation(String rotation) {
1798         return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
1799                 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
1800     }
1801 
isLandscapeClassRotation(String rotation)1802     private boolean isLandscapeClassRotation(String rotation) {
1803         return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
1804                 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
1805     }
1806 
1807     /**
1808      * Install dEQP OnDevice Package
1809      */
installTestApk()1810     private void installTestApk() throws DeviceNotAvailableException {
1811         try {
1812             File apkFile = mCtsBuild.getTestApp(DEQP_ONDEVICE_APK);
1813             String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
1814             String errorCode = getDevice().installPackage(apkFile, true, options);
1815             if (errorCode != null) {
1816                 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode);
1817             }
1818         } catch (FileNotFoundException e) {
1819             CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK);
1820         }
1821     }
1822 
1823     /**
1824      * Uninstall dEQP OnDevice Package
1825      */
uninstallTestApk()1826     private void uninstallTestApk() throws DeviceNotAvailableException {
1827         getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
1828     }
1829 
1830     /**
1831      * Parse gl nature from package name
1832      */
isOpenGlEsPackage()1833     private boolean isOpenGlEsPackage() {
1834         if ("dEQP-GLES2".equals(mName) || "dEQP-GLES3".equals(mName) ||
1835                 "dEQP-GLES31".equals(mName)) {
1836             return true;
1837         } else if ("dEQP-EGL".equals(mName)) {
1838             return false;
1839         } else {
1840             throw new IllegalStateException("dEQP runner was created with illegal name");
1841         }
1842     }
1843 
1844     /**
1845      * Check GL support (based on package name)
1846      */
isSupportedGles()1847     private boolean isSupportedGles() throws DeviceNotAvailableException {
1848         return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion());
1849     }
1850 
1851     /**
1852      * Get GL major version (based on package name)
1853      */
getGlesMajorVersion()1854     private int getGlesMajorVersion() throws DeviceNotAvailableException {
1855         if ("dEQP-GLES2".equals(mName)) {
1856             return 2;
1857         } else if ("dEQP-GLES3".equals(mName)) {
1858             return 3;
1859         } else if ("dEQP-GLES31".equals(mName)) {
1860             return 3;
1861         } else {
1862             throw new IllegalStateException("getGlesMajorVersion called for non gles pkg");
1863         }
1864     }
1865 
1866     /**
1867      * Get GL minor version (based on package name)
1868      */
getGlesMinorVersion()1869     private int getGlesMinorVersion() throws DeviceNotAvailableException {
1870         if ("dEQP-GLES2".equals(mName)) {
1871             return 0;
1872         } else if ("dEQP-GLES3".equals(mName)) {
1873             return 0;
1874         } else if ("dEQP-GLES31".equals(mName)) {
1875             return 1;
1876         } else {
1877             throw new IllegalStateException("getGlesMinorVersion called for non gles pkg");
1878         }
1879     }
1880 
1881     /**
1882      * {@inheritDoc}
1883      */
1884     @Override
run(ITestInvocationListener listener)1885     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
1886         final Map<String, String> emptyMap = Collections.emptyMap();
1887         final boolean isSupportedApi = !isOpenGlEsPackage() || isSupportedGles();
1888 
1889         listener.testRunStarted(getId(), mRemainingTests.size());
1890 
1891         try {
1892             if (isSupportedApi) {
1893                 // Make sure there is no pre-existing package form earlier interrupted test run.
1894                 uninstallTestApk();
1895                 installTestApk();
1896 
1897                 mInstanceListerner.setSink(listener);
1898                 mDeviceRecovery.setDevice(mDevice);
1899                 runTests();
1900 
1901                 uninstallTestApk();
1902             } else {
1903                 // Pass all tests if OpenGL ES version is not supported
1904                 CLog.i("Package %s not supported by the device. Tests trivially pass.", mPackageName);
1905                 fakePassTests(listener);
1906             }
1907         } catch (CapabilityQueryFailureException ex) {
1908             // Platform is not behaving correctly, for example crashing when trying to create
1909             // a window. Instead of silenty failing, signal failure by leaving the rest of the
1910             // test cases in "NotExecuted" state
1911             uninstallTestApk();
1912         }
1913 
1914         listener.testRunEnded(0, emptyMap);
1915     }
1916 }
1917