1package com.android.cts.tradefed.testtype;
2
3import com.android.compatibility.common.util.AbiUtils;
4import com.android.cts.tradefed.build.CtsBuildHelper;
5import com.android.ddmlib.AdbCommandRejectedException;
6import com.android.ddmlib.IShellOutputReceiver;
7import com.android.ddmlib.MultiLineReceiver;
8import com.android.ddmlib.ShellCommandUnresponsiveException;
9import com.android.ddmlib.TimeoutException;
10import com.android.ddmlib.testrunner.TestIdentifier;
11import com.android.tradefed.build.IBuildInfo;
12import com.android.tradefed.device.DeviceNotAvailableException;
13import com.android.tradefed.device.ITestDevice;
14import com.android.tradefed.log.LogUtil.CLog;
15import com.android.tradefed.result.ByteArrayInputStreamSource;
16import com.android.tradefed.result.ITestInvocationListener;
17import com.android.tradefed.result.LogDataType;
18import com.android.tradefed.testtype.IAbi;
19import com.android.tradefed.testtype.IBuildReceiver;
20import com.android.tradefed.testtype.IDeviceTest;
21import com.android.tradefed.testtype.IRemoteTest;
22import com.android.tradefed.util.IRunUtil;
23import com.android.tradefed.util.RunInterruptedException;
24import com.android.tradefed.util.RunUtil;
25
26import java.io.File;
27import java.io.FileNotFoundException;
28import java.io.IOException;
29import java.lang.reflect.Method;
30import java.lang.reflect.InvocationTargetException;
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.Iterator;
37import java.util.LinkedHashMap;
38import java.util.LinkedHashSet;
39import java.util.LinkedList;
40import java.util.List;
41import java.util.Map;
42import java.util.Set;
43import 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 */
50public 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    {
84        mDeviceRecovery.setSleepProvider(new SleepProvider());
85    }
86
87    public DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests,
88            Map<TestIdentifier, List<Map<String,String>>> testInstances) {
89        mPackageName = packageName;
90        mName = name;
91        mRemainingTests = new LinkedList<>(tests); // avoid modifying arguments
92        mTestInstances = parseTestInstances(tests, testInstances);
93        mTestInstabilityRatings = new HashMap<>();
94    }
95
96    /**
97     * @param abi the ABI to run the test on
98     */
99    public void setAbi(IAbi abi) {
100        mAbi = abi;
101    }
102
103    /**
104     * {@inheritDoc}
105     */
106    @Override
107    public void setBuild(IBuildInfo buildInfo) {
108        mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
109    }
110
111    /**
112     * Set the CTS build container.
113     * <p/>
114     * Exposed so unit tests can mock the provided build.
115     *
116     * @param buildHelper
117     */
118    public void setBuildHelper(CtsBuildHelper buildHelper) {
119        mCtsBuild = buildHelper;
120    }
121
122    /**
123     * Enable or disable raw dEQP test log collection.
124     */
125    public void setCollectLogs(boolean logData) {
126        mLogData = logData;
127    }
128
129    /**
130     * {@inheritDoc}
131     */
132    @Override
133    public void setDevice(ITestDevice device) {
134        mDevice = device;
135    }
136
137    /**
138     * {@inheritDoc}
139     */
140    @Override
141    public ITestDevice getDevice() {
142        return mDevice;
143    }
144
145    /**
146     * Set recovery handler.
147     *
148     * Exposed for unit testing.
149     */
150    public void setRecovery(IRecovery deviceRecovery) {
151        mDeviceRecovery = deviceRecovery;
152    }
153
154    /**
155     * Set IRunUtil.
156     *
157     * Exposed for unit testing.
158     */
159    public void setRunUtil(IRunUtil runUtil) {
160        mRunUtil = runUtil;
161    }
162
163    private static final class CapabilityQueryFailureException extends Exception {
164    }
165
166    /**
167     * Test configuration of dEPQ test instance execution.
168     * Exposed for unit testing
169     */
170    public static final class BatchRunConfiguration {
171        public static final String ROTATION_UNSPECIFIED = "unspecified";
172        public static final String ROTATION_PORTRAIT = "0";
173        public static final String ROTATION_LANDSCAPE = "90";
174        public static final String ROTATION_REVERSE_PORTRAIT = "180";
175        public static final String ROTATION_REVERSE_LANDSCAPE = "270";
176
177        private final String mGlConfig;
178        private final String mRotation;
179        private final String mSurfaceType;
180
181        public BatchRunConfiguration(String glConfig, String rotation, String surfaceType) {
182            mGlConfig = glConfig;
183            mRotation = rotation;
184            mSurfaceType = surfaceType;
185        }
186
187        /**
188         * Get string that uniquely identifies this config
189         */
190        public String getId() {
191            return String.format("{glformat=%s,rotation=%s,surfacetype=%s}",
192                    mGlConfig, mRotation, mSurfaceType);
193        }
194
195        /**
196         * Get the GL config used in this configuration.
197         */
198        public String getGlConfig() {
199            return mGlConfig;
200        }
201
202        /**
203         * Get the screen rotation used in this configuration.
204         */
205        public String getRotation() {
206            return mRotation;
207        }
208
209        /**
210         * Get the surface type used in this configuration.
211         */
212        public String getSurfaceType() {
213            return mSurfaceType;
214        }
215
216        @Override
217        public boolean equals(Object other) {
218            if (other == null) {
219                return false;
220            } else if (!(other instanceof BatchRunConfiguration)) {
221                return false;
222            } else {
223                return getId().equals(((BatchRunConfiguration)other).getId());
224            }
225        }
226
227        @Override
228        public int hashCode() {
229            return getId().hashCode();
230        }
231    }
232
233    /**
234     * dEQP test instance listerer and invocation result forwarded
235     */
236    private class TestInstanceResultListener {
237        private ITestInvocationListener mSink;
238        private BatchRunConfiguration mRunConfig;
239
240        private TestIdentifier mCurrentTestId;
241        private boolean mGotTestResult;
242        private String mCurrentTestLog;
243
244        private class PendingResult {
245            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
253        public void setSink(ITestInvocationListener sink) {
254            mSink = sink;
255        }
256
257        public void setCurrentConfig(BatchRunConfiguration runConfig) {
258            mRunConfig = runConfig;
259        }
260
261        /**
262         * Get currently processed test id, or null if not currently processing a test case
263         */
264        public TestIdentifier getCurrentTestId() {
265            return mCurrentTestId;
266        }
267
268        /**
269         * Forward result to sink
270         */
271        private void forwardFinalizedPendingResult(TestIdentifier testId) {
272            if (mRemainingTests.contains(testId)) {
273                final PendingResult result = mPendingResults.get(testId);
274
275                mPendingResults.remove(testId);
276                mRemainingTests.remove(testId);
277
278                // Forward results to the sink
279                mSink.testStarted(testId);
280
281                // Test Log
282                if (mLogData) {
283                    for (Map.Entry<BatchRunConfiguration, String> entry :
284                            result.testLogs.entrySet()) {
285                        final ByteArrayInputStreamSource source
286                                = new ByteArrayInputStreamSource(entry.getValue().getBytes());
287
288                        mSink.testLog(testId.getClassName() + "." + testId.getTestName() + "@"
289                                + entry.getKey().getId(), LogDataType.XML, source);
290
291                        source.cancel();
292                    }
293                }
294
295                // Error message
296                if (!result.allInstancesPassed) {
297                    final StringBuilder errorLog = new StringBuilder();
298
299                    for (Map.Entry<BatchRunConfiguration, String> entry :
300                            result.errorMessages.entrySet()) {
301                        if (errorLog.length() > 0) {
302                            errorLog.append('\n');
303                        }
304                        errorLog.append(String.format("=== with config %s ===\n",
305                                entry.getKey().getId()));
306                        errorLog.append(entry.getValue());
307                    }
308
309                    mSink.testFailed(testId, errorLog.toString());
310                }
311
312                final Map<String, String> emptyMap = Collections.emptyMap();
313                mSink.testEnded(testId, emptyMap);
314            } else {
315                CLog.w("Finalization for non-pending case %s", testId);
316            }
317        }
318
319        /**
320         * Declare existence of a test and instances
321         */
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         */
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         */
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         */
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         */
397        private boolean handleBeginSession(Map<String, String> values) {
398            // ignore
399            return true;
400        }
401
402        /**
403         * Handle session info
404         */
405        private boolean handleSessionInfo(Map<String, String> values) {
406            // ignore
407            return true;
408        }
409
410        /**
411         * Handles end of dEQP session.
412         */
413        private boolean handleEndSession(Map<String, String> values) {
414            // ignore
415            return true;
416        }
417
418        /**
419         * Handles beginning of dEQP testcase.
420         */
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         */
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         */
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         */
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         */
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         */
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         */
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
623        public InstrumentationParser(TestInstanceResultListener listener) {
624            mListener = listener;
625        }
626
627        /**
628         * {@inheritDoc}
629         */
630        @Override
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
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
704        public boolean isCancelled() {
705            return false;
706        }
707
708        /**
709         * Returns whether target instrumentation exited normally.
710         */
711        public boolean wasSuccessful() {
712            return mGotExitValue && mParseSuccessful;
713        }
714
715        /**
716         * Returns Instrumentation return code
717         */
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
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
759        public boolean isCancelled() {
760            return false;
761        }
762
763        /**
764         * Returns whether target instrumentation exited normally.
765         */
766        public boolean wasSuccessful() {
767            return mGotExitValue;
768        }
769
770        /**
771         * Returns Instrumentation return code
772         */
773        public int getResultCode() {
774            return mResultCode;
775        }
776
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 {
788        public void sleep(int milliseconds);
789    }
790
791    private static class SleepProvider implements ISleepProvider {
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         */
809        public void setSleepProvider(ISleepProvider sleepProvider);
810
811        /**
812         * Sets the device IRecovery works on
813         */
814        public void setDevice(ITestDevice device);
815
816        /**
817         * Informs Recovery that test execution has progressed since the last recovery
818         */
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         */
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         */
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         */
863        public void setSleepProvider(ISleepProvider sleepProvider) {
864            mSleepProvider = sleepProvider;
865        }
866
867        /**
868         * {@inheritDoc}
869         */
870        @Override
871        public void setDevice(ITestDevice device) {
872            mDevice = device;
873        }
874
875        /**
876         * {@inheritDoc}
877         */
878        @Override
879        public void onExecutionProgressed() {
880            mState = MachineState.WAIT;
881        }
882
883        /**
884         * {@inheritDoc}
885         */
886        @Override
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
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
986        private void waitCooldown() {
987            mSleepProvider.sleep(RETRY_COOLDOWN_MS);
988        }
989
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
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
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
1055        private void rebootDevice() throws DeviceNotAvailableException {
1056            mDevice.reboot();
1057        }
1058    }
1059
1060    /**
1061     * Parse map of instance arguments to map of BatchRunConfigurations
1062     */
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
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
1106    private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) {
1107        return mTestInstances.get(testId);
1108    }
1109
1110    /**
1111     * Converts dEQP testcase path to TestIdentifier.
1112     */
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
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     */
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     */
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
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     */
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
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
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
1302    private int getTestInstabilityRating(TestIdentifier testId) {
1303        if (mTestInstabilityRatings.containsKey(testId)) {
1304            return mTestInstabilityRatings.get(testId);
1305        } else {
1306            return 0;
1307        }
1308    }
1309
1310    private void recordTestInstability(TestIdentifier testId) {
1311        mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
1312    }
1313
1314    private void clearTestInstability(TestIdentifier testId) {
1315        mTestInstabilityRatings.put(testId, 0);
1316    }
1317
1318    /**
1319     * Executes all tests on the device.
1320     */
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     */
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
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 {
1377        public AdbComLinkOpenError(String description, Throwable inner) {
1378            super(description, inner);
1379        }
1380    }
1381
1382    private static final class AdbComLinkKilledError extends Exception {
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     */
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     */
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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
1797    private boolean isPortraitClassRotation(String rotation) {
1798        return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
1799                BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
1800    }
1801
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     */
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     */
1826    private void uninstallTestApk() throws DeviceNotAvailableException {
1827        getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
1828    }
1829
1830    /**
1831     * Parse gl nature from package name
1832     */
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     */
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     */
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     */
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
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