2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.drawelements.deqp.runner;
18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
19 import com.android.compatibility.common.util.AbiUtils;
20 import com.android.ddmlib.AdbCommandRejectedException;
21 import com.android.ddmlib.IShellOutputReceiver;
22 import com.android.ddmlib.MultiLineReceiver;
23 import com.android.ddmlib.ShellCommandUnresponsiveException;
24 import com.android.ddmlib.TimeoutException;
25 import com.android.ddmlib.testrunner.TestIdentifier;
26 import com.android.tradefed.build.IBuildInfo;
27 import com.android.tradefed.build.IFolderBuildInfo;
28 import com.android.tradefed.config.Option;
29 import com.android.tradefed.config.OptionClass;
30 import com.android.tradefed.device.DeviceNotAvailableException;
31 import com.android.tradefed.device.ITestDevice;
32 import com.android.tradefed.log.LogUtil.CLog;
33 import com.android.tradefed.result.ByteArrayInputStreamSource;
34 import com.android.tradefed.result.ITestInvocationListener;
35 import com.android.tradefed.result.LogDataType;
36 import com.android.tradefed.testtype.IAbi;
37 import com.android.tradefed.testtype.IBuildReceiver;
38 import com.android.tradefed.testtype.IAbiReceiver;
39 import com.android.tradefed.testtype.IDeviceTest;
40 import com.android.tradefed.testtype.IRemoteTest;
41 import com.android.tradefed.testtype.IRuntimeHintProvider;
42 import com.android.tradefed.testtype.IShardableTest;
43 import com.android.tradefed.testtype.ITestCollector;
44 import com.android.tradefed.testtype.ITestFilterReceiver;
45 import com.android.tradefed.util.IRunUtil;
46 import com.android.tradefed.util.RunInterruptedException;
47 import com.android.tradefed.util.RunUtil;
49 import java.io.BufferedReader;
51 import java.io.FileNotFoundException;
52 import java.io.FileReader;
53 import java.io.IOException;
54 import java.io.Reader;
55 import java.lang.reflect.Method;
56 import java.lang.reflect.InvocationTargetException;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.LinkedHashMap;
64 import java.util.LinkedHashSet;
65 import java.util.LinkedList;
66 import java.util.List;
68 import java.util.regex.Pattern;
70 import java.util.concurrent.TimeUnit;
73 * Test runner for dEQP tests
75 * Supports running drawElements Quality Program tests found under external/deqp.
77 @OptionClass(alias="deqp-test-runner")
78 public class DeqpTestRunner implements IBuildReceiver, IDeviceTest,
79 ITestFilterReceiver, IAbiReceiver, IShardableTest, ITestCollector,
80 IRuntimeHintProvider {
81 private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk";
82 private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp";
83 private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log";
84 private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped";
85 private static final String NOT_EXECUTABLE_LOG_MESSAGE = "Abort: Test cannot be executed";
86 private static final String CASE_LIST_FILE_NAME = "/sdcard/dEQP-TestCaseList.txt";
87 private static final String LOG_FILE_NAME = "/sdcard/TestLog.qpa";
88 public static final String FEATURE_LANDSCAPE = "android.hardware.screen.landscape";
89 public static final String FEATURE_PORTRAIT = "android.hardware.screen.portrait";
90 public static final String FEATURE_VULKAN_LEVEL = "android.hardware.vulkan.level";
92 private static final int TESTCASE_BATCH_LIMIT = 1000;
93 private static final int UNRESPONSIVE_CMD_TIMEOUT_MS = 10 * 60 * 1000; // 10min
95 // !NOTE: There's a static method copyOptions() for copying options during split.
96 // If you add state update copyOptions() as appropriate!
98 @Option(name="deqp-package",
99 description="Name of the deqp module used. Determines GLES version.",
100 importance=Option.Importance.ALWAYS)
101 private String mDeqpPackage;
102 @Option(name="deqp-gl-config-name",
103 description="GL render target config. See deqp documentation for syntax. ",
104 importance=Option.Importance.NEVER)
105 private String mConfigName = "";
106 @Option(name="deqp-caselist-file",
107 description="File listing the names of the cases to be run.",
108 importance=Option.Importance.ALWAYS)
109 private String mCaselistFile;
110 @Option(name="deqp-screen-rotation",
111 description="Screen orientation. Defaults to 'unspecified'",
112 importance=Option.Importance.NEVER)
113 private String mScreenRotation = "unspecified";
114 @Option(name="deqp-surface-type",
115 description="Surface type ('window', 'pbuffer', 'fbo'). Defaults to 'window'",
116 importance=Option.Importance.NEVER)
117 private String mSurfaceType = "window";
118 @Option(name="deqp-config-required",
119 description="Is current config required if API is supported? Defaults to false.",
120 importance=Option.Importance.NEVER)
121 private boolean mConfigRequired = false;
122 @Option(name = "include-filter",
123 description="Test include filter. '*' is zero or more letters. '.' has no special meaning.")
124 private List<String> mIncludeFilters = new ArrayList<>();
125 @Option(name = "exclude-filter",
126 description="Test exclude filter. '*' is zero or more letters. '.' has no special meaning.")
127 private List<String> mExcludeFilters = new ArrayList<>();
128 @Option(name = "collect-tests-only",
129 description = "Only invoke the instrumentation to collect list of applicable test "
130 + "cases. All test run callbacks will be triggered, but test execution will "
131 + "not be actually carried out.")
132 private boolean mCollectTestsOnly = false;
133 @Option(name = "runtime-hint",
135 description="The estimated config runtime. Defaults to 200ms x num tests.")
136 private long mRuntimeHint = -1;
138 private Collection<TestIdentifier> mRemainingTests = null;
139 private Map<TestIdentifier, Set<BatchRunConfiguration>> mTestInstances = null;
140 private final TestInstanceResultListener mInstanceListerner = new TestInstanceResultListener();
141 private final Map<TestIdentifier, Integer> mTestInstabilityRatings = new HashMap<>();
143 private CompatibilityBuildHelper mBuildHelper;
144 private boolean mLogData = false;
145 private ITestDevice mDevice;
146 private Set<String> mDeviceFeatures;
147 private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
148 private IRunUtil mRunUtil = RunUtil.getDefault();
149 // When set will override the mCaselistFile for testing purposes.
150 private Reader mCaselistReader = null;
152 private IRecovery mDeviceRecovery = new Recovery(); {
153 mDeviceRecovery.setSleepProvider(new SleepProvider());
156 public DeqpTestRunner() {
159 private DeqpTestRunner(DeqpTestRunner optionTemplate,
160 Map<TestIdentifier, Set<BatchRunConfiguration>> tests) {
161 copyOptions(this, optionTemplate);
162 mTestInstances = tests;
166 * @param abi the ABI to run the test on
169 public void setAbi(IAbi abi) {
177 public void setBuild(IBuildInfo buildInfo) {
178 setBuildHelper(new CompatibilityBuildHelper(buildInfo));
182 * Exposed for better mockability during testing. In real use, always flows from
183 * setBuild() called by the framework
185 public void setBuildHelper(CompatibilityBuildHelper helper) {
186 mBuildHelper = helper;
190 * Enable or disable raw dEQP test log collection.
192 public void setCollectLogs(boolean logData) {
197 * Get the deqp-package option contents.
199 public String getPackageName() {
207 public void setDevice(ITestDevice device) {
215 public ITestDevice getDevice() {
220 * Set recovery handler.
222 * Exposed for unit testing.
224 public void setRecovery(IRecovery deviceRecovery) {
225 mDeviceRecovery = deviceRecovery;
231 * Exposed for unit testing.
233 public void setRunUtil(IRunUtil runUtil) {
238 * Exposed for unit testing
240 public void setCaselistReader(Reader caselistReader) {
241 mCaselistReader = caselistReader;
244 private static final class CapabilityQueryFailureException extends Exception {
248 * Test configuration of dEPQ test instance execution.
249 * Exposed for unit testing
251 public static final class BatchRunConfiguration {
252 public static final String ROTATION_UNSPECIFIED = "unspecified";
253 public static final String ROTATION_PORTRAIT = "0";
254 public static final String ROTATION_LANDSCAPE = "90";
255 public static final String ROTATION_REVERSE_PORTRAIT = "180";
256 public static final String ROTATION_REVERSE_LANDSCAPE = "270";
258 private final String mGlConfig;
259 private final String mRotation;
260 private final String mSurfaceType;
261 private final boolean mRequired;
263 public BatchRunConfiguration(String glConfig, String rotation, String surfaceType, boolean required) {
264 mGlConfig = glConfig;
265 mRotation = rotation;
266 mSurfaceType = surfaceType;
267 mRequired = required;
271 * Get string that uniquely identifies this config
273 public String getId() {
274 return String.format("{glformat=%s,rotation=%s,surfacetype=%s,required=%b}",
275 mGlConfig, mRotation, mSurfaceType, mRequired);
279 * Get the GL config used in this configuration.
281 public String getGlConfig() {
286 * Get the screen rotation used in this configuration.
288 public String getRotation() {
293 * Get the surface type used in this configuration.
295 public String getSurfaceType() {
300 * Is this configuration mandatory to support, if target API is supported?
302 public boolean isRequired() {
307 public boolean equals(Object other) {
310 } else if (!(other instanceof BatchRunConfiguration)) {
313 return getId().equals(((BatchRunConfiguration)other).getId());
318 public int hashCode() {
319 return getId().hashCode();
324 * dEQP test instance listerer and invocation result forwarded
326 private class TestInstanceResultListener {
327 private ITestInvocationListener mSink;
328 private BatchRunConfiguration mRunConfig;
330 private TestIdentifier mCurrentTestId;
331 private boolean mGotTestResult;
332 private String mCurrentTestLog;
334 private class PendingResult {
335 boolean allInstancesPassed;
336 Map<BatchRunConfiguration, String> testLogs;
337 Map<BatchRunConfiguration, String> errorMessages;
338 Set<BatchRunConfiguration> remainingConfigs;
341 private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>();
343 public void setSink(ITestInvocationListener sink) {
347 public void setCurrentConfig(BatchRunConfiguration runConfig) {
348 mRunConfig = runConfig;
352 * Get currently processed test id, or null if not currently processing a test case
354 public TestIdentifier getCurrentTestId() {
355 return mCurrentTestId;
359 * Forward result to sink
361 private void forwardFinalizedPendingResult(TestIdentifier testId) {
362 if (mRemainingTests.contains(testId)) {
363 final PendingResult result = mPendingResults.get(testId);
365 mPendingResults.remove(testId);
366 mRemainingTests.remove(testId);
368 // Forward results to the sink
369 mSink.testStarted(testId);
373 for (Map.Entry<BatchRunConfiguration, String> entry :
374 result.testLogs.entrySet()) {
375 final ByteArrayInputStreamSource source
376 = new ByteArrayInputStreamSource(entry.getValue().getBytes());
378 mSink.testLog(testId.getClassName() + "." + testId.getTestName() + "@"
379 + entry.getKey().getId(), LogDataType.XML, source);
386 if (!result.allInstancesPassed) {
387 final StringBuilder errorLog = new StringBuilder();
389 for (Map.Entry<BatchRunConfiguration, String> entry :
390 result.errorMessages.entrySet()) {
391 if (errorLog.length() > 0) {
392 errorLog.append('\n');
394 errorLog.append(String.format("=== with config %s ===\n",
395 entry.getKey().getId()));
396 errorLog.append(entry.getValue());
399 mSink.testFailed(testId, errorLog.toString());
402 final Map<String, String> emptyMap = Collections.emptyMap();
403 mSink.testEnded(testId, emptyMap);
408 * Declare existence of a test and instances
410 public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) {
411 // Test instances cannot change at runtime, ignore if we have already set this
412 if (!mPendingResults.containsKey(testId)) {
413 final PendingResult pendingResult = new PendingResult();
414 pendingResult.allInstancesPassed = true;
415 pendingResult.testLogs = new LinkedHashMap<>();
416 pendingResult.errorMessages = new LinkedHashMap<>();
417 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument
418 mPendingResults.put(testId, pendingResult);
423 * Query if test instance has not yet been executed
425 public boolean isPendingTestInstance(TestIdentifier testId,
426 BatchRunConfiguration config) {
427 final PendingResult result = mPendingResults.get(testId);
428 if (result == null) {
429 // test is not in the current working batch of the runner, i.e. it cannot be
430 // "partially" completed.
431 if (!mRemainingTests.contains(testId)) {
432 // The test has been fully executed. Not pending.
435 // Test has not yet been executed. Check if such instance exists
436 return mTestInstances.get(testId).contains(config);
439 // could be partially completed, check this particular config
440 return result.remainingConfigs.contains(config);
445 * Fake execution of an instance with current config
447 public void skipTest(TestIdentifier testId) {
448 final PendingResult result = mPendingResults.get(testId);
450 result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE);
451 result.remainingConfigs.remove(mRunConfig);
453 // Pending result finished, report result
454 if (result.remainingConfigs.isEmpty()) {
455 forwardFinalizedPendingResult(testId);
460 * Fake failure of an instance with current config
462 public void abortTest(TestIdentifier testId, String errorMessage) {
463 final PendingResult result = mPendingResults.get(testId);
466 result.allInstancesPassed = false;
467 result.errorMessages.put(mRunConfig, errorMessage);
468 result.remainingConfigs.remove(mRunConfig);
470 // Pending result finished, report result
471 if (result.remainingConfigs.isEmpty()) {
472 forwardFinalizedPendingResult(testId);
475 if (testId.equals(mCurrentTestId)) {
476 mCurrentTestId = null;
481 * Handles beginning of dEQP session.
483 private void handleBeginSession(Map<String, String> values) {
488 * Handles end of dEQP session.
490 private void handleEndSession(Map<String, String> values) {
495 * Handles beginning of dEQP testcase.
497 private void handleBeginTestCase(Map<String, String> values) {
498 mCurrentTestId = pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath"));
499 mCurrentTestLog = "";
500 mGotTestResult = false;
502 // mark instance as started
503 if (mPendingResults.get(mCurrentTestId) != null) {
504 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig);
506 CLog.w("Got unexpected start of %s", mCurrentTestId);
511 * Handles end of dEQP testcase.
513 private void handleEndTestCase(Map<String, String> values) {
514 final PendingResult result = mPendingResults.get(mCurrentTestId);
516 if (result != null) {
517 if (!mGotTestResult) {
518 result.allInstancesPassed = false;
519 result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE);
522 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) {
523 result.testLogs.put(mRunConfig, mCurrentTestLog);
526 // Pending result finished, report result
527 if (result.remainingConfigs.isEmpty()) {
528 forwardFinalizedPendingResult(mCurrentTestId);
531 CLog.w("Got unexpected end of %s", mCurrentTestId);
533 mCurrentTestId = null;
537 * Handles dEQP testcase result.
539 private void handleTestCaseResult(Map<String, String> values) {
540 String code = values.get("dEQP-TestCaseResult-Code");
541 String details = values.get("dEQP-TestCaseResult-Details");
543 if (mPendingResults.get(mCurrentTestId) == null) {
544 CLog.w("Got unexpected result for %s", mCurrentTestId);
545 mGotTestResult = true;
549 if (code.compareTo("Pass") == 0) {
550 mGotTestResult = true;
551 } else if (code.compareTo("NotSupported") == 0) {
552 mGotTestResult = true;
553 } else if (code.compareTo("QualityWarning") == 0) {
554 mGotTestResult = true;
555 } else if (code.compareTo("CompatibilityWarning") == 0) {
556 mGotTestResult = true;
557 } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0
558 || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0
559 || code.compareTo("Timeout") == 0) {
560 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
561 mPendingResults.get(mCurrentTestId)
562 .errorMessages.put(mRunConfig, code + ": " + details);
563 mGotTestResult = true;
565 String codeError = "Unknown result code: " + code;
566 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
567 mPendingResults.get(mCurrentTestId)
568 .errorMessages.put(mRunConfig, codeError + ": " + details);
569 mGotTestResult = true;
574 * Handles terminated dEQP testcase.
576 private void handleTestCaseTerminate(Map<String, String> values) {
577 final PendingResult result = mPendingResults.get(mCurrentTestId);
579 if (result != null) {
580 String reason = values.get("dEQP-TerminateTestCase-Reason");
581 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
582 mPendingResults.get(mCurrentTestId)
583 .errorMessages.put(mRunConfig, "Terminated: " + reason);
585 // Pending result finished, report result
586 if (result.remainingConfigs.isEmpty()) {
587 forwardFinalizedPendingResult(mCurrentTestId);
590 CLog.w("Got unexpected termination of %s", mCurrentTestId);
593 mCurrentTestId = null;
594 mGotTestResult = true;
598 * Handles dEQP testlog data.
600 private void handleTestLogData(Map<String, String> values) {
601 mCurrentTestLog = mCurrentTestLog + values.get("dEQP-TestLogData-Log");
605 * Handles new instrumentation status message.
607 public void handleStatus(Map<String, String> values) {
608 String eventType = values.get("dEQP-EventType");
610 if (eventType == null) {
614 if (eventType.compareTo("BeginSession") == 0) {
615 handleBeginSession(values);
616 } else if (eventType.compareTo("EndSession") == 0) {
617 handleEndSession(values);
618 } else if (eventType.compareTo("BeginTestCase") == 0) {
619 handleBeginTestCase(values);
620 } else if (eventType.compareTo("EndTestCase") == 0) {
621 handleEndTestCase(values);
622 } else if (eventType.compareTo("TestCaseResult") == 0) {
623 handleTestCaseResult(values);
624 } else if (eventType.compareTo("TerminateTestCase") == 0) {
625 handleTestCaseTerminate(values);
626 } else if (eventType.compareTo("TestLogData") == 0) {
627 handleTestLogData(values);
632 * Signal listener that batch ended and forget incomplete results.
634 public void endBatch() {
635 // end open test if when stream ends
636 if (mCurrentTestId != null) {
637 // Current instance was removed from remainingConfigs when case
638 // started. Mark current instance as pending.
639 if (mPendingResults.get(mCurrentTestId) != null) {
640 mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig);
642 CLog.w("Got unexpected internal state of %s", mCurrentTestId);
645 mCurrentTestId = null;
650 * dEQP instrumentation parser
652 private static class InstrumentationParser extends MultiLineReceiver {
653 private TestInstanceResultListener mListener;
655 private Map<String, String> mValues;
656 private String mCurrentName;
657 private String mCurrentValue;
658 private int mResultCode;
659 private boolean mGotExitValue = false;
662 public InstrumentationParser(TestInstanceResultListener listener) {
663 mListener = listener;
670 public void processNewLines(String[] lines) {
671 for (String line : lines) {
672 if (mValues == null) mValues = new HashMap<String, String>();
674 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) {
675 if (mCurrentName != null) {
676 mValues.put(mCurrentName, mCurrentValue);
679 mCurrentValue = null;
682 mListener.handleStatus(mValues);
684 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
685 if (mCurrentName != null) {
686 mValues.put(mCurrentName, mCurrentValue);
688 mCurrentValue = null;
692 String prefix = "INSTRUMENTATION_STATUS: ";
693 int nameBegin = prefix.length();
694 int nameEnd = line.indexOf('=');
695 int valueBegin = nameEnd + 1;
697 mCurrentName = line.substring(nameBegin, nameEnd);
698 mCurrentValue = line.substring(valueBegin);
699 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
701 mResultCode = Integer.parseInt(line.substring(22));
702 mGotExitValue = true;
703 } catch (NumberFormatException ex) {
704 CLog.w("Instrumentation code format unexpected");
706 } else if (mCurrentValue != null) {
707 mCurrentValue = mCurrentValue + line;
717 if (mCurrentName != null) {
718 mValues.put(mCurrentName, mCurrentValue);
721 mCurrentValue = null;
724 if (mValues != null) {
725 mListener.handleStatus(mValues);
734 public boolean isCancelled() {
739 * Returns whether target instrumentation exited normally.
741 public boolean wasSuccessful() {
742 return mGotExitValue;
746 * Returns Instrumentation return code
748 public int getResultCode() {
754 * dEQP platfom query instrumentation parser
756 private static class PlatformQueryInstrumentationParser extends MultiLineReceiver {
757 private Map<String,String> mResultMap = new LinkedHashMap<>();
758 private int mResultCode;
759 private boolean mGotExitValue = false;
765 public void processNewLines(String[] lines) {
766 for (String line : lines) {
767 if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
768 final String parts[] = line.substring(24).split("=",2);
769 if (parts.length == 2) {
770 mResultMap.put(parts[0], parts[1]);
772 CLog.w("Instrumentation status format unexpected");
774 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
776 mResultCode = Integer.parseInt(line.substring(22));
777 mGotExitValue = true;
778 } catch (NumberFormatException ex) {
779 CLog.w("Instrumentation code format unexpected");
789 public boolean isCancelled() {
794 * Returns whether target instrumentation exited normally.
796 public boolean wasSuccessful() {
797 return mGotExitValue;
801 * Returns Instrumentation return code
803 public int getResultCode() {
807 public Map<String,String> getResultMap() {
813 * Interface for sleeping.
815 * Exposed for unit testing
817 public static interface ISleepProvider {
818 public void sleep(int milliseconds);
821 private static class SleepProvider implements ISleepProvider {
822 public void sleep(int milliseconds) {
824 Thread.sleep(milliseconds);
825 } catch (InterruptedException ex) {
831 * Interface for failure recovery.
833 * Exposed for unit testing
835 public static interface IRecovery {
837 * Sets the sleep provider IRecovery works on
839 public void setSleepProvider(ISleepProvider sleepProvider);
842 * Sets the device IRecovery works on
844 public void setDevice(ITestDevice device);
847 * Informs Recovery that test execution has progressed since the last recovery
849 public void onExecutionProgressed();
852 * Tries to recover device after failed refused connection.
854 * @throws DeviceNotAvailableException if recovery did not succeed
856 public void recoverConnectionRefused() throws DeviceNotAvailableException;
859 * Tries to recover device after abnormal execution termination or link failure.
861 * @param progressedSinceLastCall true if test execution has progressed since last call
862 * @throws DeviceNotAvailableException if recovery did not succeed
864 public void recoverComLinkKilled() throws DeviceNotAvailableException;
868 * State machine for execution failure recovery.
870 * Exposed for unit testing
872 public static class Recovery implements IRecovery {
873 private int RETRY_COOLDOWN_MS = 6000; // 6 seconds
874 private int PROCESS_KILL_WAIT_MS = 1000; // 1 second
876 private static enum MachineState {
877 WAIT, // recover by waiting
878 RECOVER, // recover by calling recover()
879 REBOOT, // recover by rebooting
880 FAIL, // cannot recover
883 private MachineState mState = MachineState.WAIT;
884 private ITestDevice mDevice;
885 private ISleepProvider mSleepProvider;
887 private static class ProcessKillFailureException extends Exception {
893 public void setSleepProvider(ISleepProvider sleepProvider) {
894 mSleepProvider = sleepProvider;
901 public void setDevice(ITestDevice device) {
909 public void onExecutionProgressed() {
910 mState = MachineState.WAIT;
917 public void recoverConnectionRefused() throws DeviceNotAvailableException {
919 case WAIT: // not a valid stratedy for connection refusal, fallthrough
921 // First failure, just try to recover
922 CLog.w("ADB connection failed, trying to recover");
923 mState = MachineState.REBOOT; // the next step is to reboot
927 } catch (DeviceNotAvailableException ex) {
929 recoverConnectionRefused();
934 // Second failure in a row, try to reboot
935 CLog.w("ADB connection failed after recovery, rebooting device");
936 mState = MachineState.FAIL; // the next step is to fail
940 } catch (DeviceNotAvailableException ex) {
942 recoverConnectionRefused();
947 // Third failure in a row, just fail
948 CLog.w("Cannot recover ADB connection");
949 throw new DeviceNotAvailableException("failed to connect after reboot");
957 public void recoverComLinkKilled() throws DeviceNotAvailableException {
960 // First failure, just try to wait and try again
961 CLog.w("ADB link failed, retrying after a cooldown period");
962 mState = MachineState.RECOVER; // the next step is to recover the device
966 // even if the link to deqp on-device process was killed, the process might
967 // still be alive. Locate and terminate such unwanted processes.
970 } catch (DeviceNotAvailableException ex) {
972 recoverComLinkKilled();
973 } catch (ProcessKillFailureException ex) {
975 recoverComLinkKilled();
980 // Second failure, just try to recover
981 CLog.w("ADB link failed, trying to recover");
982 mState = MachineState.REBOOT; // the next step is to reboot
987 } catch (DeviceNotAvailableException ex) {
989 recoverComLinkKilled();
990 } catch (ProcessKillFailureException ex) {
992 recoverComLinkKilled();
997 // Third failure in a row, try to reboot
998 CLog.w("ADB link failed after recovery, rebooting device");
999 mState = MachineState.FAIL; // the next step is to fail
1003 } catch (DeviceNotAvailableException ex) {
1005 recoverComLinkKilled();
1010 // Fourth failure in a row, just fail
1011 CLog.w("Cannot recover ADB connection");
1012 throw new DeviceNotAvailableException("link killed after reboot");
1016 private void waitCooldown() {
1017 mSleepProvider.sleep(RETRY_COOLDOWN_MS);
1020 private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException {
1021 final List<Integer> pids = new ArrayList<Integer>(2);
1022 final String processes = mDevice.executeShellCommand("ps | grep com.drawelements");
1023 final String[] lines = processes.split("(\\r|\\n)+");
1024 for (String line : lines) {
1025 final String[] fields = line.split("\\s+");
1026 if (fields.length < 2) {
1031 final int processId = Integer.parseInt(fields[1], 10);
1032 pids.add(processId);
1033 } catch (NumberFormatException ex) {
1040 private void killDeqpProcess() throws DeviceNotAvailableException,
1041 ProcessKillFailureException {
1042 for (Integer processId : getDeqpProcessPids()) {
1043 mDevice.executeShellCommand(String.format("kill -9 %d", processId));
1046 mSleepProvider.sleep(PROCESS_KILL_WAIT_MS);
1048 // check that processes actually died
1049 if (getDeqpProcessPids().iterator().hasNext()) {
1050 // a process is still alive, killing failed
1051 throw new ProcessKillFailureException();
1055 public void recoverDevice() throws DeviceNotAvailableException {
1056 // Work around the API. We need to call recoverDevice() on the test device and
1057 // we know that mDevice is a TestDevice. However even though the recoverDevice()
1058 // method is public suggesting it should be publicly accessible, the class itself
1059 // and its super-interface (IManagedTestDevice) are package-private.
1060 final Method recoverDeviceMethod;
1062 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice");
1063 recoverDeviceMethod.setAccessible(true);
1064 } catch (NoSuchMethodException ex) {
1065 throw new AssertionError("Test device must have recoverDevice()");
1069 recoverDeviceMethod.invoke(mDevice);
1070 } catch (InvocationTargetException ex) {
1071 if (ex.getCause() instanceof DeviceNotAvailableException) {
1072 throw (DeviceNotAvailableException)ex.getCause();
1073 } else if (ex.getCause() instanceof RuntimeException) {
1074 throw (RuntimeException)ex.getCause();
1076 throw new AssertionError("unexpected throw", ex);
1078 } catch (IllegalAccessException ex) {
1079 throw new AssertionError("unexpected throw", ex);
1083 private void rebootDevice() throws DeviceNotAvailableException {
1088 private static Map<TestIdentifier, Set<BatchRunConfiguration>> generateTestInstances(
1089 Reader testlist, String configName, String screenRotation, String surfaceType, boolean required) throws FileNotFoundException {
1090 // Note: This is specifically a LinkedHashMap to guarantee that tests are iterated
1091 // in the insertion order.
1092 final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new LinkedHashMap<>();
1094 BufferedReader testlistReader = new BufferedReader(testlist);
1096 while ((testName = testlistReader.readLine()) != null) {
1097 // Test name -> testId -> only one config -> done.
1098 final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>();
1099 BatchRunConfiguration config = new BatchRunConfiguration(configName, screenRotation, surfaceType, required);
1100 testInstanceSet.add(config);
1101 TestIdentifier test = pathToIdentifier(testName);
1102 instances.put(test, testInstanceSet);
1104 testlistReader.close();
1106 catch (IOException e)
1108 throw new RuntimeException("Failure while reading the test case list for deqp: " + e.getMessage());
1114 private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) {
1115 return mTestInstances.get(testId);
1119 * Converts dEQP testcase path to TestIdentifier.
1121 private static TestIdentifier pathToIdentifier(String testPath) {
1122 int indexOfLastDot = testPath.lastIndexOf('.');
1123 String className = testPath.substring(0, indexOfLastDot);
1124 String testName = testPath.substring(indexOfLastDot+1);
1126 return new TestIdentifier(className, testName);
1129 // \todo [2015-10-16 kalle] How unique should this be?
1130 private String getId() {
1131 return AbiUtils.createId(mAbi.getName(), mDeqpPackage);
1135 * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute.
1137 private static String generateTestCaseTrieFromPaths(Collection<String> tests) {
1138 String result = "{";
1139 boolean first = true;
1141 // Add testcases to results
1142 for (Iterator<String> iter = tests.iterator(); iter.hasNext();) {
1143 String test = iter.next();
1144 String[] components = test.split("\\.");
1146 if (components.length == 1) {
1148 result = result + ",";
1152 result += components[0];
1157 if (!tests.isEmpty()) {
1158 HashMap<String, ArrayList<String> > testGroups = new HashMap<>();
1160 // Collect all sub testgroups
1161 for (String test : tests) {
1162 String[] components = test.split("\\.");
1163 ArrayList<String> testGroup = testGroups.get(components[0]);
1165 if (testGroup == null) {
1166 testGroup = new ArrayList<String>();
1167 testGroups.put(components[0], testGroup);
1170 testGroup.add(test.substring(components[0].length()+1));
1173 for (String testGroup : testGroups.keySet()) {
1175 result = result + ",";
1179 result = result + testGroup
1180 + generateTestCaseTrieFromPaths(testGroups.get(testGroup));
1184 return result + "}";
1188 * Generates testcase trie from TestIdentifiers.
1190 private static String generateTestCaseTrie(Collection<TestIdentifier> tests) {
1191 ArrayList<String> testPaths = new ArrayList<String>();
1193 for (TestIdentifier test : tests) {
1194 testPaths.add(test.getClassName() + "." + test.getTestName());
1197 return generateTestCaseTrieFromPaths(testPaths);
1200 private static class TestBatch {
1201 public BatchRunConfiguration config;
1202 public List<TestIdentifier> tests;
1206 * Creates a TestBatch from the given tests or null if not tests remaining.
1208 * @param pool List of tests to select from
1209 * @param requiredConfig Select only instances with pending requiredConfig, or null to select
1210 * any run configuration.
1212 private TestBatch selectRunBatch(Collection<TestIdentifier> pool,
1213 BatchRunConfiguration requiredConfig) {
1214 // select one test (leading test) that is going to be executed and then pack along as many
1215 // other compatible instances as possible.
1217 TestIdentifier leadingTest = null;
1218 for (TestIdentifier test : pool) {
1219 if (!mRemainingTests.contains(test)) {
1222 if (requiredConfig != null &&
1223 !mInstanceListerner.isPendingTestInstance(test, requiredConfig)) {
1230 // no remaining tests?
1231 if (leadingTest == null) {
1235 BatchRunConfiguration leadingTestConfig = null;
1236 if (requiredConfig != null) {
1237 leadingTestConfig = requiredConfig;
1239 for (BatchRunConfiguration runConfig : getTestRunConfigs(leadingTest)) {
1240 if (mInstanceListerner.isPendingTestInstance(leadingTest, runConfig)) {
1241 leadingTestConfig = runConfig;
1247 // test pending <=> test has a pending config
1248 if (leadingTestConfig == null) {
1249 throw new AssertionError("search postcondition failed");
1252 final int leadingInstability = getTestInstabilityRating(leadingTest);
1254 final TestBatch runBatch = new TestBatch();
1255 runBatch.config = leadingTestConfig;
1256 runBatch.tests = new ArrayList<>();
1257 runBatch.tests.add(leadingTest);
1259 for (TestIdentifier test : pool) {
1260 if (test == leadingTest) {
1261 // do not re-select the leading tests
1264 if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) {
1265 // select only compatible
1268 if (getTestInstabilityRating(test) != leadingInstability) {
1269 // pack along only cases in the same stability category. Packing more dangerous
1270 // tests along jeopardizes the stability of this run. Packing more stable tests
1271 // along jeopardizes their stability rating.
1274 if (runBatch.tests.size() >= getBatchSizeLimitForInstability(leadingInstability)) {
1275 // batch size is limited.
1278 runBatch.tests.add(test);
1284 private int getBatchNumPendingCases(TestBatch batch) {
1286 for (TestIdentifier test : batch.tests) {
1287 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1294 private int getBatchSizeLimitForInstability(int batchInstabilityRating) {
1295 // reduce group size exponentially down to one
1296 return Math.max(1, TESTCASE_BATCH_LIMIT / (1 << batchInstabilityRating));
1299 private int getTestInstabilityRating(TestIdentifier testId) {
1300 if (mTestInstabilityRatings.containsKey(testId)) {
1301 return mTestInstabilityRatings.get(testId);
1307 private void recordTestInstability(TestIdentifier testId) {
1308 mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
1311 private void clearTestInstability(TestIdentifier testId) {
1312 mTestInstabilityRatings.put(testId, 0);
1316 * Executes all tests on the device.
1318 private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException {
1320 TestBatch batch = selectRunBatch(mRemainingTests, null);
1322 if (batch == null) {
1326 runTestRunBatch(batch);
1331 * Runs a TestBatch by either faking it or executing it on a device.
1333 private void runTestRunBatch(TestBatch batch) throws DeviceNotAvailableException,
1334 CapabilityQueryFailureException {
1335 // prepare instance listener
1336 mInstanceListerner.setCurrentConfig(batch.config);
1337 for (TestIdentifier test : batch.tests) {
1338 mInstanceListerner.setTestInstances(test, getTestRunConfigs(test));
1341 // execute only if config is executable, else fake results
1342 if (isSupportedRunConfiguration(batch.config)) {
1343 executeTestRunBatch(batch);
1345 if (batch.config.isRequired()) {
1346 fakeFailTestRunBatch(batch);
1348 fakePassTestRunBatch(batch);
1353 private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig)
1354 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1355 // orientation support
1356 if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) {
1357 final Set<String> features = getDeviceFeatures(mDevice);
1359 if (isPortraitClassRotation(runConfig.getRotation()) &&
1360 !features.contains(FEATURE_PORTRAIT)) {
1363 if (isLandscapeClassRotation(runConfig.getRotation()) &&
1364 !features.contains(FEATURE_LANDSCAPE)) {
1369 if (isOpenGlEsPackage()) {
1370 // renderability support for OpenGL ES tests
1371 return isSupportedGlesRenderConfig(runConfig);
1377 private static final class AdbComLinkOpenError extends Exception {
1378 public AdbComLinkOpenError(String description, Throwable inner) {
1379 super(description, inner);
1383 private static final class AdbComLinkKilledError extends Exception {
1384 public AdbComLinkKilledError(String description, Throwable inner) {
1385 super(description, inner);
1390 * Executes a given command in adb shell
1392 * @throws AdbComLinkOpenError if connection cannot be established.
1393 * @throws AdbComLinkKilledError if established connection is killed prematurely.
1395 private void executeShellCommandAndReadOutput(final String command,
1396 final IShellOutputReceiver receiver)
1397 throws AdbComLinkOpenError, AdbComLinkKilledError {
1399 mDevice.getIDevice().executeShellCommand(command, receiver,
1400 UNRESPONSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1401 } catch (TimeoutException ex) {
1402 // Opening connection timed out
1403 throw new AdbComLinkOpenError("opening connection timed out", ex);
1404 } catch (AdbCommandRejectedException ex) {
1406 throw new AdbComLinkOpenError("command rejected", ex);
1407 } catch (IOException ex) {
1408 // shell command channel killed
1409 throw new AdbComLinkKilledError("command link killed", ex);
1410 } catch (ShellCommandUnresponsiveException ex) {
1411 // shell command halted
1412 throw new AdbComLinkKilledError("command link hung", ex);
1417 * Executes given test batch on a device
1419 private void executeTestRunBatch(TestBatch batch) throws DeviceNotAvailableException {
1420 // attempt full run once
1421 executeTestRunBatchRun(batch);
1423 // split remaining tests to two sub batches and execute both. This will terminate
1424 // since executeTestRunBatchRun will always progress for a batch of size 1.
1425 final ArrayList<TestIdentifier> pendingTests = new ArrayList<>();
1427 for (TestIdentifier test : batch.tests) {
1428 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1429 pendingTests.add(test);
1433 final int divisorNdx = pendingTests.size() / 2;
1434 final List<TestIdentifier> headList = pendingTests.subList(0, divisorNdx);
1435 final List<TestIdentifier> tailList = pendingTests.subList(divisorNdx, pendingTests.size());
1439 TestBatch subBatch = selectRunBatch(headList, batch.config);
1441 if (subBatch == null) {
1445 executeTestRunBatch(subBatch);
1450 TestBatch subBatch = selectRunBatch(tailList, batch.config);
1452 if (subBatch == null) {
1456 executeTestRunBatch(subBatch);
1459 if (getBatchNumPendingCases(batch) != 0) {
1460 throw new AssertionError("executeTestRunBatch postcondition failed");
1465 * Runs one execution pass over the given batch.
1467 * Tries to run the batch. Always makes progress (executes instances or modifies stability
1470 private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException {
1471 if (getBatchNumPendingCases(batch) != batch.tests.size()) {
1472 throw new AssertionError("executeTestRunBatchRun precondition failed");
1475 checkInterrupted(); // throws if interrupted
1477 final String testCases = generateTestCaseTrie(batch.tests);
1479 mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
1480 mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
1481 mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME);
1483 final String instrumentationName =
1484 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation";
1486 final StringBuilder deqpCmdLine = new StringBuilder();
1487 deqpCmdLine.append("--deqp-caselist-file=");
1488 deqpCmdLine.append(CASE_LIST_FILE_NAME);
1489 deqpCmdLine.append(" ");
1490 deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config));
1492 // If we are not logging data, do not bother outputting the images from the test exe.
1494 deqpCmdLine.append(" --deqp-log-images=disable");
1497 deqpCmdLine.append(" --deqp-watchdog=enable");
1499 final String command = String.format(
1500 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\""
1501 + " -e deqpLogData \"%s\" %s",
1502 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(),
1503 mLogData, instrumentationName);
1505 final int numRemainingInstancesBefore = getNumRemainingInstances();
1506 final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner);
1507 Throwable interruptingError = null;
1510 executeShellCommandAndReadOutput(command, parser);
1511 } catch (Throwable ex) {
1512 interruptingError = ex;
1517 final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null ||
1518 getNumRemainingInstances() < numRemainingInstancesBefore;
1520 if (progressedSinceLastCall) {
1521 mDeviceRecovery.onExecutionProgressed();
1524 // interrupted, try to recover
1525 if (interruptingError != null) {
1526 if (interruptingError instanceof AdbComLinkOpenError) {
1527 mDeviceRecovery.recoverConnectionRefused();
1528 } else if (interruptingError instanceof AdbComLinkKilledError) {
1529 mDeviceRecovery.recoverComLinkKilled();
1530 } else if (interruptingError instanceof RunInterruptedException) {
1531 // external run interruption request. Terminate immediately.
1532 throw (RunInterruptedException)interruptingError;
1534 CLog.e(interruptingError);
1535 throw new RuntimeException(interruptingError);
1538 // recoverXXX did not throw => recovery succeeded
1539 } else if (!parser.wasSuccessful()) {
1540 mDeviceRecovery.recoverComLinkKilled();
1541 // recoverXXX did not throw => recovery succeeded
1544 // Progress guarantees.
1545 if (batch.tests.size() == 1) {
1546 final TestIdentifier onlyTest = batch.tests.iterator().next();
1547 final boolean wasTestExecuted =
1548 !mInstanceListerner.isPendingTestInstance(onlyTest, batch.config) &&
1549 mInstanceListerner.getCurrentTestId() == null;
1550 final boolean wasLinkFailure = !parser.wasSuccessful() || interruptingError != null;
1552 // Link failures can be caused by external events, require at least two observations
1554 if (!wasTestExecuted && (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) {
1555 recordTestInstability(onlyTest);
1556 // If we cannot finish the test, mark the case as a crash.
1558 // If we couldn't even start the test, fail the test instance as non-executable.
1559 // This is required so that a consistently crashing or non-existent tests will
1560 // not cause futile (non-terminating) re-execution attempts.
1561 if (mInstanceListerner.getCurrentTestId() != null) {
1562 mInstanceListerner.abortTest(onlyTest, INCOMPLETE_LOG_MESSAGE);
1564 mInstanceListerner.abortTest(onlyTest, NOT_EXECUTABLE_LOG_MESSAGE);
1566 } else if (wasTestExecuted) {
1567 clearTestInstability(onlyTest);
1572 // Analyze results to update test stability ratings. If there is no interrupting test
1573 // logged, increase instability rating of all remaining tests. If there is a
1574 // interrupting test logged, increase only its instability rating.
1576 // A successful run of tests clears instability rating.
1577 if (mInstanceListerner.getCurrentTestId() == null) {
1578 for (TestIdentifier test : batch.tests) {
1579 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1580 recordTestInstability(test);
1582 clearTestInstability(test);
1586 recordTestInstability(mInstanceListerner.getCurrentTestId());
1587 for (TestIdentifier test : batch.tests) {
1588 // \note: isPendingTestInstance is false for getCurrentTestId. Current ID is
1589 // considered 'running' and will be restored to 'pending' in endBatch().
1590 if (!test.equals(mInstanceListerner.getCurrentTestId()) &&
1591 !mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1592 clearTestInstability(test);
1598 mInstanceListerner.endBatch();
1601 private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) {
1602 final StringBuilder deqpCmdLine = new StringBuilder();
1603 if (!runConfig.getGlConfig().isEmpty()) {
1604 deqpCmdLine.append("--deqp-gl-config-name=");
1605 deqpCmdLine.append(runConfig.getGlConfig());
1607 if (!runConfig.getRotation().isEmpty()) {
1608 if (deqpCmdLine.length() != 0) {
1609 deqpCmdLine.append(" ");
1611 deqpCmdLine.append("--deqp-screen-rotation=");
1612 deqpCmdLine.append(runConfig.getRotation());
1614 if (!runConfig.getSurfaceType().isEmpty()) {
1615 if (deqpCmdLine.length() != 0) {
1616 deqpCmdLine.append(" ");
1618 deqpCmdLine.append("--deqp-surface-type=");
1619 deqpCmdLine.append(runConfig.getSurfaceType());
1621 return deqpCmdLine.toString();
1624 private int getNumRemainingInstances() {
1626 for (TestIdentifier testId : mRemainingTests) {
1627 // If case is in current working set, sum only not yet executed instances.
1628 // If case is not in current working set, sum all instances (since they are not yet
1630 if (mInstanceListerner.mPendingResults.containsKey(testId)) {
1631 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size();
1633 retVal += mTestInstances.get(testId).size();
1640 * Checks if this execution has been marked as interrupted and throws if it has.
1642 private void checkInterrupted() throws RunInterruptedException {
1643 // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly
1644 // by sleeping a value <= 0.
1649 * Pass given batch tests without running it
1651 private void fakePassTestRunBatch(TestBatch batch) {
1652 for (TestIdentifier test : batch.tests) {
1653 CLog.d("Marking '%s' invocation in config '%s' as passed without running", test.toString(),
1654 batch.config.getId());
1655 mInstanceListerner.skipTest(test);
1660 * Fail given batch tests without running it
1662 private void fakeFailTestRunBatch(TestBatch batch) {
1663 for (TestIdentifier test : batch.tests) {
1664 CLog.d("Marking '%s' invocation in config '%s' as failed without running", test.toString(),
1665 batch.config.getId());
1666 mInstanceListerner.abortTest(test, "Required config not supported");
1671 * Pass all remaining tests without running them
1673 private void fakePassTests(ITestInvocationListener listener) {
1674 Map <String, String> emptyMap = Collections.emptyMap();
1675 for (TestIdentifier test : mRemainingTests) {
1676 CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString());
1677 listener.testStarted(test);
1678 listener.testEnded(test, emptyMap);
1680 mRemainingTests.clear();
1684 * Check if device supports Vulkan.
1686 private boolean isSupportedVulkan ()
1687 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1688 final Set<String> features = getDeviceFeatures(mDevice);
1690 for (String feature : features) {
1691 if (feature.startsWith(FEATURE_VULKAN_LEVEL)) {
1700 * Check if device supports OpenGL ES version.
1702 private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion,
1703 int requiredMinorVersion) throws DeviceNotAvailableException {
1704 String roOpenglesVersion = device.getProperty("ro.opengles.version");
1706 if (roOpenglesVersion == null)
1709 int intValue = Integer.parseInt(roOpenglesVersion);
1711 int majorVersion = ((intValue & 0xffff0000) >> 16);
1712 int minorVersion = (intValue & 0xffff);
1714 return (majorVersion > requiredMajorVersion)
1715 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion);
1719 * Query if rendertarget is supported
1721 private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
1722 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1723 // query if configuration is supported
1724 final StringBuilder configCommandLine =
1725 new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
1726 if (configCommandLine.length() != 0) {
1727 configCommandLine.append(" ");
1729 configCommandLine.append("--deqp-gl-major-version=");
1730 configCommandLine.append(getGlesMajorVersion());
1731 configCommandLine.append(" --deqp-gl-minor-version=");
1732 configCommandLine.append(getGlesMinorVersion());
1734 final String commandLine = configCommandLine.toString();
1736 // check for cached result first
1737 if (mConfigQuerySupportCache.containsKey(commandLine)) {
1738 return mConfigQuerySupportCache.get(commandLine);
1741 final boolean supported = queryIsSupportedConfigCommandLine(commandLine);
1742 mConfigQuerySupportCache.put(commandLine, supported);
1746 private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
1747 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1748 final String instrumentationName =
1749 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
1750 final String command = String.format(
1751 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
1753 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName);
1755 final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser();
1756 mDevice.executeShellCommand(command, parser);
1759 if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
1760 parser.getResultMap().containsKey("Supported")) {
1761 if ("Yes".equals(parser.getResultMap().get("Supported"))) {
1763 } else if ("No".equals(parser.getResultMap().get("Supported"))) {
1766 CLog.e("Capability query did not return a result");
1767 throw new CapabilityQueryFailureException();
1769 } else if (parser.wasSuccessful()) {
1770 CLog.e("Failed to run capability query. Code: %d, Result: %s",
1771 parser.getResultCode(), parser.getResultMap().toString());
1772 throw new CapabilityQueryFailureException();
1774 CLog.e("Failed to run capability query");
1775 throw new CapabilityQueryFailureException();
1780 * Return feature set supported by the device
1782 private Set<String> getDeviceFeatures(ITestDevice device)
1783 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1784 if (mDeviceFeatures == null) {
1785 mDeviceFeatures = queryDeviceFeatures(device);
1787 return mDeviceFeatures;
1791 * Query feature set supported by the device
1793 private static Set<String> queryDeviceFeatures(ITestDevice device)
1794 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1795 // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
1796 // TODO: Move this logic to ITestDevice.
1797 String command = "pm list features";
1798 String commandOutput = device.executeShellCommand(command);
1800 // Extract the id of the new user.
1801 HashSet<String> availableFeatures = new HashSet<>();
1802 for (String feature: commandOutput.split("\\s+")) {
1803 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
1804 String[] tokens = feature.split(":");
1805 if (tokens.length < 2 || !"feature".equals(tokens[0])) {
1806 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]);
1807 throw new CapabilityQueryFailureException();
1809 availableFeatures.add(tokens[1]);
1811 return availableFeatures;
1814 private boolean isPortraitClassRotation(String rotation) {
1815 return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
1816 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
1819 private boolean isLandscapeClassRotation(String rotation) {
1820 return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
1821 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
1825 * Install dEQP OnDevice Package
1827 private void installTestApk() throws DeviceNotAvailableException {
1829 File apkFile = new File(mBuildHelper.getTestsDir(), DEQP_ONDEVICE_APK);
1830 String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
1831 String errorCode = getDevice().installPackage(apkFile, true, options);
1832 if (errorCode != null) {
1833 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode);
1835 } catch (FileNotFoundException e) {
1836 CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK);
1841 * Uninstall dEQP OnDevice Package
1843 private void uninstallTestApk() throws DeviceNotAvailableException {
1844 getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
1848 * Parse gl nature from package name
1850 private boolean isOpenGlEsPackage() {
1851 if ("dEQP-GLES2".equals(mDeqpPackage) || "dEQP-GLES3".equals(mDeqpPackage) ||
1852 "dEQP-GLES31".equals(mDeqpPackage)) {
1854 } else if ("dEQP-EGL".equals(mDeqpPackage) ||
1855 "dEQP-VK".equals(mDeqpPackage)) {
1858 throw new IllegalStateException("dEQP runner was created with illegal name");
1863 * Parse vulkan nature from package name
1865 private boolean isVulkanPackage() {
1866 if ("dEQP-GLES2".equals(mDeqpPackage) || "dEQP-GLES3".equals(mDeqpPackage) ||
1867 "dEQP-GLES31".equals(mDeqpPackage) || "dEQP-EGL".equals(mDeqpPackage)) {
1869 } else if ("dEQP-VK".equals(mDeqpPackage)) {
1872 throw new IllegalStateException("dEQP runner was created with illegal name");
1877 * Check GL support (based on package name)
1879 private boolean isSupportedGles() throws DeviceNotAvailableException {
1880 return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion());
1884 * Get GL major version (based on package name)
1886 private int getGlesMajorVersion() throws DeviceNotAvailableException {
1887 if ("dEQP-GLES2".equals(mDeqpPackage)) {
1889 } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
1891 } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
1894 throw new IllegalStateException("getGlesMajorVersion called for non gles pkg");
1899 * Get GL minor version (based on package name)
1901 private int getGlesMinorVersion() throws DeviceNotAvailableException {
1902 if ("dEQP-GLES2".equals(mDeqpPackage)) {
1904 } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
1906 } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
1909 throw new IllegalStateException("getGlesMinorVersion called for non gles pkg");
1913 private static List<Pattern> getPatternFilters(List<String> filters) {
1914 List<Pattern> patterns = new ArrayList<Pattern>();
1915 for (String filter : filters) {
1916 if (filter.contains("*")) {
1917 patterns.add(Pattern.compile(filter.replace(".","\\.").replace("*",".*")));
1923 private static Set<String> getNonPatternFilters(List<String> filters) {
1924 Set<String> nonPatternFilters = new HashSet<String>();
1925 for (String filter : filters) {
1926 if (!filter.contains("*")) {
1927 nonPatternFilters.add(filter);
1930 return nonPatternFilters;
1933 private static boolean matchesAny(TestIdentifier test, List<Pattern> patterns) {
1934 for (Pattern pattern : patterns) {
1935 if (pattern.matcher(test.toString()).matches()) {
1943 * Filter tests with the option of filtering by pattern.
1945 * '*' is 0 or more characters.
1946 * '.' is interpreted verbatim.
1948 private static void filterTests(Map<TestIdentifier, Set<BatchRunConfiguration>> tests,
1949 List<String> includeFilters,
1950 List<String> excludeFilters) {
1951 // We could filter faster by building the test case tree.
1952 // Let's see if this is fast enough.
1953 Set<String> includeStrings = getNonPatternFilters(includeFilters);
1954 Set<String> excludeStrings = getNonPatternFilters(excludeFilters);
1955 List<Pattern> includePatterns = getPatternFilters(includeFilters);
1956 List<Pattern> excludePatterns = getPatternFilters(excludeFilters);
1958 List<TestIdentifier> testList = new ArrayList(tests.keySet());
1959 for (TestIdentifier test : testList) {
1960 if (excludeStrings.contains(test.toString())) {
1961 tests.remove(test); // remove test if explicitly excluded
1964 boolean includesExist = !includeStrings.isEmpty() || !includePatterns.isEmpty();
1965 boolean testIsIncluded = includeStrings.contains(test.toString())
1966 || matchesAny(test, includePatterns);
1967 if ((includesExist && !testIsIncluded) || matchesAny(test, excludePatterns)) {
1968 // if this test isn't included and other tests are,
1969 // or if test matches exclude pattern, exclude test
1976 * Loads tests into mTestInstances based on the options. Assumes
1977 * that no tests have been loaded for this instance before.
1979 private void loadTests() {
1980 if (mTestInstances != null) throw new AssertionError("Re-load of tests not supported");
1983 Reader reader = mCaselistReader;
1984 if (reader == null) {
1985 File testlist = new File(mBuildHelper.getTestsDir(), mCaselistFile);
1986 if (!testlist.isFile()) {
1987 throw new FileNotFoundException();
1989 reader = new FileReader(testlist);
1991 mTestInstances = generateTestInstances(reader, mConfigName, mScreenRotation, mSurfaceType, mConfigRequired);
1992 mCaselistReader = null;
1995 catch (FileNotFoundException e) {
1996 throw new RuntimeException("Cannot read deqp test list file: " + mCaselistFile);
1998 catch (IOException e) {
1999 CLog.w("Failed to close test list reader.");
2002 for (String filter : mIncludeFilters) {
2003 CLog.d("Include: %s", filter);
2005 for (String filter : mExcludeFilters) {
2006 CLog.d("Exclude: %s", filter);
2009 long originalTestCount = mTestInstances.size();
2010 CLog.i("Num tests before filtering: %d", originalTestCount);
2011 if ((!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) && originalTestCount > 0) {
2012 filterTests(mTestInstances, mIncludeFilters, mExcludeFilters);
2014 // Update runtime estimation hint.
2015 if (mRuntimeHint != -1) {
2016 mRuntimeHint = (mRuntimeHint * mTestInstances.size()) / originalTestCount;
2019 CLog.i("Num tests after filtering: %d", mTestInstances.size());
2026 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
2027 final Map<String, String> emptyMap = Collections.emptyMap();
2028 // If sharded, split() will load the tests.
2029 if (mTestInstances == null) {
2033 mRemainingTests = new LinkedList<>(mTestInstances.keySet());
2034 long startTime = System.currentTimeMillis();
2035 listener.testRunStarted(getId(), mRemainingTests.size());
2038 final boolean isSupportedApi = (isOpenGlEsPackage() && isSupportedGles())
2039 || (isVulkanPackage() && isSupportedVulkan())
2040 || (!isOpenGlEsPackage() && !isVulkanPackage());
2042 if (!isSupportedApi || mCollectTestsOnly) {
2043 // Pass all tests if OpenGL ES version is not supported or we are collecting
2044 // the names of the tests only
2045 fakePassTests(listener);
2046 } else if (!mRemainingTests.isEmpty()) {
2047 // Make sure there is no pre-existing package form earlier interrupted test run.
2051 mInstanceListerner.setSink(listener);
2052 mDeviceRecovery.setDevice(mDevice);
2057 } catch (CapabilityQueryFailureException ex) {
2058 // Platform is not behaving correctly, for example crashing when trying to create
2059 // a window. Instead of silenty failing, signal failure by leaving the rest of the
2060 // test cases in "NotExecuted" state
2061 CLog.e("Capability query failed - leaving tests unexecuted.");
2065 listener.testRunEnded(System.currentTimeMillis() - startTime, emptyMap);
2072 public void addIncludeFilter(String filter) {
2073 mIncludeFilters.add(filter);
2080 public void addAllIncludeFilters(Set<String> filters) {
2081 mIncludeFilters.addAll(filters);
2088 public void addExcludeFilter(String filter) {
2089 mExcludeFilters.add(filter);
2096 public void addAllExcludeFilters(Set<String> filters) {
2097 mExcludeFilters.addAll(filters);
2104 public void setCollectTestsOnly(boolean collectTests) {
2105 mCollectTestsOnly = collectTests;
2108 private static void copyOptions(DeqpTestRunner destination, DeqpTestRunner source) {
2109 destination.mDeqpPackage = source.mDeqpPackage;
2110 destination.mConfigName = source.mConfigName;
2111 destination.mCaselistFile = source.mCaselistFile;
2112 destination.mScreenRotation = source.mScreenRotation;
2113 destination.mSurfaceType = source.mSurfaceType;
2114 destination.mConfigRequired = source.mConfigRequired;
2115 destination.mIncludeFilters = new ArrayList<>(source.mIncludeFilters);
2116 destination.mExcludeFilters = new ArrayList<>(source.mExcludeFilters);
2117 destination.mAbi = source.mAbi;
2118 destination.mLogData = source.mLogData;
2119 destination.mCollectTestsOnly = source.mCollectTestsOnly;
2126 public Collection<IRemoteTest> split() {
2127 if (mTestInstances != null) {
2128 throw new AssertionError("Re-splitting or splitting running instance?");
2130 // \todo [2015-11-23 kalle] If we split to batches at shard level, we could
2131 // basically get rid of batching. Except that sharding is optional?
2133 // Assume that tests have not been yet loaded.
2136 Collection<IRemoteTest> runners = new ArrayList<>();
2137 // NOTE: Use linked hash map to keep the insertion order in iteration
2138 Map<TestIdentifier, Set<BatchRunConfiguration>> currentSet = new LinkedHashMap<>();
2139 Map<TestIdentifier, Set<BatchRunConfiguration>> iterationSet = this.mTestInstances;
2141 // Go through tests, split
2142 for (TestIdentifier test: iterationSet.keySet()) {
2143 currentSet.put(test, iterationSet.get(test));
2144 if (currentSet.size() >= TESTCASE_BATCH_LIMIT) {
2145 runners.add(new DeqpTestRunner(this, currentSet));
2146 // NOTE: Use linked hash map to keep the insertion order in iteration
2147 currentSet = new LinkedHashMap<>();
2150 runners.add(new DeqpTestRunner(this, currentSet));
2152 // Compute new runtime hints
2153 long originalSize = iterationSet.size();
2154 if (originalSize > 0) {
2155 long fullRuntimeMs = getRuntimeHint();
2156 for (IRemoteTest remote: runners) {
2157 DeqpTestRunner runner = (DeqpTestRunner)remote;
2158 long shardRuntime = (fullRuntimeMs * runner.mTestInstances.size()) / originalSize;
2159 runner.mRuntimeHint = shardRuntime;
2163 CLog.i("Split deqp tests into %d shards", runners.size());
2171 public long getRuntimeHint() {
2172 if (mRuntimeHint != -1) {
2173 return mRuntimeHint;
2175 if (mTestInstances == null) {
2178 // Tests normally take something like ~100ms. Some take a
2179 // second. Let's guess 200ms per test.
2180 return 200 * mTestInstances.size();