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