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