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