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