• 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         CLog.d("    2023-03-01 -> 132580097");
1781 
1782         CLog.d("Minimum level required to run this caselist is %d", minimumLevel);
1783 
1784         // Now look for the feature flag.
1785         final Map<String, Optional<Integer>> features = getDeviceFeatures(mDevice);
1786 
1787         for (String feature : features.keySet()) {
1788             if (feature.startsWith(featureName)) {
1789                 final Optional<Integer> claimedDeqpLevel = features.get(feature);
1790                 if (!claimedDeqpLevel.isPresent()) {
1791                     throw new IllegalStateException("Feature " + featureName
1792                         + " has no associated version");
1793                 }
1794                 CLog.d("Device level is %d", claimedDeqpLevel.get());
1795 
1796                 final boolean shouldRunCaselist = claimedDeqpLevel.get() >= minimumLevel;
1797                 CLog.d("Running caselist? %b", shouldRunCaselist);
1798                 return shouldRunCaselist;
1799             }
1800         }
1801 
1802         CLog.d("Could not find dEQP level feature flag \"%s\".",
1803                 featureName);
1804 
1805         // A Vulkan dEQP level has been required since R.
1806         // A GLES/EGL dEQP level has only been required since S.
1807         // Thus, if the VSR level is <= R and there is no GLES dEQP level, then we can assume
1808         // a GLES dEQP level of R (2020).
1809         if (PropertyUtil.getVsrApiLevel(mDevice) <= R_API_LEVEL
1810                 && FEATURE_OPENGLES_DEQP_LEVEL.equals(featureName)) {
1811             final int claimedDeqpLevel = DEQP_LEVEL_R_2020;
1812             CLog.d("Device level is %d due to VSR R", claimedDeqpLevel);
1813             final boolean shouldRunCaselist = claimedDeqpLevel >= minimumLevel;
1814             CLog.d("Running caselist? %b", shouldRunCaselist);
1815             return shouldRunCaselist;
1816         }
1817 
1818         CLog.d("Running caselist unconditionally");
1819 
1820         return true;
1821     }
1822 
1823     /**
1824      * Check if device supports OpenGL ES version.
1825      */
isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)1826     private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion,
1827             int requiredMinorVersion) throws DeviceNotAvailableException {
1828         String roOpenglesVersion = device.getProperty("ro.opengles.version");
1829 
1830         if (roOpenglesVersion == null)
1831             return false;
1832 
1833         int intValue = Integer.parseInt(roOpenglesVersion);
1834 
1835         int majorVersion = ((intValue & 0xffff0000) >> 16);
1836         int minorVersion = (intValue & 0xffff);
1837 
1838         return (majorVersion > requiredMajorVersion)
1839                 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion);
1840     }
1841 
1842     /**
1843      * Query if rendertarget is supported
1844      */
isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)1845     private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
1846             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1847         // query if configuration is supported
1848         final StringBuilder configCommandLine =
1849                 new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
1850         if (configCommandLine.length() != 0) {
1851             configCommandLine.append(" ");
1852         }
1853         configCommandLine.append("--deqp-gl-major-version=");
1854         configCommandLine.append(getGlesMajorVersion());
1855         configCommandLine.append(" --deqp-gl-minor-version=");
1856         configCommandLine.append(getGlesMinorVersion());
1857 
1858         final String commandLine = configCommandLine.toString();
1859 
1860         // check for cached result first
1861         if (mConfigQuerySupportCache.containsKey(commandLine)) {
1862             return mConfigQuerySupportCache.get(commandLine);
1863         }
1864 
1865         final boolean supported = queryIsSupportedConfigCommandLine(commandLine);
1866         mConfigQuerySupportCache.put(commandLine, supported);
1867         return supported;
1868     }
1869 
queryIsSupportedConfigCommandLine(String deqpCommandLine)1870     private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
1871             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1872         final String instrumentationName =
1873                 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
1874         final String command = String.format(
1875                 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
1876                     + " %s",
1877                 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName);
1878 
1879         final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser();
1880         mDevice.executeShellCommand(command, parser);
1881         parser.flush();
1882 
1883         if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
1884                 parser.getResultMap().containsKey("Supported")) {
1885             if ("Yes".equals(parser.getResultMap().get("Supported"))) {
1886                 return true;
1887             } else if ("No".equals(parser.getResultMap().get("Supported"))) {
1888                 return false;
1889             } else {
1890                 CLog.e("Capability query did not return a result");
1891                 throw new CapabilityQueryFailureException();
1892             }
1893         } else if (parser.wasSuccessful()) {
1894             CLog.e("Failed to run capability query. Code: %d, Result: %s",
1895                     parser.getResultCode(), parser.getResultMap().toString());
1896             throw new CapabilityQueryFailureException();
1897         } else {
1898             CLog.e("Failed to run capability query");
1899             throw new CapabilityQueryFailureException();
1900         }
1901     }
1902 
1903     /**
1904      * Return feature set supported by the device, mapping integer-valued features to their values
1905      */
getDeviceFeatures(ITestDevice device)1906     private Map<String, Optional<Integer>> getDeviceFeatures(ITestDevice device)
1907             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1908         if (mDeviceFeatures == null) {
1909             mDeviceFeatures = queryDeviceFeatures(device);
1910         }
1911         return mDeviceFeatures;
1912     }
1913 
1914     /**
1915      * Query feature set supported by the device
1916      */
queryDeviceFeatures(ITestDevice device)1917     private static Map<String, Optional<Integer>> queryDeviceFeatures(ITestDevice device)
1918             throws DeviceNotAvailableException, CapabilityQueryFailureException {
1919         // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
1920         // TODO: Move this logic to ITestDevice.
1921         String command = "pm list features";
1922         String commandOutput = device.executeShellCommand(command);
1923 
1924         // Extract the id of the new user.
1925         Map<String, Optional<Integer>> availableFeatures = new HashMap<>();
1926         for (String feature: commandOutput.split("\\s+")) {
1927             // Each line in the output of the command has the format "feature:{FEATURE_NAME}",
1928             // optionally followed by "={FEATURE_VERSION}".
1929             String[] tokens = feature.split(":|=");
1930             if (tokens.length < 2 || !"feature".equals(tokens[0])) {
1931                 CLog.e("Failed parse features. Unexpect format on line \"%s\"", feature);
1932                 throw new CapabilityQueryFailureException();
1933             }
1934             final String featureName = tokens[1];
1935             Optional<Integer> featureValue = Optional.empty();
1936             if (tokens.length > 2) {
1937                 try {
1938                     // Integer.decode, rather than Integer.parseInt, is used here since some
1939                     // feature versions may be presented in decimal and others in hexadecimal.
1940                     featureValue = Optional.of(Integer.decode(tokens[2]));
1941                 } catch (NumberFormatException numberFormatException) {
1942                     CLog.e("Failed parse features. Feature value \"%s\" was not an integer on "
1943                         + "line \"%s\"", tokens[2], feature);
1944                     throw new CapabilityQueryFailureException();
1945                 }
1946 
1947             }
1948             availableFeatures.put(featureName, featureValue);
1949         }
1950         return availableFeatures;
1951     }
1952 
isPortraitClassRotation(String rotation)1953     private boolean isPortraitClassRotation(String rotation) {
1954         return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
1955                 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
1956     }
1957 
isLandscapeClassRotation(String rotation)1958     private boolean isLandscapeClassRotation(String rotation) {
1959         return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
1960                 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
1961     }
1962 
checkRecognizedPackage()1963     private void checkRecognizedPackage() {
1964         if (!isRecognizedPackage()) {
1965             throw new IllegalStateException("dEQP runner was created with illegal package name");
1966         }
1967     }
1968 
isRecognizedPackage()1969     private boolean isRecognizedPackage() {
1970         return "dEQP-EGL".equals(mDeqpPackage) || "dEQP-GLES2".equals(mDeqpPackage)
1971                 || "dEQP-GLES3".equals(mDeqpPackage) || "dEQP-GLES31".equals(mDeqpPackage)
1972                 || "dEQP-VK".equals(mDeqpPackage);
1973     }
1974 
1975     /**
1976      * Parse EGL nature from package name
1977      */
isEglPackage()1978     private boolean isEglPackage() {
1979         checkRecognizedPackage();
1980         return "dEQP-EGL".equals(mDeqpPackage);
1981     }
1982 
1983     /**
1984      * Parse gl nature from package name
1985      */
isOpenGlEsPackage()1986     private boolean isOpenGlEsPackage() {
1987         checkRecognizedPackage();
1988         return "dEQP-GLES2".equals(mDeqpPackage) || "dEQP-GLES3".equals(mDeqpPackage)
1989                 || "dEQP-GLES31".equals(mDeqpPackage);
1990     }
1991 
1992     /**
1993      * Parse vulkan nature from package name
1994      */
isVulkanPackage()1995     private boolean isVulkanPackage() {
1996         checkRecognizedPackage();
1997         return "dEQP-VK".equals(mDeqpPackage);
1998     }
1999 
2000     /**
2001      * Check GL support (based on package name)
2002      */
isSupportedGles()2003     private boolean isSupportedGles() throws DeviceNotAvailableException {
2004         return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion());
2005     }
2006 
2007     /**
2008      * Get GL major version (based on package name)
2009      */
getGlesMajorVersion()2010     private int getGlesMajorVersion() {
2011         if ("dEQP-GLES2".equals(mDeqpPackage)) {
2012             return 2;
2013         } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
2014             return 3;
2015         } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
2016             return 3;
2017         } else {
2018             throw new IllegalStateException("getGlesMajorVersion called for non gles pkg");
2019         }
2020     }
2021 
2022     /**
2023      * Get GL minor version (based on package name)
2024      */
getGlesMinorVersion()2025     private int getGlesMinorVersion() {
2026         if ("dEQP-GLES2".equals(mDeqpPackage)) {
2027             return 0;
2028         } else if ("dEQP-GLES3".equals(mDeqpPackage)) {
2029             return 0;
2030         } else if ("dEQP-GLES31".equals(mDeqpPackage)) {
2031             return 1;
2032         } else {
2033             throw new IllegalStateException("getGlesMinorVersion called for non gles pkg");
2034         }
2035     }
2036 
getPatternFilters(List<String> filters)2037     private static List<Pattern> getPatternFilters(List<String> filters) {
2038         List<Pattern> patterns = new ArrayList<Pattern>();
2039         for (String filter : filters) {
2040             if (filter.contains("*")) {
2041                 patterns.add(Pattern.compile(filter.replace(".","\\.").replace("*",".*")));
2042             }
2043         }
2044         return patterns;
2045     }
2046 
getNonPatternFilters(List<String> filters)2047     private static Set<String> getNonPatternFilters(List<String> filters) {
2048         Set<String> nonPatternFilters = new HashSet<String>();
2049         for (String filter : filters) {
2050             if (filter.startsWith("#") || filter.isEmpty()) {
2051                 // Skip comments and empty lines
2052                 continue;
2053             }
2054             if (!filter.contains("*")) {
2055                 // Deqp usesly only dots for separating between parts of the names
2056                 // Convert last dot to hash if needed.
2057                 if (!filter.contains("#")) {
2058                     int lastSeparator = filter.lastIndexOf('.');
2059                     String filterWithHash = filter.substring(0, lastSeparator) + "#" +
2060                         filter.substring(lastSeparator + 1, filter.length());
2061                     nonPatternFilters.add(filterWithHash);
2062                 }
2063                 else {
2064                     nonPatternFilters.add(filter);
2065                 }
2066             }
2067         }
2068         return nonPatternFilters;
2069     }
2070 
matchesAny(TestDescription test, List<Pattern> patterns)2071     private static boolean matchesAny(TestDescription test, List<Pattern> patterns) {
2072         for (Pattern pattern : patterns) {
2073             if (pattern.matcher(test.toString()).matches()) {
2074                 return true;
2075             }
2076         }
2077         return false;
2078     }
2079 
2080     /**
2081      * Filter tests with the option of filtering by pattern.
2082      *
2083      * '*' is 0 or more characters.
2084      * '.' is interpreted verbatim.
2085      */
filterTests(Map<TestDescription, Set<BatchRunConfiguration>> tests, List<String> includeFilters, List<String> excludeFilters)2086     private static void filterTests(Map<TestDescription, Set<BatchRunConfiguration>> tests,
2087                                     List<String> includeFilters,
2088                                     List<String> excludeFilters) {
2089         // We could filter faster by building the test case tree.
2090         // Let's see if this is fast enough.
2091         Set<String> includeStrings = getNonPatternFilters(includeFilters);
2092         Set<String> excludeStrings = getNonPatternFilters(excludeFilters);
2093         List<Pattern> includePatterns = getPatternFilters(includeFilters);
2094         List<Pattern> excludePatterns = getPatternFilters(excludeFilters);
2095 
2096         List<TestDescription> testList = new ArrayList<>(tests.keySet());
2097         for (TestDescription test : testList) {
2098             if (excludeStrings.contains(test.toString())) {
2099                 tests.remove(test); // remove test if explicitly excluded
2100                 continue;
2101             }
2102             boolean includesExist = !includeStrings.isEmpty() || !includePatterns.isEmpty();
2103             boolean testIsIncluded = includeStrings.contains(test.toString())
2104                     || matchesAny(test, includePatterns);
2105             if ((includesExist && !testIsIncluded) || matchesAny(test, excludePatterns)) {
2106                 // if this test isn't included and other tests are,
2107                 // or if test matches exclude pattern, exclude test
2108                 tests.remove(test);
2109             }
2110         }
2111     }
2112 
2113     /**
2114      * Read each line from a file.
2115      */
readFile(Collection<String> lines, File file)2116     static private void readFile(Collection<String> lines, File file) throws FileNotFoundException {
2117         if (!file.canRead()) {
2118             CLog.e("Failed to read file '%s'", file.getPath());
2119             throw new FileNotFoundException();
2120         }
2121         try (Reader plainReader = new FileReader(file);
2122              BufferedReader reader = new BufferedReader(plainReader)) {
2123             String line = "";
2124             while ((line = reader.readLine()) != null) {
2125                 // TOOD: Sanity check filter
2126                 lines.add(line);
2127             }
2128             // Rely on try block to autoclose
2129         }
2130         catch (IOException e)
2131         {
2132             throw new RuntimeException("Failed to read file '" + file.getPath() + "': " +
2133                      e.getMessage());
2134         }
2135     }
2136 
2137     /**
2138      * Prints filters into debug log stream, limiting to 20 entries.
2139      */
printFilters(List<String> filters)2140     static private void printFilters(List<String> filters) {
2141         int numPrinted = 0;
2142         for (String filter : filters) {
2143             CLog.d("    %s", filter);
2144             if (++numPrinted == 20) {
2145                 CLog.d("    ... AND %d others", filters.size() - numPrinted);
2146                 break;
2147             }
2148         }
2149     }
2150 
2151     /**
2152      * Loads tests into mTestInstances based on the options. Assumes
2153      * that no tests have been loaded for this instance before.
2154      */
loadTests()2155     private void loadTests() {
2156         if (mTestInstances != null) throw new AssertionError("Re-load of tests not supported");
2157 
2158         // Note: This is specifically a LinkedHashMap to guarantee that tests are iterated
2159         // in the insertion order.
2160         mTestInstances = new LinkedHashMap<>();
2161 
2162         try {
2163             File testlist = new File(mBuildHelper.getTestsDir(), mCaselistFile);
2164             if (!testlist.isFile()) {
2165                 // Finding file in sub directory if no matching file in the first layer of
2166                 // testdir.
2167                 testlist = FileUtil.findFile(mBuildHelper.getTestsDir(), mCaselistFile);
2168                 if (testlist == null || !testlist.isFile()) {
2169                     throw new FileNotFoundException("Cannot find deqp test list file: "
2170                         + mCaselistFile);
2171                 }
2172             }
2173             addTestsToInstancesMap(
2174                 testlist,
2175                 mConfigName,
2176                 mScreenRotation,
2177                 mSurfaceType,
2178                 mConfigRequired,
2179                 mTestInstances);
2180         }
2181         catch (FileNotFoundException e) {
2182             throw new RuntimeException("Cannot read deqp test list file: "  + mCaselistFile);
2183         }
2184 
2185         try
2186         {
2187             if (isIncrementalDeqpRun()) {
2188                 for (String testFile : mIncrementalDeqpIncludeFiles) {
2189                     CLog.d("Read incremental dEQP include file '%s'", testFile);
2190                     File file = new File(mBuildHelper.getTestsDir(), testFile);
2191                     if (!file.isFile()) {
2192                         // Find file in sub directory if no matching file in the first layer of
2193                         // testdir.
2194                         file = FileUtil.findFile(mBuildHelper.getTestsDir(), testFile);
2195                         if (file == null || !file.isFile()) {
2196                             throw new FileNotFoundException(
2197                                 "Cannot find incremental dEQP include file: " + testFile);
2198                         }
2199                     }
2200                     readFile(mIncrementalDeqpIncludeTests, file);
2201                 }
2202             }
2203             for (String filterFile : mIncludeFilterFiles) {
2204                 CLog.d("Read include filter file '%s'", filterFile);
2205                 File file = new File(mBuildHelper.getTestsDir(), filterFile);
2206                 if (!file.isFile())
2207                 {
2208                     // Find file in sub directory if no matching file in the first layer of
2209                     // testdir.
2210                     file = FileUtil.findFile(mBuildHelper.getTestsDir(), filterFile);
2211                     if(file == null || !file.isFile()){
2212                         throw new FileNotFoundException(
2213                             "Cannot find include-filter-file file: " + filterFile);
2214                     }
2215                 }
2216                 readFile(mIncludeFilters, file);
2217             }
2218             for (String filterFile : mExcludeFilterFiles) {
2219                 CLog.d("Read exclude filter file '%s'", filterFile);
2220                 File file = new File(mBuildHelper.getTestsDir(), filterFile);
2221                 if(!file.isFile())
2222                 {
2223                     // Find file in sub directory if no matching file in the first layer of
2224                     // testdir.
2225                     file = FileUtil.findFile(mBuildHelper.getTestsDir(), filterFile);
2226                     if(file == null || !file.isFile()){
2227                         throw new FileNotFoundException(
2228                             "Cannot find exclude-filter-file file: " + filterFile);
2229                     }
2230                 }
2231                 readFile(mExcludeFilters, file);
2232             }
2233         }
2234         catch (FileNotFoundException e) {
2235             throw new HarnessRuntimeException("Cannot read deqp filter list file." + e,
2236                 TestErrorIdentifier.TEST_ABORTED);
2237         }
2238 
2239         CLog.d("Include filters:");
2240         printFilters(mIncludeFilters);
2241         CLog.d("Exclude filters:");
2242         printFilters(mExcludeFilters);
2243 
2244         long originalTestCount = mTestInstances.size();
2245         CLog.i("Num tests before filtering: %d", originalTestCount);
2246         if ((!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) && originalTestCount > 0) {
2247             filterTests(mTestInstances, mIncludeFilters, mExcludeFilters);
2248 
2249             // Update runtime estimation hint.
2250             if (mRuntimeHint != -1) {
2251                 mRuntimeHint = (mRuntimeHint * mTestInstances.size()) / originalTestCount;
2252             }
2253         }
2254         CLog.i("Num tests after filtering: %d", mTestInstances.size());
2255     }
2256 
2257     /**
2258      * Set up the test environment.
2259      */
setupTestEnvironment()2260     private void setupTestEnvironment() throws DeviceNotAvailableException {
2261         try {
2262             // Get the system into a known state.
2263             // Clear ANGLE Global.Settings values
2264             mDevice.executeShellCommand("settings delete global angle_gl_driver_selection_pkgs");
2265             mDevice.executeShellCommand("settings delete global angle_gl_driver_selection_values");
2266 
2267             // ANGLE
2268             if (mAngle.equals(ANGLE_VULKAN)) {
2269                 CLog.i("Configuring ANGLE to use: " + mAngle);
2270                 // Force dEQP to use ANGLE
2271                 mDevice.executeShellCommand(
2272                     "settings put global angle_gl_driver_selection_pkgs " + DEQP_ONDEVICE_PKG);
2273                 mDevice.executeShellCommand(
2274                     "settings put global angle_gl_driver_selection_values angle");
2275                 // Configure ANGLE to use Vulkan
2276                 mDevice.executeShellCommand("setprop debug.angle.backend 2");
2277             } else if (mAngle.equals(ANGLE_OPENGLES)) {
2278                 CLog.i("Configuring ANGLE to use: " + mAngle);
2279                 // Force dEQP to use ANGLE
2280                 mDevice.executeShellCommand(
2281                     "settings put global angle_gl_driver_selection_pkgs " + DEQP_ONDEVICE_PKG);
2282                 mDevice.executeShellCommand(
2283                     "settings put global angle_gl_driver_selection_values angle");
2284                 // Configure ANGLE to use Vulkan
2285                 mDevice.executeShellCommand("setprop debug.angle.backend 0");
2286             }
2287         } catch (DeviceNotAvailableException ex) {
2288             // chain forward
2289             CLog.e("Failed to set up ANGLE correctly.");
2290             throw new DeviceNotAvailableException("Device not available", ex,
2291                 mDevice.getSerialNumber());
2292         }
2293     }
2294 
2295     /**
2296      * Clean up the test environment.
2297      */
teardownTestEnvironment()2298     private void teardownTestEnvironment() throws DeviceNotAvailableException {
2299         // ANGLE
2300         try {
2301             CLog.i("Cleaning up ANGLE");
2302             // Stop forcing dEQP to use ANGLE
2303             mDevice.executeShellCommand("settings delete global angle_gl_driver_selection_pkgs");
2304             mDevice.executeShellCommand("settings delete global angle_gl_driver_selection_values");
2305         } catch (DeviceNotAvailableException ex) {
2306             // chain forward
2307             CLog.e("Failed to clean up ANGLE correctly.");
2308             throw new DeviceNotAvailableException("Device not available", ex,
2309                 mDevice.getSerialNumber());
2310         }
2311     }
2312 
2313     /**
2314      * {@inheritDoc}
2315      */
2316     @Override
run(ITestInvocationListener listener)2317     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
2318         final HashMap<String, Metric> emptyMap = new HashMap<>();
2319         // If sharded, split() will load the tests.
2320         if (mTestInstances == null) {
2321             loadTests();
2322         }
2323 
2324         mRemainingTests = new HashSet<>(mTestInstances.keySet());
2325         long startTime = System.currentTimeMillis();
2326         listener.testRunStarted(getId(), mRemainingTests.size());
2327 
2328         try {
2329             if (mRemainingTests.isEmpty()) {
2330                 CLog.d("No tests to run.");
2331                 return;
2332             }
2333             final boolean isSupportedApi = (isOpenGlEsPackage() && isSupportedGles())
2334                                             || (isVulkanPackage() && isSupportedVulkan())
2335                                             || (!isOpenGlEsPackage() && !isVulkanPackage());
2336             if (mCollectTestsOnly
2337                 || !isSupportedApi
2338                 || !claimedDeqpLevelIsRecentEnough()) {
2339                 // Pass all tests trivially if:
2340                 // - we are collecting the names of the tests only, or
2341                 // - the relevant API is not supported, or
2342                 // - the device's feature flags do not claim to pass the tests
2343                 fakePassTests(listener);
2344             } else if (!mRemainingTests.isEmpty()) {
2345                 mInstanceListerner.setSink(listener);
2346                 mDeviceRecovery.setDevice(mDevice);
2347                 setupTestEnvironment();
2348                 runTests();
2349                 teardownTestEnvironment();
2350             }
2351         } catch (CapabilityQueryFailureException ex) {
2352             // Platform is not behaving correctly, for example crashing when trying to create
2353             // a window. Instead of silently failing, signal failure by leaving the rest of the
2354             // test cases in "NotExecuted" state
2355             CLog.e("Capability query failed - leaving tests unexecuted.");
2356         } finally {
2357             listener.testRunEnded(System.currentTimeMillis() - startTime, emptyMap);
2358         }
2359     }
2360 
2361    /**
2362      * {@inheritDoc}
2363      */
2364     @Override
addIncludeFilter(String filter)2365     public void addIncludeFilter(String filter) {
2366         mIncludeFilters.add(filter);
2367     }
2368 
2369     /**
2370      * {@inheritDoc}
2371      */
2372     @Override
addAllIncludeFilters(Set<String> filters)2373     public void addAllIncludeFilters(Set<String> filters) {
2374         mIncludeFilters.addAll(filters);
2375     }
2376 
2377     /**
2378      * {@inheritDoc}
2379      */
2380     @Override
getIncludeFilters()2381     public Set<String> getIncludeFilters() {
2382         return new HashSet<>(mIncludeFilters);
2383     }
2384 
2385     /**
2386      * {@inheritDoc}
2387      */
2388     @Override
clearIncludeFilters()2389     public void clearIncludeFilters() {
2390         mIncludeFilters.clear();
2391     }
2392 
2393     /**
2394      * {@inheritDoc}
2395      */
2396     @Override
addExcludeFilter(String filter)2397     public void addExcludeFilter(String filter) {
2398         mExcludeFilters.add(filter);
2399     }
2400 
2401     /**
2402      * {@inheritDoc}
2403      */
2404     @Override
addAllExcludeFilters(Set<String> filters)2405     public void addAllExcludeFilters(Set<String> filters) {
2406         mExcludeFilters.addAll(filters);
2407     }
2408 
2409     /**
2410      * {@inheritDoc}
2411      */
2412     @Override
getExcludeFilters()2413     public Set<String> getExcludeFilters() {
2414         return new HashSet<>(mExcludeFilters);
2415     }
2416 
2417     /**
2418      * {@inheritDoc}
2419      */
2420     @Override
clearExcludeFilters()2421     public void clearExcludeFilters() {
2422         mExcludeFilters.clear();
2423     }
2424 
2425     /**
2426      * {@inheritDoc}
2427      */
2428     @Override
setCollectTestsOnly(boolean collectTests)2429     public void setCollectTestsOnly(boolean collectTests) {
2430         mCollectTestsOnly = collectTests;
2431     }
2432 
2433     /**
2434      * These methods are for testing.
2435      */
addIncrementalDeqpIncludeTest(String test)2436     public void addIncrementalDeqpIncludeTest(String test) {
2437         mIncrementalDeqpIncludeTests.add(test);
2438     }
2439 
2440     /**
2441      * These methods are for testing.
2442      */
addIncrementalDeqpIncludeTests(Collection<String> tests)2443     public void addIncrementalDeqpIncludeTests(Collection<String> tests) {
2444         mIncrementalDeqpIncludeTests.addAll(tests);
2445     }
2446 
copyOptions(DeqpTestRunner destination, DeqpTestRunner source)2447     private static void copyOptions(DeqpTestRunner destination, DeqpTestRunner source) {
2448         destination.mUnresponsiveCmdTimeoutMs = source.mUnresponsiveCmdTimeoutMs;
2449         destination.mDeqpPackage = source.mDeqpPackage;
2450         destination.mConfigName = source.mConfigName;
2451         destination.mCaselistFile = source.mCaselistFile;
2452         destination.mScreenRotation = source.mScreenRotation;
2453         destination.mSurfaceType = source.mSurfaceType;
2454         destination.mConfigRequired = source.mConfigRequired;
2455         destination.mIncludeFilters = new ArrayList<>(source.mIncludeFilters);
2456         destination.mIncludeFilterFiles = new ArrayList<>(source.mIncludeFilterFiles);
2457         destination.mExcludeFilters = new ArrayList<>(source.mExcludeFilters);
2458         destination.mExcludeFilterFiles = new ArrayList<>(source.mExcludeFilterFiles);
2459         destination.mAbi = source.mAbi;
2460         destination.mLogData = source.mLogData;
2461         destination.mCollectTestsOnly = source.mCollectTestsOnly;
2462         destination.mAngle = source.mAngle;
2463         destination.mDisableWatchdog = source.mDisableWatchdog;
2464         destination.mIncrementalDeqpIncludeFiles = new ArrayList<>(source.mIncrementalDeqpIncludeFiles);
2465 
2466     }
2467 
2468     /**
2469      * Helper to update the RuntimeHint of the tests after being sharded.
2470      */
updateRuntimeHint(long originalSize, Collection<IRemoteTest> runners)2471     private void updateRuntimeHint(long originalSize, Collection<IRemoteTest> runners) {
2472         if (originalSize > 0) {
2473             long fullRuntimeMs = getRuntimeHint();
2474             for (IRemoteTest remote: runners) {
2475                 DeqpTestRunner runner = (DeqpTestRunner)remote;
2476                 long shardRuntime = (fullRuntimeMs * runner.mTestInstances.size()) / originalSize;
2477                 runner.mRuntimeHint = shardRuntime;
2478             }
2479         }
2480     }
2481 
2482     /**
2483      * {@inheritDoc}
2484      */
2485     @Override
split()2486     public Collection<IRemoteTest> split() {
2487         if (mTestInstances != null) {
2488             throw new AssertionError("Re-splitting or splitting running instance?");
2489         }
2490         // \todo [2015-11-23 kalle] If we split to batches at shard level, we could
2491         // basically get rid of batching. Except that sharding is optional?
2492 
2493         // Assume that tests have not been yet loaded.
2494         loadTests();
2495 
2496         Collection<IRemoteTest> runners = new ArrayList<>();
2497         // NOTE: Use linked hash map to keep the insertion order in iteration
2498         Map<TestDescription, Set<BatchRunConfiguration>> currentSet = new LinkedHashMap<>();
2499         Map<TestDescription, Set<BatchRunConfiguration>> iterationSet = this.mTestInstances;
2500 
2501         if (iterationSet.keySet().isEmpty()) {
2502             CLog.i("Cannot split deqp tests, no tests to run");
2503             return null;
2504         }
2505 
2506         // Go through tests, split
2507         for (TestDescription test: iterationSet.keySet()) {
2508             currentSet.put(test, iterationSet.get(test));
2509             if (currentSet.size() >= getBatchSizeLimit()) {
2510                 runners.add(new DeqpTestRunner(this, currentSet));
2511                 // NOTE: Use linked hash map to keep the insertion order in iteration
2512                 currentSet = new LinkedHashMap<>();
2513             }
2514         }
2515         runners.add(new DeqpTestRunner(this, currentSet));
2516 
2517         // Compute new runtime hints
2518         updateRuntimeHint(iterationSet.size(), runners);
2519         CLog.i("Split deqp tests into %d shards", runners.size());
2520         return runners;
2521     }
2522 
2523     /**
2524      * {@inheritDoc}
2525      */
2526     @Override
getRuntimeHint()2527     public long getRuntimeHint() {
2528         if (mRuntimeHint != -1) {
2529             return mRuntimeHint;
2530         }
2531         if (mTestInstances == null) {
2532             loadTests();
2533         }
2534         // Tests normally take something like ~100ms. Some take a
2535         // second. Let's guess 200ms per test.
2536         return 200 * mTestInstances.size();
2537     }
2538 }
2539