xref: /aosp_15_r20/external/deqp/android/cts/runner/src/com/drawelements/deqp/runner/DeqpTestRunner.java (revision 35238bce31c2a825756842865a792f8cf7f89930)
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.drawelements.deqp.runner;
17 
18 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
19 import com.android.compatibility.common.tradefed.targetprep.IncrementalDeqpPreparer;
20 import com.android.compatibility.common.util.PropertyUtil;
21 import com.android.ddmlib.AdbCommandRejectedException;
22 import com.android.ddmlib.IShellOutputReceiver;
23 import com.android.ddmlib.MultiLineReceiver;
24 import com.android.ddmlib.ShellCommandUnresponsiveException;
25 import com.android.ddmlib.TimeoutException;
26 import com.android.tradefed.build.IBuildInfo;
27 import com.android.tradefed.config.Option;
28 import com.android.tradefed.config.OptionClass;
29 import com.android.tradefed.device.DeviceNotAvailableException;
30 import com.android.tradefed.device.IManagedTestDevice;
31 import com.android.tradefed.device.ITestDevice;
32 import com.android.tradefed.error.HarnessRuntimeException;
33 import com.android.tradefed.log.LogUtil.CLog;
34 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
35 import com.android.tradefed.result.ByteArrayInputStreamSource;
36 import com.android.tradefed.result.ITestInvocationListener;
37 import com.android.tradefed.result.LogDataType;
38 import com.android.tradefed.result.TestDescription;
39 import com.android.tradefed.result.error.DeviceErrorIdentifier;
40 import com.android.tradefed.result.error.TestErrorIdentifier;
41 import com.android.tradefed.testtype.IAbi;
42 import com.android.tradefed.testtype.IAbiReceiver;
43 import com.android.tradefed.testtype.IBuildReceiver;
44 import com.android.tradefed.testtype.IDeviceTest;
45 import com.android.tradefed.testtype.IRemoteTest;
46 import com.android.tradefed.testtype.IRuntimeHintProvider;
47 import com.android.tradefed.testtype.IShardableTest;
48 import com.android.tradefed.testtype.ITestCollector;
49 import com.android.tradefed.testtype.ITestFilterReceiver;
50 import com.android.tradefed.util.AbiUtils;
51 import com.android.tradefed.util.FileUtil;
52 import com.android.tradefed.util.IRunUtil;
53 import com.android.tradefed.util.RunInterruptedException;
54 import com.android.tradefed.util.RunUtil;
55 import java.io.BufferedReader;
56 import java.io.File;
57 import java.io.FileNotFoundException;
58 import java.io.FileReader;
59 import java.io.IOException;
60 import java.io.Reader;
61 import java.nio.file.Paths;
62 import java.util.ArrayList;
63 import java.util.Collection;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.Iterator;
67 import java.util.LinkedHashMap;
68 import java.util.LinkedHashSet;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Optional;
72 import java.util.Set;
73 import java.util.concurrent.TimeUnit;
74 import java.util.regex.Matcher;
75 import java.util.regex.Pattern;
76 
77 /**
78  * Test runner for dEQP tests
79  *
80  * Supports running drawElements Quality Program tests found under
81  * external/deqp.
82  */
83 @OptionClass(alias = "deqp-test-runner")
84 public class DeqpTestRunner
85     implements IBuildReceiver, IDeviceTest, ITestFilterReceiver, IAbiReceiver,
86                IShardableTest, ITestCollector, IRuntimeHintProvider {
87     private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk";
88     private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp";
89     protected static final String INCOMPLETE_LOG_MESSAGE =
90         "Crash: Incomplete test log";
91     protected static final String TIMEOUT_LOG_MESSAGE = "Timeout: Test timeout";
92     private static final String SKIPPED_INSTANCE_LOG_MESSAGE =
93         "Configuration skipped";
94     public static final String ASSUMPTION_FAILURE_DEQP_LEVEL_LOG_MESSAGE =
95         "Features to be tested are not supported by device";
96     protected static final String NOT_EXECUTABLE_LOG_MESSAGE =
97         "Abort: Test cannot be executed";
98     protected static final String APP_DIR = "/sdcard/";
99     protected static final String CASE_LIST_FILE_NAME = "dEQP-TestCaseList.txt";
100     protected static final String LOG_FILE_NAME = "TestLog.qpa";
101     public static final String FEATURE_LANDSCAPE =
102         "android.hardware.screen.landscape";
103     public static final String FEATURE_PORTRAIT =
104         "android.hardware.screen.portrait";
105     public static final String FEATURE_VULKAN_LEVEL =
106         "android.hardware.vulkan.level";
107     public static final String FEATURE_VULKAN_DEQP_LEVEL =
108         "android.software.vulkan.deqp.level";
109     public static final String FEATURE_OPENGLES_DEQP_LEVEL =
110         "android.software.opengles.deqp.level";
111 
112     private static final int TESTCASE_BATCH_LIMIT = 1000;
113     private static final int UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT =
114         10 * 60 * 1000; // 10min
115     private static final int R_API_LEVEL = 30;
116     private static final int DEQP_LEVEL_R_2020 = 132383489;
117 
118     protected static final String ANGLE_NONE = "none";
119     protected static final String ANGLE_VULKAN = "vulkan";
120     protected static final String ANGLE_OPENGLES = "opengles";
121 
122     // !NOTE: There's a static method copyOptions() for copying options during
123     // split. If you add state update copyOptions() as appropriate!
124 
125     @Option(name = "timeout",
126             description =
127                 "Timeout for unresponsive tests in milliseconds. Default: " +
128                 UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT,
129             importance = Option.Importance.NEVER)
130     private long mUnresponsiveCmdTimeoutMs =
131         UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT;
132     @Option(name = "deqp-package",
133             description =
134                 "Name of the deqp module used. Determines GLES version.",
135             importance = Option.Importance.ALWAYS)
136     private String mDeqpPackage;
137     @Option(name = "deqp-gl-config-name",
138             description =
139                 "GL render target config. See deqp documentation for syntax. ",
140             importance = Option.Importance.NEVER)
141     private String mConfigName = "";
142     @Option(name = "deqp-caselist-file",
143             description = "File listing the names of the cases to be run.",
144             importance = Option.Importance.ALWAYS)
145     private String mCaselistFile;
146     @Option(name = "deqp-screen-rotation",
147             description = "Screen orientation. Defaults to 'unspecified'",
148             importance = Option.Importance.NEVER)
149     private String mScreenRotation = "unspecified";
150     @Option(
151         name = "deqp-surface-type",
152         description =
153             "Surface type ('window', 'pbuffer', 'fbo'). Defaults to 'window'",
154         importance = Option.Importance.NEVER)
155     private String mSurfaceType = "window";
156     @Option(
157         name = "deqp-config-required",
158         description =
159             "Is current config required if API is supported? Defaults to false.",
160         importance = Option.Importance.NEVER)
161     private boolean mConfigRequired = false;
162     @Option(
163         name = "include-filter",
164         description =
165             "Test include filter. '*' is zero or more letters. '.' has no special meaning.")
166     protected List<String> mIncludeFilters = new ArrayList<>();
167     @Option(name = "include-filter-file",
168             description = "Load list of includes from the files given.")
169     private List<String> mIncludeFilterFiles = new ArrayList<>();
170     @Option(
171         name = "exclude-filter",
172         description =
173             "Test exclude filter. '*' is zero or more letters. '.' has no special meaning.")
174     protected List<String> mExcludeFilters = new ArrayList<>();
175     @Option(name = "exclude-filter-file",
176             description = "Load list of excludes from the files given.")
177     private List<String> mExcludeFilterFiles = new ArrayList<>();
178     @Option(
179         name = "incremental-deqp-include-file",
180         description =
181             "Load list of includes from the files given for incremental dEQP.")
182     private List<String> mIncrementalDeqpIncludeFiles = new ArrayList<>();
183     @Option(
184         name = "collect-tests-only",
185         description =
186             "Only invoke the instrumentation to collect list of applicable test "
187             +
188             "cases. All test run callbacks will be triggered, but test execution will "
189             + "not be actually carried out.")
190     private boolean mCollectTestsOnly = false;
191     @Option(name = "runtime-hint", isTimeVal = true,
192             description =
193                 "The estimated config runtime. Defaults to 200ms x num tests.")
194     private long mRuntimeHint = -1;
195 
196     @Option(name = "collect-raw-logs",
197             description = "whether to collect raw deqp test log data")
198     protected boolean mLogData = false;
199 
200     @Option(
201         name = "deqp-use-angle",
202         description =
203             "ANGLE backend ('none', 'vulkan', 'opengles'). Defaults to 'none' (don't use ANGLE)",
204         importance = Option.Importance.NEVER)
205     protected String mAngle = "none";
206 
207     @Option(name = "disable-watchdog",
208             description = "Disable the native testrunner's per-test watchdog.")
209     private boolean mDisableWatchdog = false;
210 
211     @Option(
212         name = "force-deqp-level",
213         description =
214             "Force dEQP level to a specific level instead of device dEQP level. "
215             + "'all' enforces all dEQP tests to run")
216     private String mForceDeqpLevel = "";
217 
218     protected Set<TestDescription> mRemainingTests = null;
219     private Map<TestDescription, Set<BatchRunConfiguration>> mTestInstances =
220         null;
221     private final TestInstanceResultListener mInstanceListerner =
222         new TestInstanceResultListener();
223     private final Map<TestDescription, Integer> mTestInstabilityRatings =
224         new HashMap<>();
225     protected IAbi mAbi;
226     protected CompatibilityBuildHelper mBuildHelper;
227     protected ITestDevice mDevice;
228     private Map<String, Optional<Integer>> mDeviceFeatures;
229     private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
230     protected IRunUtil mRunUtil = RunUtil.getDefault();
231     private Set<String> mIncrementalDeqpIncludeTests = new HashSet<>();
232     protected long mTimeOfLastRun = 0;
233 
234     protected IRecovery mDeviceRecovery = new Recovery();
mDeviceRecovery.setSleepProvider(new SleepProvider())235     { mDeviceRecovery.setSleepProvider(new SleepProvider()); }
236 
DeqpTestRunner()237     public DeqpTestRunner() {}
238 
DeqpTestRunner( DeqpTestRunner optionTemplate, Map<TestDescription, Set<BatchRunConfiguration>> tests)239     private DeqpTestRunner(
240         DeqpTestRunner optionTemplate,
241         Map<TestDescription, Set<BatchRunConfiguration>> tests) {
242         copyOptions(this, optionTemplate);
243         mTestInstances = tests;
244     }
245 
246     /**
247      * @param abi the ABI to run the test on
248      */
249     @Override
setAbi(IAbi abi)250     public void setAbi(IAbi abi) {
251         mAbi = abi;
252     }
253 
254     @Override
getAbi()255     public IAbi getAbi() {
256         return mAbi;
257     }
258 
259     /**
260      * {@inheritDoc}
261      */
262     @Override
setBuild(IBuildInfo buildInfo)263     public void setBuild(IBuildInfo buildInfo) {
264         setBuildHelper(new CompatibilityBuildHelper(buildInfo));
265     }
266 
267     /**
268      * Exposed for better mockability during testing. In real use, always flows
269      * from setBuild() called by the framework
270      */
setBuildHelper(CompatibilityBuildHelper helper)271     public void setBuildHelper(CompatibilityBuildHelper helper) {
272         mBuildHelper = helper;
273     }
274 
275     /**
276      * Get the deqp-package option contents.
277      */
getPackageName()278     public String getPackageName() { return mDeqpPackage; }
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
setDevice(ITestDevice device)284     public void setDevice(ITestDevice device) {
285         mDevice = device;
286     }
287 
288     /**
289      * {@inheritDoc}
290      */
291     @Override
getDevice()292     public ITestDevice getDevice() {
293         return mDevice;
294     }
295 
296     /**
297      * Set recovery handler.
298      *
299      * Exposed for unit testing.
300      */
setRecovery(IRecovery deviceRecovery)301     public void setRecovery(IRecovery deviceRecovery) {
302         mDeviceRecovery = deviceRecovery;
303     }
304 
305     /**
306      * Set IRunUtil.
307      *
308      * Exposed for unit testing.
309      */
setRunUtil(IRunUtil runUtil)310     public void setRunUtil(IRunUtil runUtil) { mRunUtil = runUtil; }
311 
312     private static final class CapabilityQueryFailureException
313         extends Exception {}
314 
getInstanceListener()315     protected TestInstanceResultListener getInstanceListener() {return mInstanceListerner;}
316 
317     /**
318      * dEQP test instance listerer and invocation result forwarded
319      */
320     protected class TestInstanceResultListener {
321         private BatchRunConfiguration mRunConfig;
322         protected ITestInvocationListener mSink;
323         protected TestDescription mCurrentTestId;
324         protected boolean mGotTestResult;
325         protected String mCurrentTestLog;
326 
327         private class PendingResult {
328             boolean allInstancesPassed;
329             Map<BatchRunConfiguration, String> testLogs;
330             Map<BatchRunConfiguration, String> errorMessages;
331             Set<BatchRunConfiguration> remainingConfigs;
332         }
333 
334         private final Map<TestDescription, PendingResult> mPendingResults =
335             new HashMap<>();
336 
setSink(ITestInvocationListener sink)337         public void setSink(ITestInvocationListener sink) { mSink = sink; }
338 
setCurrentConfig(BatchRunConfiguration runConfig)339         public void setCurrentConfig(BatchRunConfiguration runConfig) {
340             mRunConfig = runConfig;
341         }
342 
343         /**
344          * Get currently processed test id, or null if not currently processing
345          * a test case
346          */
getCurrentTestId()347         public TestDescription getCurrentTestId() { return mCurrentTestId; }
348 
349         /**
350          * Forward result to sink
351          */
forwardFinalizedPendingResult(TestDescription testId)352         protected void forwardFinalizedPendingResult(TestDescription testId) {
353             if (mRemainingTests.contains(testId)) {
354                 final PendingResult result = mPendingResults.get(testId);
355 
356                 mPendingResults.remove(testId);
357                 mRemainingTests.remove(testId);
358 
359                 // Forward results to the sink
360                 mSink.testStarted(testId);
361 
362                 // Test Log
363                 if (mLogData) {
364                     for (Map.Entry<BatchRunConfiguration, String> entry :
365                          result.testLogs.entrySet()) {
366                         final ByteArrayInputStreamSource source =
367                             new ByteArrayInputStreamSource(
368                                 entry.getValue().getBytes());
369 
370                         mSink.testLog(testId.getClassName() + "." +
371                                           testId.getTestName() + "@" +
372                                           entry.getKey().getId(),
373                                       LogDataType.XML, source);
374 
375                         source.close();
376                     }
377                 }
378 
379                 // Error message
380                 if (!result.allInstancesPassed) {
381                     final StringBuilder errorLog = new StringBuilder();
382 
383                     for (Map.Entry<BatchRunConfiguration, String> entry :
384                          result.errorMessages.entrySet()) {
385                         if (errorLog.length() > 0) {
386                             errorLog.append('\n');
387                         }
388                         errorLog.append(
389                             String.format("=== with config %s ===\n",
390                                           entry.getKey().getId()));
391                         errorLog.append(entry.getValue());
392                     }
393 
394                     mSink.testFailed(testId, errorLog.toString());
395                 }
396 
397                 final HashMap<String, Metric> emptyMap = new HashMap<>();
398                 mSink.testEnded(testId, emptyMap);
399             }
400         }
401 
402         /**
403          * Declare existence of a test and instances
404          */
setTestInstances(TestDescription testId, Set<BatchRunConfiguration> configs)405         public void setTestInstances(TestDescription testId,
406                                      Set<BatchRunConfiguration> configs) {
407             // Test instances cannot change at runtime, ignore if we have
408             // already set this
409             if (!mPendingResults.containsKey(testId)) {
410                 final PendingResult pendingResult = new PendingResult();
411                 pendingResult.allInstancesPassed = true;
412                 pendingResult.testLogs = new LinkedHashMap<>();
413                 pendingResult.errorMessages = new LinkedHashMap<>();
414                 pendingResult.remainingConfigs =
415                     new HashSet<>(configs); // avoid mutating argument
416                 mPendingResults.put(testId, pendingResult);
417             }
418         }
419 
420         /**
421          * Query if test instance has not yet been executed
422          */
isPendingTestInstance(TestDescription testId, BatchRunConfiguration config)423         public boolean isPendingTestInstance(TestDescription testId,
424                                              BatchRunConfiguration config) {
425             final PendingResult result = mPendingResults.get(testId);
426             if (result == null) {
427                 // test is not in the current working batch of the runner, i.e.
428                 // it cannot be "partially" completed.
429                 if (!mRemainingTests.contains(testId)) {
430                     // The test has been fully executed. Not pending.
431                     return false;
432                 } else {
433                     // Test has not yet been executed. Check if such instance
434                     // exists
435                     return mTestInstances.get(testId).contains(config);
436                 }
437             } else {
438                 // could be partially completed, check this particular config
439                 return result.remainingConfigs.contains(config);
440             }
441         }
442 
443         /**
444          * Fake execution of an instance with current config
445          */
skipTest(TestDescription testId)446         public void skipTest(TestDescription testId) {
447             final PendingResult result = mPendingResults.get(testId);
448 
449             result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE);
450             result.remainingConfigs.remove(mRunConfig);
451 
452             // Pending result finished, report result
453             if (result.remainingConfigs.isEmpty()) {
454                 forwardFinalizedPendingResult(testId);
455             }
456         }
457 
458         /**
459          * Fake failure of an instance with current config
460          */
abortTest(TestDescription testId, String errorMessage)461         public void abortTest(TestDescription testId, String errorMessage) {
462             final PendingResult result = mPendingResults.get(testId);
463 
464             // Mark as executed
465             result.allInstancesPassed = false;
466             result.errorMessages.put(mRunConfig, errorMessage);
467             result.remainingConfigs.remove(mRunConfig);
468 
469             // Pending result finished, report result
470             if (result.remainingConfigs.isEmpty()) {
471                 forwardFinalizedPendingResult(testId);
472             }
473 
474             if (testId.equals(mCurrentTestId)) {
475                 mCurrentTestId = null;
476             }
477         }
478 
479         /**
480          * Handles beginning of dEQP session.
481          */
handleBeginSession(Map<String, String> values)482         protected void handleBeginSession(Map<String, String> values) {
483             // ignore
484         }
485 
486         /**
487          * Handles end of dEQP session.
488          */
handleEndSession(Map<String, String> values)489         protected void handleEndSession(Map<String, String> values) {
490             // ignore
491         }
492 
493         /**
494          * Handles beginning of dEQP testcase.
495          */
handleBeginTestCase(Map<String, String> values)496         protected void handleBeginTestCase(Map<String, String> values) {
497             mCurrentTestId =
498                 pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath"));
499             mCurrentTestLog = "";
500             mGotTestResult = false;
501 
502             // mark instance as started
503             if (mPendingResults.get(mCurrentTestId) != null) {
504                 mPendingResults.get(mCurrentTestId)
505                     .remainingConfigs.remove(mRunConfig);
506             } else {
507                 CLog.w("Got unexpected start of %s", mCurrentTestId);
508             }
509         }
510 
511         /**
512          * Handles end of dEQP testcase.
513          */
handleEndTestCase(Map<String, String> values)514         protected void handleEndTestCase(Map<String, String> values) {
515             final PendingResult result = mPendingResults.get(mCurrentTestId);
516 
517             if (result != null) {
518                 if (!mGotTestResult) {
519                     result.allInstancesPassed = false;
520                     result.errorMessages.put(mRunConfig,
521                                              INCOMPLETE_LOG_MESSAGE);
522                 }
523 
524                 if (mLogData && mCurrentTestLog != null &&
525                     mCurrentTestLog.length() > 0) {
526                     result.testLogs.put(mRunConfig, mCurrentTestLog);
527                 }
528 
529                 // Pending result finished, report result
530                 if (result.remainingConfigs.isEmpty()) {
531                     forwardFinalizedPendingResult(mCurrentTestId);
532                 }
533             } else {
534                 CLog.w("Got unexpected end of %s", mCurrentTestId);
535             }
536             mCurrentTestId = null;
537         }
538 
539         /**
540          * Handles dEQP testcase result.
541          */
handleTestCaseResult(Map<String, String> values)542         protected void handleTestCaseResult(Map<String, String> values) {
543             String code = values.get("dEQP-TestCaseResult-Code");
544             String details = values.get("dEQP-TestCaseResult-Details");
545 
546             if (mPendingResults.get(mCurrentTestId) == null) {
547                 CLog.w("Got unexpected result for %s", mCurrentTestId);
548                 mGotTestResult = true;
549                 return;
550             }
551 
552             if (code.compareTo("Pass") == 0) {
553                 mGotTestResult = true;
554             } else if (code.compareTo("NotSupported") == 0) {
555                 mGotTestResult = true;
556             } else if (code.compareTo("QualityWarning") == 0) {
557                 mGotTestResult = true;
558             } else if (code.compareTo("CompatibilityWarning") == 0) {
559                 mGotTestResult = true;
560             } else if (code.compareTo("Fail") == 0 ||
561                        code.compareTo("ResourceError") == 0 ||
562                        code.compareTo("InternalError") == 0 ||
563                        code.compareTo("Crash") == 0 ||
564                        code.compareTo("Timeout") == 0) {
565                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
566                 mPendingResults.get(mCurrentTestId)
567                     .errorMessages.put(mRunConfig, code + ": " + details);
568                 mGotTestResult = true;
569             } else {
570                 String codeError = "Unknown result code: " + code;
571                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
572                 mPendingResults.get(mCurrentTestId)
573                     .errorMessages.put(mRunConfig, codeError + ": " + details);
574                 mGotTestResult = true;
575             }
576         }
577 
578         /**
579          * Handles terminated dEQP testcase.
580          */
handleTestCaseTerminate(Map<String, String> values)581         protected void handleTestCaseTerminate(Map<String, String> values) {
582             final PendingResult result = mPendingResults.get(mCurrentTestId);
583 
584             if (result != null) {
585                 String reason = values.get("dEQP-TerminateTestCase-Reason");
586                 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
587                 mPendingResults.get(mCurrentTestId)
588                     .errorMessages.put(mRunConfig, "Terminated: " + reason);
589 
590                 // Pending result finished, report result
591                 if (result.remainingConfigs.isEmpty()) {
592                     forwardFinalizedPendingResult(mCurrentTestId);
593                 }
594             } else {
595                 CLog.w("Got unexpected termination of %s", mCurrentTestId);
596             }
597 
598             mCurrentTestId = null;
599             mGotTestResult = true;
600         }
601 
602         /**
603          * Handles dEQP testlog data.
604          */
handleTestLogData(Map<String, String> values)605         protected void handleTestLogData(Map<String, String> values) {
606             mCurrentTestLog =
607                 mCurrentTestLog + values.get("dEQP-TestLogData-Log");
608         }
609 
610         /**
611          * Handles new instrumentation status message.
612          */
handleStatus(Map<String, String> values)613         public void handleStatus(Map<String, String> values) {
614             String eventType = values.get("dEQP-EventType");
615 
616             if (eventType == null) {
617                 return;
618             }
619 
620             if (eventType.compareTo("BeginSession") == 0) {
621                 handleBeginSession(values);
622             } else if (eventType.compareTo("EndSession") == 0) {
623                 handleEndSession(values);
624             } else if (eventType.compareTo("BeginTestCase") == 0) {
625                 handleBeginTestCase(values);
626             } else if (eventType.compareTo("EndTestCase") == 0) {
627                 handleEndTestCase(values);
628             } else if (eventType.compareTo("TestCaseResult") == 0) {
629                 handleTestCaseResult(values);
630             } else if (eventType.compareTo("TerminateTestCase") == 0) {
631                 handleTestCaseTerminate(values);
632             } else if (eventType.compareTo("TestLogData") == 0) {
633                 handleTestLogData(values);
634             }
635         }
636 
637         /**
638          * Signal listener that batch ended and forget incomplete results.
639          */
endBatch()640         public void endBatch() {
641             // end open test if when stream ends
642             if (mCurrentTestId != null) {
643                 // Current instance was removed from remainingConfigs when case
644                 // started. Mark current instance as pending.
645                 if (mPendingResults.get(mCurrentTestId) != null) {
646                     mPendingResults.get(mCurrentTestId)
647                         .remainingConfigs.add(mRunConfig);
648                 } else {
649                     CLog.w("Got unexpected internal state of %s",
650                            mCurrentTestId);
651                 }
652             }
653             mCurrentTestId = null;
654         }
655     }
656 
657     /**
658      * dEQP instrumentation parser
659      */
660     protected static class InstrumentationParser extends MultiLineReceiver {
661         protected TestInstanceResultListener mListener;
662 
663         protected Map<String, String> mValues;
664         protected String mCurrentName;
665         protected String mCurrentValue;
666         protected int mResultCode;
667         protected boolean mGotExitValue = false;
668 
InstrumentationParser()669         protected InstrumentationParser(){}
670 
InstrumentationParser(TestInstanceResultListener listener)671         public InstrumentationParser(TestInstanceResultListener listener) {
672             mListener = listener;
673         }
674 
675         /**
676          * {@inheritDoc}
677          */
678         @Override
processNewLines(String[] lines)679         public void processNewLines(String[] lines) {
680             for (String line : lines) {
681                 if (mValues == null)
682                     mValues = new HashMap<String, String>();
683 
684                 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) {
685                     if (mCurrentName != null) {
686                         mValues.put(mCurrentName, mCurrentValue);
687 
688                         mCurrentName = null;
689                         mCurrentValue = null;
690                     }
691 
692                     mListener.handleStatus(mValues);
693                     mValues = null;
694                 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
695                     if (mCurrentName != null) {
696                         mValues.put(mCurrentName, mCurrentValue);
697 
698                         mCurrentValue = null;
699                         mCurrentName = null;
700                     }
701 
702                     String prefix = "INSTRUMENTATION_STATUS: ";
703                     int nameBegin = prefix.length();
704                     int nameEnd = line.indexOf('=');
705                     int valueBegin = nameEnd + 1;
706 
707                     mCurrentName = line.substring(nameBegin, nameEnd);
708                     mCurrentValue = line.substring(valueBegin);
709                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
710                     try {
711                         mResultCode = Integer.parseInt(line.substring(22));
712                         mGotExitValue = true;
713                     } catch (NumberFormatException ex) {
714                         CLog.w("Instrumentation code format unexpected");
715                     }
716                 } else if (mCurrentValue != null) {
717                     mCurrentValue = mCurrentValue + line;
718                 }
719             }
720         }
721 
722         /**
723          * {@inheritDoc}
724          */
725         @Override
done()726         public void done() {
727             if (mCurrentName != null) {
728                 mValues.put(mCurrentName, mCurrentValue);
729 
730                 mCurrentName = null;
731                 mCurrentValue = null;
732             }
733 
734             if (mValues != null) {
735                 mListener.handleStatus(mValues);
736                 mValues = null;
737             }
738         }
739 
740         /**
741          * {@inheritDoc}
742          */
743         @Override
isCancelled()744         public boolean isCancelled() {
745             return false;
746         }
747 
748         /**
749          * Returns whether target instrumentation exited normally.
750          */
wasSuccessful()751         public boolean wasSuccessful() { return mGotExitValue; }
752 
753         /**
754          * Returns Instrumentation return code
755          */
getResultCode()756         public int getResultCode() { return mResultCode; }
757     }
758 
759     /**
760      * dEQP platfom query instrumentation parser
761      */
762     private static class PlatformQueryInstrumentationParser
763         extends MultiLineReceiver {
764         private Map<String, String> mResultMap = new LinkedHashMap<>();
765         private int mResultCode;
766         private boolean mGotExitValue = false;
767 
768         /**
769          * {@inheritDoc}
770          */
771         @Override
processNewLines(String[] lines)772         public void processNewLines(String[] lines) {
773             for (String line : lines) {
774                 if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
775                     final String parts[] = line.substring(24).split("=", 2);
776                     if (parts.length == 2) {
777                         mResultMap.put(parts[0], parts[1]);
778                     } else {
779                         CLog.w("Instrumentation status format unexpected");
780                     }
781                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
782                     try {
783                         mResultCode = Integer.parseInt(line.substring(22));
784                         mGotExitValue = true;
785                     } catch (NumberFormatException ex) {
786                         CLog.w("Instrumentation code format unexpected");
787                     }
788                 }
789             }
790         }
791 
792         /**
793          * {@inheritDoc}
794          */
795         @Override
isCancelled()796         public boolean isCancelled() {
797             return false;
798         }
799 
800         /**
801          * Returns whether target instrumentation exited normally.
802          */
wasSuccessful()803         public boolean wasSuccessful() { return mGotExitValue; }
804 
805         /**
806          * Returns Instrumentation return code
807          */
getResultCode()808         public int getResultCode() { return mResultCode; }
809 
getResultMap()810         public Map<String, String> getResultMap() { return mResultMap; }
811     }
812 
813     /**
814      * Interface for sleeping.
815      *
816      * Exposed for unit testing
817      */
818     public static interface ISleepProvider {
sleep(int milliseconds)819         public void sleep(int milliseconds);
820     }
821 
822     private static class SleepProvider implements ISleepProvider {
823         @Override
sleep(int milliseconds)824         public void sleep(int milliseconds) {
825             RunUtil.getDefault().sleep(milliseconds);
826         }
827     }
828 
829     /**
830      * Interface for failure recovery.
831      *
832      * Exposed for unit testing
833      */
834     public static interface IRecovery {
835         /**
836          * Sets the sleep provider IRecovery works on
837          */
setSleepProvider(ISleepProvider sleepProvider)838         public void setSleepProvider(ISleepProvider sleepProvider);
839 
840         /**
841          * Sets the device IRecovery works on
842          */
setDevice(ITestDevice device)843         public void setDevice(ITestDevice device);
844 
845         /**
846          * Informs Recovery that test execution has progressed since the last
847          * recovery
848          */
onExecutionProgressed()849         public void onExecutionProgressed();
850 
851         /**
852          * Tries to recover device after failed refused connection.
853          *
854          * @throws DeviceNotAvailableException if recovery did not succeed
855          */
recoverConnectionRefused()856         public void recoverConnectionRefused()
857             throws DeviceNotAvailableException;
858 
859         /**
860          * Tries to recover device after abnormal execution termination or link
861          * failure.
862          *
863          * @throws DeviceNotAvailableException if recovery did not succeed
864          */
recoverComLinkKilled()865         public void recoverComLinkKilled() throws DeviceNotAvailableException;
866     }
867 
868     /**
869      * State machine for execution failure recovery.
870      *
871      * Exposed for unit testing
872      */
873     public static class Recovery implements IRecovery {
874         private int RETRY_COOLDOWN_MS = 6000;    // 6 seconds
875         private int PROCESS_KILL_WAIT_MS = 1000; // 1 second
876 
877         private static enum MachineState {
878             WAIT,    // recover by waiting
879             RECOVER, // recover by calling recover()
880             REBOOT,  // recover by rebooting
881             FAIL,    // cannot recover
882         }
883 
884         private MachineState mState = MachineState.WAIT;
885         private ITestDevice mDevice;
886         private ISleepProvider mSleepProvider;
887 
888         private static class ProcessKillFailureException extends Exception {}
889 
890         /**
891          * {@inheritDoc}
892          */
893         @Override
setSleepProvider(ISleepProvider sleepProvider)894         public void setSleepProvider(ISleepProvider sleepProvider) {
895             mSleepProvider = sleepProvider;
896         }
897 
898         /**
899          * {@inheritDoc}
900          */
901         @Override
setDevice(ITestDevice device)902         public void setDevice(ITestDevice device) {
903             mDevice = device;
904         }
905 
906         /**
907          * {@inheritDoc}
908          */
909         @Override
onExecutionProgressed()910         public void onExecutionProgressed() {
911             mState = MachineState.WAIT;
912         }
913 
914         /**
915          * {@inheritDoc}
916          */
917         @Override
recoverConnectionRefused()918         public void recoverConnectionRefused()
919             throws DeviceNotAvailableException {
920             switch (mState) {
921             case WAIT: // not a valid stratedy for connection refusal,
922                        // fallthrough
923             case RECOVER:
924                 // First failure, just try to recover
925                 CLog.w("ADB connection failed, trying to recover");
926                 mState = MachineState.REBOOT; // the next step is to reboot
927 
928                 try {
929                     recoverDevice();
930                 } catch (DeviceNotAvailableException ex) {
931                     // chain forward
932                     recoverConnectionRefused();
933                 }
934                 break;
935 
936             case REBOOT:
937                 // Second failure in a row, try to reboot
938                 CLog.w(
939                     "ADB connection failed after recovery, rebooting device");
940                 mState = MachineState.FAIL; // the next step is to fail
941 
942                 try {
943                     rebootDevice();
944                 } catch (DeviceNotAvailableException ex) {
945                     // chain forward
946                     recoverConnectionRefused();
947                 }
948                 break;
949 
950             case FAIL:
951                 // Third failure in a row, just fail
952                 CLog.w("Cannot recover ADB connection");
953                 throw new DeviceNotAvailableException(
954                     "failed to connect after reboot",
955                     mDevice.getSerialNumber());
956             }
957         }
958 
959         /**
960          * {@inheritDoc}
961          */
962         @Override
recoverComLinkKilled()963         public void recoverComLinkKilled() throws DeviceNotAvailableException {
964             switch (mState) {
965             case WAIT:
966                 // First failure, just try to wait and try again
967                 CLog.w("ADB link failed, retrying after a cooldown period");
968                 mState = MachineState
969                              .RECOVER; // the next step is to recover the device
970 
971                 waitCooldown();
972 
973                 // even if the link to deqp on-device process was killed, the
974                 // process might still be alive. Locate and terminate such
975                 // unwanted processes.
976                 try {
977                     killDeqpProcess();
978                 } catch (DeviceNotAvailableException ex) {
979                     // chain forward
980                     recoverComLinkKilled();
981                 } catch (ProcessKillFailureException ex) {
982                     // chain forward
983                     recoverComLinkKilled();
984                 }
985                 break;
986 
987             case RECOVER:
988                 // Second failure, just try to recover
989                 CLog.w("ADB link failed, trying to recover");
990                 mState = MachineState.REBOOT; // the next step is to reboot
991 
992                 try {
993                     recoverDevice();
994                     killDeqpProcess();
995                 } catch (DeviceNotAvailableException ex) {
996                     // chain forward
997                     recoverComLinkKilled();
998                 } catch (ProcessKillFailureException ex) {
999                     // chain forward
1000                     recoverComLinkKilled();
1001                 }
1002                 break;
1003 
1004             case REBOOT:
1005                 // Third failure in a row, try to reboot
1006                 CLog.w("ADB link failed after recovery, rebooting device");
1007                 mState = MachineState.FAIL; // the next step is to fail
1008 
1009                 try {
1010                     rebootDevice();
1011                 } catch (DeviceNotAvailableException ex) {
1012                     // chain forward
1013                     recoverComLinkKilled();
1014                 }
1015                 break;
1016 
1017             case FAIL:
1018                 // Fourth failure in a row, just fail
1019                 CLog.w("Cannot recover ADB connection");
1020                 throw new DeviceNotAvailableException(
1021                     "link killed after reboot", mDevice.getSerialNumber(),
1022                     DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
1023             }
1024         }
1025 
waitCooldown()1026         private void waitCooldown() { mSleepProvider.sleep(RETRY_COOLDOWN_MS); }
1027 
getDeqpProcessPids()1028         private Iterable<Integer> getDeqpProcessPids()
1029             throws DeviceNotAvailableException {
1030             final List<Integer> pids = new ArrayList<Integer>(2);
1031             final String processes =
1032                 mDevice.executeShellCommand("ps | grep com.drawelements");
1033             final String[] lines = processes.split("(\\r|\\n)+");
1034             for (String line : lines) {
1035                 final String[] fields = line.split("\\s+");
1036                 if (fields.length < 2) {
1037                     continue;
1038                 }
1039 
1040                 try {
1041                     final int processId = Integer.parseInt(fields[1], 10);
1042                     pids.add(processId);
1043                 } catch (NumberFormatException ex) {
1044                     continue;
1045                 }
1046             }
1047             return pids;
1048         }
1049 
killDeqpProcess()1050         private void killDeqpProcess()
1051             throws DeviceNotAvailableException, ProcessKillFailureException {
1052             for (Integer processId : getDeqpProcessPids()) {
1053                 mDevice.executeShellCommand(
1054                     String.format("kill -9 %d", processId));
1055             }
1056 
1057             mSleepProvider.sleep(PROCESS_KILL_WAIT_MS);
1058 
1059             // check that processes actually died
1060             if (getDeqpProcessPids().iterator().hasNext()) {
1061                 // a process is still alive, killing failed
1062                 throw new ProcessKillFailureException();
1063             }
1064         }
1065 
recoverDevice()1066         public void recoverDevice() throws DeviceNotAvailableException {
1067             ((IManagedTestDevice)mDevice).recoverDevice();
1068         }
1069 
rebootDevice()1070         private void rebootDevice() throws DeviceNotAvailableException {
1071             mDevice.reboot();
1072         }
1073     }
1074 
addTestsToInstancesMap( File testlist, String configName, String screenRotation, String surfaceType, boolean required, Map<TestDescription, Set<BatchRunConfiguration>> instances)1075     private static void addTestsToInstancesMap(
1076         File testlist, String configName, String screenRotation,
1077         String surfaceType, boolean required,
1078         Map<TestDescription, Set<BatchRunConfiguration>> instances) {
1079 
1080         try (final FileReader testlistInnerReader = new FileReader(testlist);
1081              final BufferedReader testlistReader =
1082                  new BufferedReader(testlistInnerReader)) {
1083 
1084             String testName;
1085             while ((testName = testlistReader.readLine()) != null) {
1086                 testName = testName.trim();
1087 
1088                 // Skip empty lines.
1089                 if (testName.isEmpty()) {
1090                     continue;
1091                 }
1092 
1093                 // Lines starting with "#" are comments.
1094                 if (testName.startsWith("#")) {
1095                     continue;
1096                 }
1097 
1098                 // If the "testName" ends with .txt, then it is a path to
1099                 // another test list (relative to the current test list, path
1100                 // separator is "/") that we need to read.
1101                 if (testName.endsWith(".txt")) {
1102                     addTestsToInstancesMap(
1103                         Paths.get(testlist.getParent(), testName.split("/"))
1104                             .toFile(),
1105                         configName, screenRotation, surfaceType, required,
1106                         instances);
1107                     continue;
1108                 }
1109 
1110                 // Test name -> testId -> only one config -> done.
1111                 final Set<BatchRunConfiguration> testInstanceSet =
1112                     new LinkedHashSet<>();
1113                 BatchRunConfiguration config = new BatchRunConfiguration(
1114                     configName, screenRotation, surfaceType, required);
1115                 testInstanceSet.add(config);
1116                 TestDescription test = pathToIdentifier(testName);
1117                 instances.put(test, testInstanceSet);
1118             }
1119         } catch (IOException e) {
1120             throw new RuntimeException(
1121                 "Failure while reading the test case list for deqp: " +
1122                 e.getMessage());
1123         }
1124     }
1125 
1126     private Set<BatchRunConfiguration>
getTestRunConfigs(TestDescription testId)1127     getTestRunConfigs(TestDescription testId) {
1128         return mTestInstances.get(testId);
1129     }
1130 
1131     /**
1132      * Get the test instance of the runner. Exposed for testing.
1133      */
getTestInstance()1134     Map<TestDescription, Set<BatchRunConfiguration>> getTestInstance() {
1135         return mTestInstances;
1136     }
1137 
1138     /**
1139      * Converts dEQP testcase path to TestDescription.
1140      */
pathToIdentifier(String testPath)1141     protected static TestDescription pathToIdentifier(String testPath) {
1142         int indexOfLastDot = testPath.lastIndexOf('.');
1143         String className = testPath.substring(0, indexOfLastDot);
1144         String testName = testPath.substring(indexOfLastDot + 1);
1145 
1146         return new TestDescription(className, testName);
1147     }
1148 
1149     // \todo [2015-10-16 kalle] How unique should this be?
getId()1150     protected String getId() {
1151         return AbiUtils.createId(mAbi.getName(), mDeqpPackage);
1152     }
1153 
1154     /**
1155      * Generates tescase trie from dEQP testcase paths. Used to define which
1156      * testcases to execute.
1157      */
1158     protected static String
generateTestCaseTrieFromPaths(Collection<String> tests)1159     generateTestCaseTrieFromPaths(Collection<String> tests) {
1160         String result = "{";
1161         boolean first = true;
1162 
1163         // Add testcases to results
1164         for (Iterator<String> iter = tests.iterator(); iter.hasNext();) {
1165             String test = iter.next();
1166             String[] components = test.split("\\.");
1167 
1168             if (components.length == 1) {
1169                 if (!first) {
1170                     result = result + ",";
1171                 }
1172                 first = false;
1173 
1174                 result += components[0];
1175                 iter.remove();
1176             }
1177         }
1178 
1179         if (!tests.isEmpty()) {
1180             HashMap<String, ArrayList<String>> testGroups = new HashMap<>();
1181 
1182             // Collect all sub testgroups
1183             for (String test : tests) {
1184                 String[] components = test.split("\\.");
1185                 ArrayList<String> testGroup = testGroups.get(components[0]);
1186 
1187                 if (testGroup == null) {
1188                     testGroup = new ArrayList<String>();
1189                     testGroups.put(components[0], testGroup);
1190                 }
1191 
1192                 testGroup.add(test.substring(components[0].length() + 1));
1193             }
1194 
1195             for (String testGroup : testGroups.keySet()) {
1196                 if (!first) {
1197                     result = result + ",";
1198                 }
1199 
1200                 first = false;
1201                 result =
1202                     result + testGroup +
1203                     generateTestCaseTrieFromPaths(testGroups.get(testGroup));
1204             }
1205         }
1206 
1207         return result + "}";
1208     }
1209 
1210     /**
1211      * Generates testcase trie from TestDescriptions.
1212      */
1213     protected static String
generateTestCaseTrie(Collection<TestDescription> tests)1214     generateTestCaseTrie(Collection<TestDescription> tests) {
1215         ArrayList<String> testPaths = new ArrayList<String>();
1216 
1217         for (TestDescription test : tests) {
1218             testPaths.add(test.getClassName() + "." + test.getTestName());
1219         }
1220 
1221         return generateTestCaseTrieFromPaths(testPaths);
1222     }
1223 
1224     protected static class TestBatch {
1225         private BatchRunConfiguration mConfig;
1226         protected List<TestDescription> mTests;
1227 
getTestBatchConfig()1228         public BatchRunConfiguration getTestBatchConfig() {return mConfig;}
getTestBatchTestDescriptionList()1229         public List<TestDescription> getTestBatchTestDescriptionList() {return mTests;}
setTestBatchConfig(BatchRunConfiguration config)1230         public void setTestBatchConfig(BatchRunConfiguration config) {mConfig = config;}
setTestBatchTestDescriptionList(List<TestDescription> tests)1231         public void setTestBatchTestDescriptionList(List<TestDescription> tests) {mTests = tests;}
1232     }
1233 
1234     /**
1235      * Creates a TestBatch from the given tests or null if not tests remaining.
1236      *
1237      *  @param pool List of tests to select from
1238      *  @param requiredConfig Select only instances with pending requiredConfig,
1239      * or null to select any run configuration.
1240      */
selectRunBatch(Collection<TestDescription> pool, BatchRunConfiguration requiredConfig)1241     protected TestBatch selectRunBatch(Collection<TestDescription> pool,
1242                                      BatchRunConfiguration requiredConfig) {
1243         // select one test (leading test) that is going to be executed and then
1244         // pack along as many other compatible instances as possible.
1245         TestDescription leadingTest = null;
1246         for (TestDescription test : pool) {
1247             if (!mRemainingTests.contains(test)) {
1248                 continue;
1249             }
1250             if (requiredConfig != null &&
1251                 !getInstanceListener().isPendingTestInstance(test,
1252                                                           requiredConfig)) {
1253                 continue;
1254             }
1255             leadingTest = test;
1256             break;
1257         }
1258 
1259         // no remaining tests?
1260         if (leadingTest == null) {
1261             return null;
1262         }
1263 
1264         BatchRunConfiguration leadingTestConfig = null;
1265         if (requiredConfig != null) {
1266             leadingTestConfig = requiredConfig;
1267         } else {
1268             for (BatchRunConfiguration runConfig :
1269                  getTestRunConfigs(leadingTest)) {
1270                 if (getInstanceListener().isPendingTestInstance(leadingTest,
1271                                                              runConfig)) {
1272                     leadingTestConfig = runConfig;
1273                     break;
1274                 }
1275             }
1276         }
1277 
1278         // test pending <=> test has a pending config
1279         if (leadingTestConfig == null) {
1280             throw new AssertionError("search postcondition failed");
1281         }
1282 
1283         final int leadingInstability = getTestInstabilityRating(leadingTest);
1284 
1285         final TestBatch runBatch = new TestBatch();
1286         runBatch.setTestBatchConfig(leadingTestConfig);
1287         List<TestDescription> runBatchTests = new ArrayList<>();
1288         runBatchTests.add(leadingTest);
1289 
1290         for (TestDescription test : pool) {
1291             if (test == leadingTest) {
1292                 // do not re-select the leading tests
1293                 continue;
1294             }
1295             if (!getInstanceListener().isPendingTestInstance(test,
1296                                                           leadingTestConfig)) {
1297                 // select only compatible
1298                 continue;
1299             }
1300             if (getTestInstabilityRating(test) != leadingInstability) {
1301                 // pack along only cases in the same stability category. Packing
1302                 // more dangerous tests along jeopardizes the stability of this
1303                 // run. Packing more stable tests along jeopardizes their
1304                 // stability rating.
1305                 continue;
1306             }
1307             if (runBatchTests.size() >=
1308                 getBatchSizeLimitForInstability(leadingInstability)) {
1309                 // batch size is limited.
1310                 break;
1311             }
1312             runBatchTests.add(test);
1313         }
1314         runBatch.setTestBatchTestDescriptionList(runBatchTests);
1315 
1316         return runBatch;
1317     }
1318 
getBatchSizeLimit()1319     private int getBatchSizeLimit() {
1320         return TESTCASE_BATCH_LIMIT;
1321     }
1322 
getBatchNumPendingCases(TestBatch batch)1323     protected int getBatchNumPendingCases(TestBatch batch) {
1324         int numPending = 0;
1325         for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1326             if (getInstanceListener().isPendingTestInstance(test, batch.getTestBatchConfig())) {
1327                 ++numPending;
1328             }
1329         }
1330         return numPending;
1331     }
1332 
getBatchSizeLimitForInstability(int batchInstabilityRating)1333     protected int getBatchSizeLimitForInstability(int batchInstabilityRating) {
1334         // reduce group size exponentially down to one
1335         return Math.max(1, getBatchSizeLimit() / (1 << batchInstabilityRating));
1336     }
1337 
getTestInstabilityRating(TestDescription testId)1338     protected int getTestInstabilityRating(TestDescription testId) {
1339         if (mTestInstabilityRatings.containsKey(testId)) {
1340             return mTestInstabilityRatings.get(testId);
1341         } else {
1342             return 0;
1343         }
1344     }
1345 
recordTestInstability(TestDescription testId)1346     protected void recordTestInstability(TestDescription testId) {
1347         mTestInstabilityRatings.put(testId,
1348                                     getTestInstabilityRating(testId) + 1);
1349     }
1350 
clearTestInstability(TestDescription testId)1351     protected void clearTestInstability(TestDescription testId) {
1352         mTestInstabilityRatings.put(testId, 0);
1353     }
1354 
1355     /**
1356      * Executes all tests on the device.
1357      */
runTests()1358     private void runTests()
1359         throws DeviceNotAvailableException, CapabilityQueryFailureException {
1360         for (;;) {
1361             TestBatch batch = selectRunBatch(mTestInstances.keySet(), null);
1362 
1363             if (batch == null) {
1364                 break;
1365             }
1366 
1367             runTestRunBatch(batch);
1368         }
1369     }
1370 
1371     /**
1372      * Runs a TestBatch by either faking it or executing it on a device.
1373      */
runTestRunBatch(TestBatch batch)1374     protected void runTestRunBatch(TestBatch batch)
1375         throws DeviceNotAvailableException, CapabilityQueryFailureException {
1376         // prepare instance listener
1377         getInstanceListener().setCurrentConfig(batch.getTestBatchConfig());
1378         for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1379             getInstanceListener().setTestInstances(test, getTestRunConfigs(test));
1380         }
1381 
1382         // execute only if config is executable, else fake results
1383         if (isSupportedRunConfiguration(batch.getTestBatchConfig())) {
1384             executeTestRunBatch(batch);
1385         } else {
1386             if (batch.getTestBatchConfig().isRequired()) {
1387                 fakeFailTestRunBatch(batch);
1388             } else {
1389                 fakePassTestRunBatch(batch);
1390             }
1391         }
1392     }
1393 
1394     /**
1395      * Checks if the runner should ignore dEQP tests completely and report nothing.
1396      */
shouldBypassTestExecutionAndReporting()1397     private boolean shouldBypassTestExecutionAndReporting() {
1398         // When the incremental dEQP attribute is set, the run is just for dEQP dependencies
1399         // collection and should be done by the dEQP binary. There is no need to run dEQP tests by
1400         // the runner.
1401         IBuildInfo buildInfo = mBuildHelper.getBuildInfo();
1402         return buildInfo.getBuildAttributes().containsKey(
1403             IncrementalDeqpPreparer.INCREMENTAL_DEQP_ATTRIBUTE_NAME);
1404     }
1405 
isSupportedRunConfiguration(BatchRunConfiguration runConfig)1406     private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig)
1407         throws DeviceNotAvailableException, CapabilityQueryFailureException {
1408         // orientation support
1409         if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(
1410                 runConfig.getRotation())) {
1411             final Map<String, Optional<Integer>> features =
1412                 getDeviceFeatures(mDevice);
1413 
1414             if (isPortraitClassRotation(runConfig.getRotation()) &&
1415                 !features.containsKey(FEATURE_PORTRAIT)) {
1416                 return false;
1417             }
1418             if (isLandscapeClassRotation(runConfig.getRotation()) &&
1419                 !features.containsKey(FEATURE_LANDSCAPE)) {
1420                 return false;
1421             }
1422         }
1423 
1424         if (isOpenGlEsPackage()) {
1425             // renderability support for OpenGL ES tests
1426             return isSupportedGlesRenderConfig(runConfig);
1427         } else {
1428             return true;
1429         }
1430     }
1431 
1432     protected static final class AdbComLinkOpenError extends Exception {
AdbComLinkOpenError(String description, Throwable inner)1433         public AdbComLinkOpenError(String description, Throwable inner) {
1434             super(description, inner);
1435         }
1436     }
1437 
1438     protected static final class AdbComLinkKilledError extends Exception {
AdbComLinkKilledError(String description, Throwable inner)1439         public AdbComLinkKilledError(String description, Throwable inner) {
1440             super(description, inner);
1441         }
1442     }
1443 
1444     protected static final class AdbComLinkUnresponsiveError extends Exception {
AdbComLinkUnresponsiveError(String description, Throwable inner)1445         public AdbComLinkUnresponsiveError(String description,
1446                                            Throwable inner) {
1447             super(description, inner);
1448         }
1449     }
1450 
1451     /**
1452      * Executes a given command in adb shell
1453      *
1454      * @throws AdbComLinkOpenError if connection cannot be established.
1455      * @throws AdbComLinkKilledError if established connection is killed
1456      *     prematurely.
1457      */
1458     protected void
executeShellCommandAndReadOutput(final String command, final IShellOutputReceiver receiver)1459     executeShellCommandAndReadOutput(final String command,
1460                                      final IShellOutputReceiver receiver)
1461         throws AdbComLinkOpenError, AdbComLinkKilledError,
1462                AdbComLinkUnresponsiveError {
1463         try {
1464             mDevice.getIDevice().executeShellCommand(command, receiver,
1465                                                      mUnresponsiveCmdTimeoutMs,
1466                                                      TimeUnit.MILLISECONDS);
1467         } catch (TimeoutException ex) {
1468             // Opening connection timed out
1469             throw new AdbComLinkOpenError("opening connection timed out", ex);
1470         } catch (AdbCommandRejectedException ex) {
1471             // Command rejected
1472             throw new AdbComLinkOpenError("command rejected", ex);
1473         } catch (IOException ex) {
1474             // shell command channel killed
1475             throw new AdbComLinkKilledError("command link killed", ex);
1476         } catch (ShellCommandUnresponsiveException ex) {
1477             // shell command halted
1478             throw new AdbComLinkUnresponsiveError(
1479                 "command link was unresponsive for longer than requested timeout",
1480                 ex);
1481         }
1482     }
1483 
1484     /**
1485      * Executes given test batch on a device
1486      */
executeTestRunBatch(TestBatch batch)1487     protected void executeTestRunBatch(TestBatch batch)
1488         throws DeviceNotAvailableException {
1489         final String instrumentationName =
1490             "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation";
1491 
1492         final StringBuilder deqpCmdLine = new StringBuilder();
1493         deqpCmdLine.append("--deqp-caselist-file=");
1494         deqpCmdLine.append(APP_DIR + CASE_LIST_FILE_NAME);
1495         deqpCmdLine.append(" ");
1496         deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.getTestBatchConfig()));
1497 
1498         // If we are not logging data, do not bother outputting the images from
1499         // the test exe.
1500         if (!mLogData) {
1501             deqpCmdLine.append(" --deqp-log-images=disable");
1502         }
1503 
1504         if (!mDisableWatchdog) {
1505             deqpCmdLine.append(" --deqp-watchdog=enable");
1506         }
1507 
1508         final String command = String.format(
1509             "am instrument %s -w -e deqpLogFilename \"%s\" -e deqpCmdLine \"%s\""
1510                 + " -e deqpLogData \"%s\" %s",
1511             AbiUtils.createAbiFlag(mAbi.getName()), APP_DIR + LOG_FILE_NAME,
1512             deqpCmdLine.toString(), mLogData, instrumentationName);
1513 
1514         final InstrumentationParser parser =
1515             new InstrumentationParser(getInstanceListener());
1516         // attempt full run once
1517         executeTestRunBatchRun(batch, instrumentationName, command, parser);
1518 
1519         // split remaining tests to two sub batches and execute both. This will
1520         // terminate since executeTestRunBatchRun will always progress for a
1521         // batch of size 1.
1522         final ArrayList<TestDescription> pendingTests = new ArrayList<>();
1523 
1524         for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1525             if (getInstanceListener().isPendingTestInstance(test, batch.getTestBatchConfig())) {
1526                 pendingTests.add(test);
1527             }
1528         }
1529 
1530         final int divisorNdx = pendingTests.size() / 2;
1531         final List<TestDescription> headList =
1532             pendingTests.subList(0, divisorNdx);
1533         final List<TestDescription> tailList =
1534             pendingTests.subList(divisorNdx, pendingTests.size());
1535 
1536         // head
1537         for (;;) {
1538             TestBatch subBatch = selectRunBatch(headList, batch.getTestBatchConfig());
1539 
1540             if (subBatch == null) {
1541                 break;
1542             }
1543 
1544             executeTestRunBatch(subBatch);
1545         }
1546 
1547         // tail
1548         for (;;) {
1549             TestBatch subBatch = selectRunBatch(tailList, batch.getTestBatchConfig());
1550 
1551             if (subBatch == null) {
1552                 break;
1553             }
1554 
1555             executeTestRunBatch(subBatch);
1556         }
1557 
1558         if (getBatchNumPendingCases(batch) != 0) {
1559             throw new AssertionError(
1560                 "executeTestRunBatch postcondition failed");
1561         }
1562     }
1563 
1564     /**
1565      * Runs one execution pass over the given batch.
1566      *
1567      * Tries to run the batch. Always makes progress (executes instances or
1568      * modifies stability scores).
1569      */
executeTestRunBatchRun(TestBatch batch, final String instrumentationName, final String runCommand, final InstrumentationParser parser)1570     protected void executeTestRunBatchRun(TestBatch batch, final String instrumentationName, final String runCommand, final InstrumentationParser parser)
1571         throws DeviceNotAvailableException {
1572         if (getBatchNumPendingCases(batch) != batch.getTestBatchTestDescriptionList().size()) {
1573             throw new AssertionError(
1574                 "executeTestRunBatchRun precondition failed");
1575         }
1576 
1577         checkInterrupted(); // throws if interrupted
1578 
1579         final String testCases = generateTestCaseTrie(batch.getTestBatchTestDescriptionList());
1580         final String testCaseFilename = APP_DIR + CASE_LIST_FILE_NAME;
1581         mDevice.executeShellCommand("rm " + testCaseFilename);
1582         mDevice.executeShellCommand("rm " + APP_DIR + LOG_FILE_NAME);
1583         if (!mDevice.pushString(testCases + "\n", testCaseFilename)) {
1584             throw new RuntimeException("Failed to write test cases to " +
1585                                        testCaseFilename);
1586         }
1587 
1588         final int numRemainingInstancesBefore = getNumRemainingInstances();
1589         Throwable interruptingError = null;
1590 
1591         // Fix the requirement of sleep() between batches
1592         long duration = System.currentTimeMillis() - mTimeOfLastRun;
1593         if (duration < 5000) {
1594             CLog.i("Sleeping for %dms", 5000 - duration);
1595             mRunUtil.sleep(5000 - duration);
1596         }
1597 
1598         try {
1599             executeShellCommandAndReadOutput(runCommand, parser);
1600         } catch (Throwable ex) {
1601             interruptingError = ex;
1602         } finally {
1603             mTimeOfLastRun = System.currentTimeMillis();
1604             parser.flush();
1605         }
1606 
1607         final boolean progressedSinceLastCall =
1608             getInstanceListener().getCurrentTestId() != null ||
1609             getNumRemainingInstances() < numRemainingInstancesBefore;
1610 
1611         if (progressedSinceLastCall) {
1612             mDeviceRecovery.onExecutionProgressed();
1613         }
1614 
1615         // interrupted either because of ADB or test timeout
1616         if (interruptingError != null) {
1617 
1618             // AdbComLinkUnresponsiveError means the test has timeout during
1619             // execution. Device is likely fine, so we won't attempt to recover
1620             // the device.
1621             if (interruptingError instanceof AdbComLinkUnresponsiveError) {
1622                 getInstanceListener().abortTest(
1623                     getInstanceListener().getCurrentTestId(), TIMEOUT_LOG_MESSAGE);
1624             } else if (interruptingError instanceof AdbComLinkOpenError) {
1625                 mDeviceRecovery.recoverConnectionRefused();
1626             } else if (interruptingError instanceof AdbComLinkKilledError) {
1627                 mDeviceRecovery.recoverComLinkKilled();
1628             } else if (interruptingError instanceof RunInterruptedException) {
1629                 // external run interruption request. Terminate immediately.
1630                 throw (RunInterruptedException)interruptingError;
1631             } else {
1632                 CLog.e(interruptingError);
1633                 throw new RuntimeException(interruptingError);
1634             }
1635 
1636             // recoverXXX did not throw => recovery succeeded
1637         } else if (!parser.wasSuccessful()) {
1638             mDeviceRecovery.recoverComLinkKilled();
1639             // recoverXXX did not throw => recovery succeeded
1640         }
1641 
1642         // Progress guarantees.
1643         if (batch.getTestBatchTestDescriptionList().size() == 1) {
1644             final TestDescription onlyTest = batch.getTestBatchTestDescriptionList().iterator().next();
1645             final boolean wasTestExecuted =
1646                 !getInstanceListener().isPendingTestInstance(onlyTest,
1647                                                           batch.getTestBatchConfig()) &&
1648                 getInstanceListener().getCurrentTestId() == null;
1649             final boolean wasLinkFailure =
1650                 !parser.wasSuccessful() || interruptingError != null;
1651 
1652             // Link failures can be caused by external events, require at least
1653             // two observations until bailing.
1654             if (!wasTestExecuted &&
1655                 (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) {
1656                 recordTestInstability(onlyTest);
1657                 // If we cannot finish the test, mark the case as a crash.
1658                 //
1659                 // If we couldn't even start the test, fail the test instance as
1660                 // non-executable. This is required so that a consistently
1661                 // crashing or non-existent tests will not cause futile
1662                 // (non-terminating) re-execution attempts.
1663                 if (getInstanceListener().getCurrentTestId() != null) {
1664                     getInstanceListener().abortTest(onlyTest,
1665                                                  INCOMPLETE_LOG_MESSAGE);
1666                 } else {
1667                     getInstanceListener().abortTest(onlyTest,
1668                                                  NOT_EXECUTABLE_LOG_MESSAGE);
1669                 }
1670             } else if (wasTestExecuted) {
1671                 clearTestInstability(onlyTest);
1672             }
1673         } else {
1674             // Analyze results to update test stability ratings. If there is no
1675             // interrupting test logged, increase instability rating of all
1676             // remaining tests. If there is a interrupting test logged, increase
1677             // only its instability rating.
1678             //
1679             // A successful run of tests clears instability rating.
1680             if (getInstanceListener().getCurrentTestId() == null) {
1681                 for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1682                     if (getInstanceListener().isPendingTestInstance(
1683                             test, batch.getTestBatchConfig())) {
1684                         recordTestInstability(test);
1685                     } else {
1686                         clearTestInstability(test);
1687                     }
1688                 }
1689             } else {
1690                 recordTestInstability(getInstanceListener().getCurrentTestId());
1691                 for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1692                     // \note: isPendingTestInstance is false for
1693                     // getCurrentTestId. Current ID is considered 'running' and
1694                     // will be restored to 'pending' in endBatch().
1695                     if (!test.equals(getInstanceListener().getCurrentTestId()) &&
1696                         !getInstanceListener().isPendingTestInstance(
1697                             test, batch.getTestBatchConfig())) {
1698                         clearTestInstability(test);
1699                     }
1700                 }
1701             }
1702         }
1703 
1704         getInstanceListener().endBatch();
1705     }
1706 
1707     protected String
getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig)1708     getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) {
1709         final StringBuilder deqpCmdLine = new StringBuilder();
1710         if (!runConfig.getGlConfig().isEmpty()) {
1711             deqpCmdLine.append("--deqp-gl-config-name=");
1712             deqpCmdLine.append(runConfig.getGlConfig());
1713         }
1714         if (!runConfig.getRotation().isEmpty()) {
1715             if (deqpCmdLine.length() != 0) {
1716                 deqpCmdLine.append(" ");
1717             }
1718             deqpCmdLine.append("--deqp-screen-rotation=");
1719             deqpCmdLine.append(runConfig.getRotation());
1720         }
1721         if (!runConfig.getSurfaceType().isEmpty()) {
1722             if (deqpCmdLine.length() != 0) {
1723                 deqpCmdLine.append(" ");
1724             }
1725             deqpCmdLine.append("--deqp-surface-type=");
1726             deqpCmdLine.append(runConfig.getSurfaceType());
1727         }
1728         return deqpCmdLine.toString();
1729     }
1730 
getNumRemainingInstances()1731     protected int getNumRemainingInstances() {
1732         int retVal = 0;
1733         for (TestDescription testId : mRemainingTests) {
1734             // If case is in current working set, sum only not yet executed
1735             // instances. If case is not in current working set, sum all
1736             // instances (since they are not yet executed).
1737             if (getInstanceListener().mPendingResults.containsKey(testId)) {
1738                 retVal += getInstanceListener().mPendingResults.get(testId)
1739                               .remainingConfigs.size();
1740             } else {
1741                 retVal += mTestInstances.get(testId).size();
1742             }
1743         }
1744         return retVal;
1745     }
1746 
1747     /**
1748      * Checks if this execution has been marked as interrupted and throws if it
1749      * has.
1750      */
checkInterrupted()1751     protected void checkInterrupted() throws RunInterruptedException {
1752         // Work around the API. RunUtil::checkInterrupted is private but we can
1753         // call it indirectly by sleeping a value <= 0.
1754         mRunUtil.sleep(0);
1755     }
1756 
1757     /**
1758      * Pass given batch tests without running it
1759      */
fakePassTestRunBatch(TestBatch batch)1760     private void fakePassTestRunBatch(TestBatch batch) {
1761         for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1762             CLog.d(
1763                 "Marking '%s' invocation in config '%s' as passed without running",
1764                 test.toString(), batch.getTestBatchConfig().getId());
1765             getInstanceListener().skipTest(test);
1766         }
1767     }
1768 
1769     /**
1770      * Fail given batch tests without running it
1771      */
fakeFailTestRunBatch(TestBatch batch)1772     private void fakeFailTestRunBatch(TestBatch batch) {
1773         for (TestDescription test : batch.getTestBatchTestDescriptionList()) {
1774             CLog.d(
1775                 "Marking '%s' invocation in config '%s' as failed without running",
1776                 test.toString(), batch.getTestBatchConfig().getId());
1777             getInstanceListener().abortTest(test, "Required config not supported");
1778         }
1779     }
1780 
1781     /**
1782      * Pass tests without running them
1783      */
fakePassTests(ITestInvocationListener listener)1784     private void fakePassTests(ITestInvocationListener listener) {
1785         HashMap<String, Metric> emptyMap = new HashMap<>();
1786         for (TestDescription test : mTestInstances.keySet()) {
1787             listener.testStarted(test);
1788             listener.testEnded(test, emptyMap);
1789         }
1790 
1791         // Log only once all the skipped tests
1792         CLog.d(
1793             "Marking tests '%s', as pass because tests are simply being collected",
1794             mTestInstances.keySet());
1795         mRemainingTests.removeAll(mTestInstances.keySet());
1796     }
1797 
1798     /**
1799      * Ignoring tests without running them
1800      */
1801     private void
markTestsAsAssumptionFailure(ITestInvocationListener listener)1802     markTestsAsAssumptionFailure(ITestInvocationListener listener) {
1803         HashMap<String, Metric> emptyMap = new HashMap<>();
1804         for (TestDescription test : mTestInstances.keySet()) {
1805             listener.testStarted(test);
1806             listener.testAssumptionFailure(
1807                 test, ASSUMPTION_FAILURE_DEQP_LEVEL_LOG_MESSAGE);
1808             listener.testEnded(test, emptyMap);
1809         }
1810 
1811         // Log only once after all the tests marked as assumption Failure
1812         CLog.d(
1813             "Assumption failed for tests '%s' because features are not supported by device",
1814             mRemainingTests);
1815         mRemainingTests.removeAll(mTestInstances.keySet());
1816     }
1817 
1818     /**
1819      * Ignoring tests without running them
1820      */
ignoreTests(ITestInvocationListener listener)1821     private void ignoreTests(ITestInvocationListener listener) {
1822         HashMap<String, Metric> emptyMap = new HashMap<>();
1823         for (TestDescription test : mTestInstances.keySet()) {
1824             listener.testStarted(test);
1825             listener.testIgnored(test);
1826             listener.testEnded(test, emptyMap);
1827         }
1828 
1829         // Log only once after all the tests ignored
1830         CLog.d(
1831             "Tests '%s', ignored because they are not required by the deqp level",
1832             mRemainingTests);
1833         mRemainingTests.removeAll(mTestInstances.keySet());
1834     }
1835 
1836     /**
1837      * Check if device supports Vulkan.
1838      */
isSupportedVulkan()1839     private boolean isSupportedVulkan()
1840         throws DeviceNotAvailableException, CapabilityQueryFailureException {
1841         final Map<String, Optional<Integer>> features =
1842             getDeviceFeatures(mDevice);
1843 
1844         for (String feature : features.keySet()) {
1845             if (feature.startsWith(FEATURE_VULKAN_LEVEL)) {
1846                 return true;
1847             }
1848         }
1849 
1850         return false;
1851     }
1852 
1853     /**
1854      * Check whether the device's claimed dEQP level is high enough that it
1855      * should pass the tests in the caselist.
1856      */
claimedDeqpLevelIsRecentEnough()1857     private boolean claimedDeqpLevelIsRecentEnough()
1858         throws CapabilityQueryFailureException, DeviceNotAvailableException {
1859         if (mForceDeqpLevel.equals("all")) {
1860             CLog.d("All deqp levels have been forced for this run");
1861             return true;
1862         }
1863 
1864         // Determine whether we need to check the dEQP feature flag for Vulkan
1865         // or OpenGL ES.
1866         final String featureName;
1867         if (isVulkanPackage()) {
1868             featureName = FEATURE_VULKAN_DEQP_LEVEL;
1869         } else if (isOpenGlEsPackage() || isEglPackage()) {
1870             // The OpenGL ES feature flag is used for EGL as well.
1871             featureName = FEATURE_OPENGLES_DEQP_LEVEL;
1872         } else {
1873             throw new AssertionError(
1874                 "Claims about dEQP support should only be checked for Vulkan, OpenGL ES, or EGL "
1875                 + "packages");
1876         }
1877 
1878         CLog.d("For caselist \"%s\", the dEQP level feature flag is \"%s\".",
1879                mCaselistFile, featureName);
1880 
1881         // A Vulkan/OpenGL ES caselist filename has the form:
1882         //     {gles2,gles3,gles31,vk,egl}-main-YYYY-MM-DD.txt
1883         final Pattern caseListFilenamePattern =
1884             Pattern.compile("-main-(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)\\.txt$");
1885         final Matcher matcher = caseListFilenamePattern.matcher(mCaselistFile);
1886         if (!matcher.find()) {
1887             CLog.d(
1888                 "No dEQP level date found in caselist. Running unconditionally.");
1889             return true;
1890         }
1891 
1892         final int year = Integer.parseInt(matcher.group(1));
1893         final int month = Integer.parseInt(matcher.group(2));
1894         final int day = Integer.parseInt(matcher.group(3));
1895         CLog.d("Caselist date is %04d-%02d-%02d", year, month, day);
1896 
1897         // As per the documentation for FEATURE_VULKAN_DEQP_LEVEL and
1898         // FEATURE_OPENGLES_DEQP_LEVEL in android.content.pm.PackageManager, a
1899         // year is encoded as an integer by devoting bits 31-16 to year, 15-8 to
1900         // month and 7-0 to day.
1901         final int minimumLevel = (year << 16) + (month << 8) + day;
1902 
1903         CLog.d("For reference, date -> level mappings are:");
1904         CLog.d("    2019-03-01 -> 132317953");
1905         CLog.d("    2020-03-01 -> 132383489");
1906         CLog.d("    2021-03-01 -> 132449025");
1907         CLog.d("    2022-03-01 -> 132514561");
1908         CLog.d("    2023-03-01 -> 132580097");
1909         CLog.d("    2024-03-01 -> 132645633");
1910 	CLog.d("    2025-03-01 -> 132711169");
1911 
1912         CLog.d("Minimum level required to run this caselist is %d",
1913                minimumLevel);
1914 
1915         if (!mForceDeqpLevel.isEmpty()) {
1916             int forcedDepqLevel;
1917             try {
1918                 forcedDepqLevel = Integer.parseInt(mForceDeqpLevel);
1919                 CLog.d("%s forced as deqp level");
1920             } catch (NumberFormatException e) {
1921                 throw new AssertionError(
1922                     "Deqp Level is not an acceptable numeric value");
1923             }
1924 
1925             final boolean shouldRunCaselist = forcedDepqLevel >= minimumLevel;
1926             CLog.d("Running caselist? %b", shouldRunCaselist);
1927             return shouldRunCaselist;
1928         }
1929 
1930         // Now look for the feature flag.
1931         final Map<String, Optional<Integer>> features =
1932             getDeviceFeatures(mDevice);
1933 
1934         for (String feature : features.keySet()) {
1935             if (feature.startsWith(featureName)) {
1936                 final Optional<Integer> claimedDeqpLevel =
1937                     features.get(feature);
1938                 if (!claimedDeqpLevel.isPresent()) {
1939                     throw new IllegalStateException(
1940                         "Feature " + featureName +
1941                         " has no associated version");
1942                 }
1943                 CLog.d("Device level is %d", claimedDeqpLevel.get());
1944 
1945                 final boolean shouldRunCaselist =
1946                     claimedDeqpLevel.get() >= minimumLevel;
1947                 CLog.d("Running caselist? %b", shouldRunCaselist);
1948                 return shouldRunCaselist;
1949             }
1950         }
1951 
1952         CLog.d("Could not find dEQP level feature flag \"%s\".", featureName);
1953 
1954         // A Vulkan dEQP level has been required since R.
1955         // A GLES/EGL dEQP level has only been required since S.
1956         // Thus, if the VSR level is <= R and there is no GLES dEQP level, then
1957         // we can assume a GLES dEQP level of R (2020).
1958         if (PropertyUtil.getVsrApiLevel(mDevice) <= R_API_LEVEL &&
1959             FEATURE_OPENGLES_DEQP_LEVEL.equals(featureName)) {
1960             final int claimedDeqpLevel = DEQP_LEVEL_R_2020;
1961             CLog.d("Device level is %d due to VSR R", claimedDeqpLevel);
1962             final boolean shouldRunCaselist = claimedDeqpLevel >= minimumLevel;
1963             CLog.d("Running caselist? %b", shouldRunCaselist);
1964             return shouldRunCaselist;
1965         }
1966 
1967         CLog.d("Running caselist unconditionally");
1968 
1969         return true;
1970     }
1971 
1972     /**
1973      * Check if device supports OpenGL ES version.
1974      */
isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)1975     private static boolean isSupportedGles(ITestDevice device,
1976                                            int requiredMajorVersion,
1977                                            int requiredMinorVersion)
1978         throws DeviceNotAvailableException {
1979         String roOpenglesVersion = device.getProperty("ro.opengles.version");
1980 
1981         if (roOpenglesVersion == null)
1982             return false;
1983 
1984         int intValue = Integer.parseInt(roOpenglesVersion);
1985 
1986         int majorVersion = ((intValue & 0xffff0000) >> 16);
1987         int minorVersion = (intValue & 0xffff);
1988 
1989         return (majorVersion > requiredMajorVersion) ||
1990             (majorVersion == requiredMajorVersion &&
1991              minorVersion >= requiredMinorVersion);
1992     }
1993 
1994     /**
1995      * Query if rendertarget is supported
1996      */
isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)1997     private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
1998         throws DeviceNotAvailableException, CapabilityQueryFailureException {
1999         // query if configuration is supported
2000         final StringBuilder configCommandLine =
2001             new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
2002         if (configCommandLine.length() != 0) {
2003             configCommandLine.append(" ");
2004         }
2005         configCommandLine.append("--deqp-gl-major-version=");
2006         configCommandLine.append(getGlesMajorVersion());
2007         configCommandLine.append(" --deqp-gl-minor-version=");
2008         configCommandLine.append(getGlesMinorVersion());
2009 
2010         final String commandLine = configCommandLine.toString();
2011 
2012         // check for cached result first
2013         if (mConfigQuerySupportCache.containsKey(commandLine)) {
2014             return mConfigQuerySupportCache.get(commandLine);
2015         }
2016 
2017         final boolean supported =
2018             queryIsSupportedConfigCommandLine(commandLine);
2019         mConfigQuerySupportCache.put(commandLine, supported);
2020         return supported;
2021     }
2022 
queryIsSupportedConfigCommandLine(String deqpCommandLine)2023     private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
2024         throws DeviceNotAvailableException, CapabilityQueryFailureException {
2025         final String instrumentationName =
2026             "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
2027         final String command = String.format(
2028             "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
2029                 + " %s",
2030             AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine,
2031             instrumentationName);
2032 
2033         final PlatformQueryInstrumentationParser parser =
2034             new PlatformQueryInstrumentationParser();
2035         mDevice.executeShellCommand(command, parser);
2036         parser.flush();
2037 
2038         if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
2039             parser.getResultMap().containsKey("Supported")) {
2040             if ("Yes".equals(parser.getResultMap().get("Supported"))) {
2041                 return true;
2042             } else if ("No".equals(parser.getResultMap().get("Supported"))) {
2043                 return false;
2044             } else {
2045                 CLog.e("Capability query did not return a result");
2046                 throw new CapabilityQueryFailureException();
2047             }
2048         } else if (parser.wasSuccessful()) {
2049             CLog.e("Failed to run capability query. Code: %d, Result: %s",
2050                    parser.getResultCode(), parser.getResultMap().toString());
2051             throw new CapabilityQueryFailureException();
2052         } else {
2053             CLog.e("Failed to run capability query");
2054             throw new CapabilityQueryFailureException();
2055         }
2056     }
2057 
2058     /**
2059      * Return feature set supported by the device, mapping integer-valued
2060      * features to their values
2061      */
getDeviceFeatures(ITestDevice device)2062     private Map<String, Optional<Integer>> getDeviceFeatures(ITestDevice device)
2063         throws DeviceNotAvailableException, CapabilityQueryFailureException {
2064         if (mDeviceFeatures == null) {
2065             mDeviceFeatures = queryDeviceFeatures(device);
2066         }
2067         return mDeviceFeatures;
2068     }
2069 
2070     /**
2071      * Query feature set supported by the device
2072      */
2073     private static Map<String, Optional<Integer>>
queryDeviceFeatures(ITestDevice device)2074     queryDeviceFeatures(ITestDevice device)
2075         throws DeviceNotAvailableException, CapabilityQueryFailureException {
2076         // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
2077         // TODO: Move this logic to ITestDevice.
2078         String command = "pm list features";
2079         String commandOutput = device.executeShellCommand(command);
2080 
2081         // Extract the id of the new user.
2082         Map<String, Optional<Integer>> availableFeatures = new HashMap<>();
2083         for (String feature : commandOutput.split("\\s+")) {
2084             // Each line in the output of the command has the format
2085             // "feature:{FEATURE_NAME}", optionally followed by
2086             // "={FEATURE_VERSION}".
2087             String[] tokens = feature.split(":|=");
2088             if (tokens.length < 2 || !"feature".equals(tokens[0])) {
2089                 CLog.e("Failed parse features. Unexpect format on line \"%s\"",
2090                        feature);
2091                 throw new CapabilityQueryFailureException();
2092             }
2093             final String featureName = tokens[1];
2094             Optional<Integer> featureValue = Optional.empty();
2095             if (tokens.length > 2) {
2096                 try {
2097                     // Integer.decode, rather than Integer.parseInt, is used
2098                     // here since some feature versions may be presented in
2099                     // decimal and others in hexadecimal.
2100                     featureValue = Optional.of(Integer.decode(tokens[2]));
2101                 } catch (NumberFormatException numberFormatException) {
2102                     CLog.e(
2103                         "Failed parse features. Feature value \"%s\" was not an integer on "
2104                             + "line \"%s\"",
2105                         tokens[2], feature);
2106                     throw new CapabilityQueryFailureException();
2107                 }
2108             }
2109             availableFeatures.put(featureName, featureValue);
2110         }
2111         return availableFeatures;
2112     }
2113 
isPortraitClassRotation(String rotation)2114     private boolean isPortraitClassRotation(String rotation) {
2115         return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
2116             BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
2117     }
2118 
isLandscapeClassRotation(String rotation)2119     private boolean isLandscapeClassRotation(String rotation) {
2120         return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
2121             BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
2122     }
2123 
checkRecognizedPackage()2124     private void checkRecognizedPackage() {
2125         if (!isRecognizedPackage()) {
2126             throw new IllegalStateException(
2127                 "dEQP runner was created with illegal package name");
2128         }
2129     }
2130 
isRecognizedPackage()2131     private boolean isRecognizedPackage() {
2132         return "dEQP-EGL".equals(mDeqpPackage) ||
2133             "dEQP-GLES2".equals(mDeqpPackage) ||
2134             "dEQP-GLES3".equals(mDeqpPackage) ||
2135             "dEQP-GLES31".equals(mDeqpPackage) ||
2136             "dEQP-VK".equals(mDeqpPackage);
2137     }
2138 
2139     /**
2140      * Parse EGL nature from package name
2141      */
isEglPackage()2142     private boolean isEglPackage() {
2143         checkRecognizedPackage();
2144         return "dEQP-EGL".equals(mDeqpPackage);
2145     }
2146 
2147     /**
2148      * Parse gl nature from package name
2149      */
isOpenGlEsPackage()2150     private boolean isOpenGlEsPackage() {
2151         checkRecognizedPackage();
2152         return "dEQP-GLES2".equals(mDeqpPackage) ||
2153             "dEQP-GLES3".equals(mDeqpPackage) ||
2154             "dEQP-GLES31".equals(mDeqpPackage);
2155     }
2156 
2157     /**
2158      * Parse vulkan nature from package name
2159      */
isVulkanPackage()2160     private boolean isVulkanPackage() {
2161         checkRecognizedPackage();
2162         return "dEQP-VK".equals(mDeqpPackage);
2163     }
2164 
2165     /**
2166      * Check GL support (based on package name)
2167      */
isSupportedGles()2168     private boolean isSupportedGles() throws DeviceNotAvailableException {
2169         return isSupportedGles(mDevice, getGlesMajorVersion(),
2170                                getGlesMinorVersion());
2171     }
2172 
2173     /**
2174      * Get GL major version (based on package name)
2175      */
getGlesMajorVersion()2176     private int getGlesMajorVersion() {
2177         if ("dEQP-GLES2".equals(mDeqpPackage)) {
2178             return 2;
2179         } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
2180             return 3;
2181         } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
2182             return 3;
2183         } else {
2184             throw new IllegalStateException(
2185                 "getGlesMajorVersion called for non gles pkg");
2186         }
2187     }
2188 
2189     /**
2190      * Get GL minor version (based on package name)
2191      */
getGlesMinorVersion()2192     private int getGlesMinorVersion() {
2193         if ("dEQP-GLES2".equals(mDeqpPackage)) {
2194             return 0;
2195         } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
2196             return 0;
2197         } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
2198             return 1;
2199         } else {
2200             throw new IllegalStateException(
2201                 "getGlesMinorVersion called for non gles pkg");
2202         }
2203     }
2204 
getPatternFilters(List<String> filters)2205     protected static List<Pattern> getPatternFilters(List<String> filters) {
2206         List<Pattern> patterns = new ArrayList<Pattern>();
2207         for (String filter : filters) {
2208             if (filter.contains("*")) {
2209                 patterns.add(Pattern.compile(
2210                     filter.replace(".", "\\.").replace("*", ".*")));
2211             }
2212         }
2213         return patterns;
2214     }
2215 
getNonPatternFilters(List<String> filters)2216     protected static Set<String> getNonPatternFilters(List<String> filters) {
2217         Set<String> nonPatternFilters = new HashSet<String>();
2218         for (String filter : filters) {
2219             if (filter.startsWith("#") || filter.isEmpty()) {
2220                 // Skip comments and empty lines
2221                 continue;
2222             }
2223             if (!filter.contains("*")) {
2224                 // Deqp usesly only dots for separating between parts of the
2225                 // names Convert last dot to hash if needed.
2226                 if (!filter.contains("#")) {
2227                     int lastSeparator = filter.lastIndexOf('.');
2228                     String filterWithHash =
2229                         filter.substring(0, lastSeparator) + "#" +
2230                         filter.substring(lastSeparator + 1, filter.length());
2231                     nonPatternFilters.add(filterWithHash);
2232                 } else {
2233                     nonPatternFilters.add(filter);
2234                 }
2235             }
2236         }
2237         return nonPatternFilters;
2238     }
2239 
matchesAny(TestDescription test, List<Pattern> patterns)2240     protected static boolean matchesAny(TestDescription test,
2241                                       List<Pattern> patterns) {
2242         for (Pattern pattern : patterns) {
2243             if (pattern.matcher(test.toString()).matches()) {
2244                 return true;
2245             }
2246         }
2247         return false;
2248     }
2249 
2250     /**
2251      * Filter tests with the option of filtering by pattern.
2252      *
2253      * '*' is 0 or more characters.
2254      * '.' is interpreted verbatim.
2255      */
2256     private static void
filterTests(Map<TestDescription, Set<BatchRunConfiguration>> tests, List<String> includeFilters, List<String> excludeFilters)2257     filterTests(Map<TestDescription, Set<BatchRunConfiguration>> tests,
2258                 List<String> includeFilters, List<String> excludeFilters) {
2259         // We could filter faster by building the test case tree.
2260         // Let's see if this is fast enough.
2261         Set<String> includeStrings = getNonPatternFilters(includeFilters);
2262         Set<String> excludeStrings = getNonPatternFilters(excludeFilters);
2263         List<Pattern> includePatterns = getPatternFilters(includeFilters);
2264         List<Pattern> excludePatterns = getPatternFilters(excludeFilters);
2265 
2266         List<TestDescription> testList = new ArrayList<>(tests.keySet());
2267         for (TestDescription test : testList) {
2268             if (excludeStrings.contains(test.toString())) {
2269                 tests.remove(test); // remove test if explicitly excluded
2270                 continue;
2271             }
2272             boolean includesExist =
2273                 !includeStrings.isEmpty() || !includePatterns.isEmpty();
2274             boolean testIsIncluded = includeStrings.contains(test.toString()) ||
2275                                      matchesAny(test, includePatterns);
2276             if ((includesExist && !testIsIncluded) ||
2277                 matchesAny(test, excludePatterns)) {
2278                 // if this test isn't included and other tests are,
2279                 // or if test matches exclude pattern, exclude test
2280                 tests.remove(test);
2281             }
2282         }
2283     }
2284 
2285     /**
2286      * Read each line from a file.
2287      */
readFile(Collection<String> lines, File file)2288     static private void readFile(Collection<String> lines, File file)
2289         throws FileNotFoundException {
2290         if (!file.canRead()) {
2291             CLog.e("Failed to read file '%s'", file.getPath());
2292             throw new FileNotFoundException();
2293         }
2294         try (Reader plainReader = new FileReader(file);
2295              BufferedReader reader = new BufferedReader(plainReader)) {
2296             String line = "";
2297             while ((line = reader.readLine()) != null) {
2298                 // TOOD: Quick check filter
2299                 lines.add(line);
2300             }
2301             // Rely on try block to autoclose
2302         } catch (IOException e) {
2303             throw new RuntimeException("Failed to read file '" +
2304                                        file.getPath() + "': " + e.getMessage());
2305         }
2306     }
2307 
2308     /**
2309      * Prints filters into debug log stream, limiting to 20 entries.
2310      */
printFilters(List<String> filters)2311     static private void printFilters(List<String> filters) {
2312         int numPrinted = 0;
2313         for (String filter : filters) {
2314             CLog.d("    %s", filter);
2315             if (++numPrinted == 20) {
2316                 CLog.d("    ... AND %d others", filters.size() - numPrinted);
2317                 break;
2318             }
2319         }
2320     }
2321 
2322     /**
2323      * Loads tests into mTestInstances based on the options. Assumes
2324      * that no tests have been loaded for this instance before.
2325      */
loadTests()2326     private void loadTests() {
2327         if (mTestInstances != null)
2328             throw new AssertionError("Re-load of tests not supported");
2329 
2330         // Note: This is specifically a LinkedHashMap to guarantee that tests
2331         // are iterated in the insertion order.
2332         mTestInstances = new LinkedHashMap<>();
2333 
2334         if (shouldBypassTestExecutionAndReporting()) {
2335             return;
2336         }
2337 
2338         try {
2339             File testlist = new File(mBuildHelper.getTestsDir(), mCaselistFile);
2340             if (!testlist.isFile()) {
2341                 // Finding file in sub directory if no matching file in the
2342                 // first layer of testdir.
2343                 testlist = FileUtil.findFile(mBuildHelper.getTestsDir(),
2344                                              mCaselistFile);
2345                 if (testlist == null || !testlist.isFile()) {
2346                     throw new FileNotFoundException(
2347                         "Cannot find deqp test list file: " + mCaselistFile);
2348                 }
2349             }
2350             addTestsToInstancesMap(testlist, mConfigName, mScreenRotation,
2351                                    mSurfaceType, mConfigRequired,
2352                                    mTestInstances);
2353         } catch (FileNotFoundException e) {
2354             throw new RuntimeException("Cannot read deqp test list file: " +
2355                                        mCaselistFile);
2356         }
2357 
2358         try {
2359             for (String filterFile : mIncludeFilterFiles) {
2360                 CLog.d("Read include filter file '%s'", filterFile);
2361                 File file = new File(mBuildHelper.getTestsDir(), filterFile);
2362                 if (!file.isFile()) {
2363                     // Find file in sub directory if no matching file in the
2364                     // first layer of testdir.
2365                     file = FileUtil.findFile(mBuildHelper.getTestsDir(),
2366                                              filterFile);
2367                     if (file == null || !file.isFile()) {
2368                         throw new FileNotFoundException(
2369                             "Cannot find include-filter-file file: " +
2370                             filterFile);
2371                     }
2372                 }
2373                 readFile(mIncludeFilters, file);
2374             }
2375             for (String filterFile : mExcludeFilterFiles) {
2376                 CLog.d("Read exclude filter file '%s'", filterFile);
2377                 File file = new File(mBuildHelper.getTestsDir(), filterFile);
2378                 if (!file.isFile()) {
2379                     // Find file in sub directory if no matching file in the
2380                     // first layer of testdir.
2381                     file = FileUtil.findFile(mBuildHelper.getTestsDir(),
2382                                              filterFile);
2383                     if (file == null || !file.isFile()) {
2384                         throw new FileNotFoundException(
2385                             "Cannot find exclude-filter-file file: " +
2386                             filterFile);
2387                     }
2388                 }
2389                 readFile(mExcludeFilters, file);
2390             }
2391         } catch (FileNotFoundException e) {
2392             throw new HarnessRuntimeException(
2393                 "Cannot read deqp filter list file." + e,
2394                 TestErrorIdentifier.TEST_ABORTED);
2395         }
2396 
2397         CLog.d("Include filters:");
2398         printFilters(mIncludeFilters);
2399         CLog.d("Exclude filters:");
2400         printFilters(mExcludeFilters);
2401 
2402         long originalTestCount = mTestInstances.size();
2403         CLog.i("Num tests before filtering: %d", originalTestCount);
2404         if ((!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) &&
2405             originalTestCount > 0) {
2406             filterTests(mTestInstances, mIncludeFilters, mExcludeFilters);
2407 
2408             // Update runtime estimation hint.
2409             if (mRuntimeHint != -1) {
2410                 mRuntimeHint =
2411                     (mRuntimeHint * mTestInstances.size()) / originalTestCount;
2412             }
2413         }
2414         CLog.i("Num tests after filtering: %d", mTestInstances.size());
2415     }
2416 
2417     /**
2418      * Set up the test environment.
2419      */
setupTestEnvironment(String testPackageName)2420     protected void setupTestEnvironment(String testPackageName) throws DeviceNotAvailableException {
2421         try {
2422             // Get the system into a known state.
2423             // Clear ANGLE Global.Settings values
2424             mDevice.executeShellCommand(
2425                 "settings delete global angle_gl_driver_selection_pkgs");
2426             mDevice.executeShellCommand(
2427                 "settings delete global angle_gl_driver_selection_values");
2428 
2429             // ANGLE
2430             if (mAngle.equals(ANGLE_VULKAN)) {
2431                 CLog.i("Configuring ANGLE to use: " + mAngle);
2432                 // Force dEQP to use ANGLE
2433                 mDevice.executeShellCommand(
2434                     "settings put global angle_gl_driver_selection_pkgs " +
2435                     testPackageName);
2436                 mDevice.executeShellCommand(
2437                     "settings put global angle_gl_driver_selection_values angle");
2438                 // Configure ANGLE to use Vulkan
2439                 mDevice.executeShellCommand("setprop debug.angle.backend 2");
2440             } else if (mAngle.equals(ANGLE_OPENGLES)) {
2441                 CLog.i("Configuring ANGLE to use: " + mAngle);
2442                 // Force dEQP to use ANGLE
2443                 mDevice.executeShellCommand(
2444                     "settings put global angle_gl_driver_selection_pkgs " +
2445                     testPackageName);
2446                 mDevice.executeShellCommand(
2447                     "settings put global angle_gl_driver_selection_values angle");
2448                 // Configure ANGLE to use Vulkan
2449                 mDevice.executeShellCommand("setprop debug.angle.backend 0");
2450             }
2451         } catch (DeviceNotAvailableException ex) {
2452             // chain forward
2453             CLog.e("Failed to set up ANGLE correctly.");
2454             throw new DeviceNotAvailableException("Device not available", ex,
2455                                                   mDevice.getSerialNumber());
2456         }
2457     }
2458 
2459     /**
2460      * Clean up the test environment.
2461      */
teardownTestEnvironment()2462     protected void teardownTestEnvironment() throws DeviceNotAvailableException {
2463         // ANGLE
2464         try {
2465             CLog.i("Cleaning up ANGLE");
2466             // Stop forcing dEQP to use ANGLE
2467             mDevice.executeShellCommand(
2468                 "settings delete global angle_gl_driver_selection_pkgs");
2469             mDevice.executeShellCommand(
2470                 "settings delete global angle_gl_driver_selection_values");
2471         } catch (DeviceNotAvailableException ex) {
2472             // chain forward
2473             CLog.e("Failed to clean up ANGLE correctly.");
2474             throw new DeviceNotAvailableException("Device not available", ex,
2475                                                   mDevice.getSerialNumber());
2476         }
2477     }
2478 
2479     /**
2480      * {@inheritDoc}
2481      */
2482     @Override
run(ITestInvocationListener listener)2483     public void run(ITestInvocationListener listener)
2484         throws DeviceNotAvailableException {
2485         final HashMap<String, Metric> emptyMap = new HashMap<>();
2486         // If sharded, split() will load the tests.
2487         if (mTestInstances == null) {
2488             loadTests();
2489         }
2490 
2491         mRemainingTests = new HashSet<>();
2492         if (!shouldBypassTestExecutionAndReporting()) {
2493             mRemainingTests.addAll(mTestInstances.keySet());
2494         }
2495         long startTime = System.currentTimeMillis();
2496         listener.testRunStarted(getId(), mRemainingTests.size());
2497 
2498         try {
2499             if (mRemainingTests.isEmpty()) {
2500                 CLog.d("No tests to run.");
2501                 return;
2502             }
2503             final boolean isSupportedApi =
2504                 (isOpenGlEsPackage() && isSupportedGles()) ||
2505                 (isVulkanPackage() && isSupportedVulkan()) ||
2506                 (!isOpenGlEsPackage() && !isVulkanPackage());
2507             final boolean deqpLevelIsRecent = claimedDeqpLevelIsRecentEnough();
2508 
2509             if (mCollectTestsOnly) {
2510                 // Pass all tests trivially if:
2511                 // - we are only collecting the names of the tests
2512                 fakePassTests(listener);
2513             } else if (!isSupportedApi) {
2514                 // Skip tests with "Assumption Failure" when
2515                 // - the relevant API/feature is not supported or
2516                 markTestsAsAssumptionFailure(listener);
2517             } else if (!deqpLevelIsRecent) {
2518                 // Skip tests with "Ignore" when
2519                 // - the device's deqp level do not claim to pass the tests
2520                 ignoreTests(listener);
2521             } else if (!mRemainingTests.isEmpty()) {
2522                 getInstanceListener().setSink(listener);
2523                 mDeviceRecovery.setDevice(mDevice);
2524                 setupTestEnvironment(DEQP_ONDEVICE_PKG);
2525                 runTests();
2526                 teardownTestEnvironment();
2527             }
2528         } catch (CapabilityQueryFailureException ex) {
2529             // Platform is not behaving correctly, for example crashing when
2530             // trying to create a window. Instead of silently failing, signal
2531             // failure by leaving the rest of the test cases in "NotExecuted"
2532             // state
2533             CLog.e("Capability query failed - leaving tests unexecuted.");
2534         } finally {
2535             listener.testRunEnded(System.currentTimeMillis() - startTime,
2536                                   emptyMap);
2537         }
2538     }
2539 
2540     /**
2541      * {@inheritDoc}
2542      */
2543     @Override
addIncludeFilter(String filter)2544     public void addIncludeFilter(String filter) {
2545         mIncludeFilters.add(filter);
2546     }
2547 
2548     /**
2549      * {@inheritDoc}
2550      */
2551     @Override
addAllIncludeFilters(Set<String> filters)2552     public void addAllIncludeFilters(Set<String> filters) {
2553         mIncludeFilters.addAll(filters);
2554     }
2555 
2556     /**
2557      * {@inheritDoc}
2558      */
2559     @Override
getIncludeFilters()2560     public Set<String> getIncludeFilters() {
2561         return new HashSet<>(mIncludeFilters);
2562     }
2563 
2564     /**
2565      * {@inheritDoc}
2566      */
2567     @Override
clearIncludeFilters()2568     public void clearIncludeFilters() {
2569         mIncludeFilters.clear();
2570     }
2571 
2572     /**
2573      * {@inheritDoc}
2574      */
2575     @Override
addExcludeFilter(String filter)2576     public void addExcludeFilter(String filter) {
2577         mExcludeFilters.add(filter);
2578     }
2579 
2580     /**
2581      * {@inheritDoc}
2582      */
2583     @Override
addAllExcludeFilters(Set<String> filters)2584     public void addAllExcludeFilters(Set<String> filters) {
2585         mExcludeFilters.addAll(filters);
2586     }
2587 
2588     /**
2589      * {@inheritDoc}
2590      */
2591     @Override
getExcludeFilters()2592     public Set<String> getExcludeFilters() {
2593         return new HashSet<>(mExcludeFilters);
2594     }
2595 
2596     /**
2597      * {@inheritDoc}
2598      */
2599     @Override
clearExcludeFilters()2600     public void clearExcludeFilters() {
2601         mExcludeFilters.clear();
2602     }
2603 
2604     /**
2605      * {@inheritDoc}
2606      */
2607     @Override
setCollectTestsOnly(boolean collectTests)2608     public void setCollectTestsOnly(boolean collectTests) {
2609         mCollectTestsOnly = collectTests;
2610     }
2611 
2612     /**
2613      * These methods are for testing.
2614      */
addIncrementalDeqpIncludeTests(Collection<String> tests)2615     public void addIncrementalDeqpIncludeTests(Collection<String> tests) {
2616         mIncrementalDeqpIncludeTests.addAll(tests);
2617     }
2618 
copyOptions(DeqpTestRunner destination, DeqpTestRunner source)2619     private static void copyOptions(DeqpTestRunner destination,
2620                                     DeqpTestRunner source) {
2621         destination.mUnresponsiveCmdTimeoutMs =
2622             source.mUnresponsiveCmdTimeoutMs;
2623         destination.mDeqpPackage = source.mDeqpPackage;
2624         destination.mConfigName = source.mConfigName;
2625         destination.mCaselistFile = source.mCaselistFile;
2626         destination.mScreenRotation = source.mScreenRotation;
2627         destination.mSurfaceType = source.mSurfaceType;
2628         destination.mConfigRequired = source.mConfigRequired;
2629         destination.mIncludeFilters = new ArrayList<>(source.mIncludeFilters);
2630         destination.mIncludeFilterFiles =
2631             new ArrayList<>(source.mIncludeFilterFiles);
2632         destination.mExcludeFilters = new ArrayList<>(source.mExcludeFilters);
2633         destination.mExcludeFilterFiles =
2634             new ArrayList<>(source.mExcludeFilterFiles);
2635         destination.mAbi = source.mAbi;
2636         destination.mLogData = source.mLogData;
2637         destination.mCollectTestsOnly = source.mCollectTestsOnly;
2638         destination.mAngle = source.mAngle;
2639         destination.mDisableWatchdog = source.mDisableWatchdog;
2640         destination.mIncrementalDeqpIncludeFiles =
2641             new ArrayList<>(source.mIncrementalDeqpIncludeFiles);
2642         destination.mForceDeqpLevel = source.mForceDeqpLevel;
2643     }
2644 
2645     /**
2646      * Helper to update the RuntimeHint of the tests after being sharded.
2647      */
updateRuntimeHint(long originalSize, Collection<IRemoteTest> runners)2648     private void updateRuntimeHint(long originalSize,
2649                                    Collection<IRemoteTest> runners) {
2650         if (originalSize > 0) {
2651             long fullRuntimeMs = getRuntimeHint();
2652             for (IRemoteTest remote : runners) {
2653                 DeqpTestRunner runner = (DeqpTestRunner)remote;
2654                 long shardRuntime =
2655                     (fullRuntimeMs * runner.mTestInstances.size()) /
2656                     originalSize;
2657                 runner.mRuntimeHint = shardRuntime;
2658             }
2659         }
2660     }
2661 
2662     /**
2663      * {@inheritDoc}
2664      */
2665     @Override
split()2666     public Collection<IRemoteTest> split() {
2667         if (mTestInstances != null) {
2668             throw new AssertionError(
2669                 "Re-splitting or splitting running instance?");
2670         }
2671         // \todo [2015-11-23 kalle] If we split to batches at shard level, we
2672         // could basically get rid of batching. Except that sharding is
2673         // optional?
2674 
2675         // Assume that tests have not been yet loaded.
2676         loadTests();
2677 
2678         Collection<IRemoteTest> runners = new ArrayList<>();
2679         // NOTE: Use linked hash map to keep the insertion order in iteration
2680         Map<TestDescription, Set<BatchRunConfiguration>> currentSet =
2681             new LinkedHashMap<>();
2682         Map<TestDescription, Set<BatchRunConfiguration>> iterationSet =
2683             this.mTestInstances;
2684 
2685         if (iterationSet.keySet().isEmpty()) {
2686             CLog.i("Cannot split deqp tests, no tests to run");
2687             return null;
2688         }
2689 
2690         // Go through tests, split
2691         for (TestDescription test : iterationSet.keySet()) {
2692             currentSet.put(test, iterationSet.get(test));
2693             if (currentSet.size() >= getBatchSizeLimit()) {
2694                 runners.add(new DeqpTestRunner(this, currentSet));
2695                 // NOTE: Use linked hash map to keep the insertion order in
2696                 // iteration
2697                 currentSet = new LinkedHashMap<>();
2698             }
2699         }
2700         runners.add(new DeqpTestRunner(this, currentSet));
2701 
2702         // Compute new runtime hints
2703         updateRuntimeHint(iterationSet.size(), runners);
2704         CLog.i("Split deqp tests into %d shards", runners.size());
2705         return runners;
2706     }
2707 
2708     /**
2709      * {@inheritDoc}
2710      */
2711     @Override
getRuntimeHint()2712     public long getRuntimeHint() {
2713         if (mRuntimeHint != -1) {
2714             return mRuntimeHint;
2715         }
2716         if (mTestInstances == null) {
2717             loadTests();
2718         }
2719         // Tests normally take something like ~100ms. Some take a
2720         // second. Let's guess 200ms per test.
2721         return 200 * mTestInstances.size();
2722     }
2723 }
2724