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