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