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