blob: 43aaf980297f2836d7612c1a2280e9963a06de7d [file] [log] [blame]
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001package com.android.cts.tradefed.testtype;
2
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -07003import com.android.cts.tradefed.build.CtsBuildHelper;
Stuart Scotte00a2b82014-09-05 17:37:18 -07004import com.android.cts.util.AbiUtils;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07005import com.android.ddmlib.AdbCommandRejectedException;
6import com.android.ddmlib.IShellOutputReceiver;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03007import com.android.ddmlib.MultiLineReceiver;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07008import com.android.ddmlib.ShellCommandUnresponsiveException;
9import com.android.ddmlib.TimeoutException;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030010import com.android.ddmlib.testrunner.TestIdentifier;
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -070011import com.android.tradefed.build.IBuildInfo;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030012import com.android.tradefed.device.DeviceNotAvailableException;
13import com.android.tradefed.device.ITestDevice;
Mika Isojärvi69bd10f2014-09-10 13:55:05 +030014import com.android.tradefed.log.LogUtil.CLog;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030015import com.android.tradefed.result.ByteArrayInputStreamSource;
16import com.android.tradefed.result.ITestInvocationListener;
17import com.android.tradefed.result.LogDataType;
Stuart Scottfef5e2c2014-09-05 17:37:18 -070018import com.android.tradefed.testtype.IAbi;
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -070019import com.android.tradefed.testtype.IBuildReceiver;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030020import com.android.tradefed.testtype.IDeviceTest;
21import com.android.tradefed.testtype.IRemoteTest;
Jarkko Pöyry35d54ee2015-05-12 15:28:58 -070022import com.android.tradefed.util.IRunUtil;
23import com.android.tradefed.util.RunInterruptedException;
24import com.android.tradefed.util.RunUtil;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030025
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -070026import java.io.File;
27import java.io.FileNotFoundException;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -070028import java.io.IOException;
29import java.lang.reflect.Method;
30import java.lang.reflect.InvocationTargetException;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030031import java.util.ArrayList;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.HashMap;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080035import java.util.HashSet;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030036import java.util.Iterator;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080037import java.util.LinkedHashMap;
38import java.util.LinkedHashSet;
39import java.util.LinkedList;
40import java.util.List;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030041import java.util.Map;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080042import java.util.Set;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -070043import java.util.concurrent.TimeUnit;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030044
45/**
46 * Test runner for dEQP tests
47 *
48 * Supports running drawElements Quality Program tests found under external/deqp.
49 */
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -070050public class DeqpTestRunner implements IBuildReceiver, IDeviceTest, IRemoteTest {
51
52 private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk";
53 private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp";
Pyry Haulos7cc94442014-10-02 13:16:37 -070054 private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log";
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080055 private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped";
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -070056 private static final String NOT_EXECUTABLE_LOG_MESSAGE = "Abort: Test cannot be executed";
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080057 private static final String CASE_LIST_FILE_NAME = "/sdcard/dEQP-TestCaseList.txt";
58 private static final String LOG_FILE_NAME = "/sdcard/TestLog.qpa";
59 public static final String FEATURE_LANDSCAPE = "android.hardware.screen.landscape";
60 public static final String FEATURE_PORTRAIT = "android.hardware.screen.portrait";
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -070061
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080062 private static final int TESTCASE_BATCH_LIMIT = 1000;
63 private static final BatchRunConfiguration DEFAULT_CONFIG =
64 new BatchRunConfiguration("rgba8888d24s8", "unspecified", "window");
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030065
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -070066 private static final int UNRESPOSIVE_CMD_TIMEOUT_MS = 60000; // one minute
67
Stuart Scotte00a2b82014-09-05 17:37:18 -070068 private final String mPackageName;
Mika Isojärvi69bd10f2014-09-10 13:55:05 +030069 private final String mName;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080070 private final Collection<TestIdentifier> mRemainingTests;
71 private final Map<TestIdentifier, Set<BatchRunConfiguration>> mTestInstances;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -070072 private final TestInstanceResultListener mInstanceListerner = new TestInstanceResultListener();
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -070073 private final Map<TestIdentifier, Integer> mTestInstabilityRatings;
Stuart Scottfef5e2c2014-09-05 17:37:18 -070074 private IAbi mAbi;
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -070075 private CtsBuildHelper mCtsBuild;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -070076 private boolean mLogData = false;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080077 private ITestDevice mDevice;
78 private Set<String> mDeviceFeatures;
Jarkko Pöyry3327b9f2015-04-09 15:09:07 -070079 private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>();
Jarkko Pöyry35d54ee2015-05-12 15:28:58 -070080 private IRunUtil mRunUtil = RunUtil.getDefault();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030081
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -070082 private IRecovery mDeviceRecovery = new Recovery();
83 {
84 mDeviceRecovery.setSleepProvider(new SleepProvider());
85 }
86
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080087 public DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests,
88 Map<TestIdentifier, List<Map<String,String>>> testInstances) {
Stuart Scotte00a2b82014-09-05 17:37:18 -070089 mPackageName = packageName;
Mika Isojärvi69bd10f2014-09-10 13:55:05 +030090 mName = name;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -080091 mRemainingTests = new LinkedList<>(tests); // avoid modifying arguments
92 mTestInstances = parseTestInstances(tests, testInstances);
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -070093 mTestInstabilityRatings = new HashMap<>();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +030094 }
95
96 /**
Stuart Scottfef5e2c2014-09-05 17:37:18 -070097 * @param abi the ABI to run the test on
98 */
99 public void setAbi(IAbi abi) {
100 mAbi = abi;
101 }
102
103 /**
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -0700104 * {@inheritDoc}
105 */
106 @Override
107 public void setBuild(IBuildInfo buildInfo) {
108 mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
109 }
110
111 /**
Jarkko Pöyrya8fe8f82014-10-07 17:46:26 -0700112 * Set the CTS build container.
113 * <p/>
114 * Exposed so unit tests can mock the provided build.
115 *
116 * @param buildHelper
117 */
118 public void setBuildHelper(CtsBuildHelper buildHelper) {
119 mCtsBuild = buildHelper;
120 }
121
122 /**
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300123 * Enable or disable raw dEQP test log collection.
124 */
125 public void setCollectLogs(boolean logData) {
126 mLogData = logData;
127 }
128
129 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800130 * {@inheritDoc}
131 */
132 @Override
133 public void setDevice(ITestDevice device) {
134 mDevice = device;
135 }
136
137 /**
138 * {@inheritDoc}
139 */
140 @Override
141 public ITestDevice getDevice() {
142 return mDevice;
143 }
144
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700145 /**
146 * Set recovery handler.
147 *
148 * Exposed for unit testing.
149 */
150 public void setRecovery(IRecovery deviceRecovery) {
151 mDeviceRecovery = deviceRecovery;
152 }
153
Jarkko Pöyry35d54ee2015-05-12 15:28:58 -0700154 /**
155 * Set IRunUtil.
156 *
157 * Exposed for unit testing.
158 */
159 public void setRunUtil(IRunUtil runUtil) {
160 mRunUtil = runUtil;
161 }
162
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800163 private static final class CapabilityQueryFailureException extends Exception {
164 };
165
166 /**
167 * Test configuration of dEPQ test instance execution.
168 * Exposed for unit testing
169 */
170 public static final class BatchRunConfiguration {
171 public static final String ROTATION_UNSPECIFIED = "unspecified";
172 public static final String ROTATION_PORTRAIT = "0";
173 public static final String ROTATION_LANDSCAPE = "90";
174 public static final String ROTATION_REVERSE_PORTRAIT = "180";
175 public static final String ROTATION_REVERSE_LANDSCAPE = "270";
176
177 private final String mGlConfig;
178 private final String mRotation;
179 private final String mSurfaceType;
180
181 public BatchRunConfiguration(String glConfig, String rotation, String surfaceType) {
182 mGlConfig = glConfig;
183 mRotation = rotation;
184 mSurfaceType = surfaceType;
185 }
186
187 /**
188 * Get string that uniquely identifies this config
189 */
190 public String getId() {
191 return String.format("{glformat=%s,rotation=%s,surfacetype=%s}",
192 mGlConfig, mRotation, mSurfaceType);
193 }
194
195 /**
196 * Get the GL config used in this configuration.
197 */
198 public String getGlConfig() {
199 return mGlConfig;
200 }
201
202 /**
203 * Get the screen rotation used in this configuration.
204 */
205 public String getRotation() {
206 return mRotation;
207 }
208
209 /**
210 * Get the surface type used in this configuration.
211 */
212 public String getSurfaceType() {
213 return mSurfaceType;
214 }
215
216 @Override
217 public boolean equals(Object other) {
218 if (other == null) {
219 return false;
220 } else if (!(other instanceof BatchRunConfiguration)) {
221 return false;
222 } else {
223 return getId().equals(((BatchRunConfiguration)other).getId());
224 }
225 }
226
227 @Override
228 public int hashCode() {
229 return getId().hashCode();
230 }
231 }
232
233 /**
234 * dEQP test instance listerer and invocation result forwarded
235 */
236 private class TestInstanceResultListener {
237 private ITestInvocationListener mSink;
238 private BatchRunConfiguration mRunConfig;
239
240 private TestIdentifier mCurrentTestId;
241 private boolean mGotTestResult;
242 private String mCurrentTestLog;
243
244 private class PendingResult
245 {
246 boolean allInstancesPassed;
247 Map<BatchRunConfiguration, String> testLogs;
248 Map<BatchRunConfiguration, String> errorMessages;
249 Set<BatchRunConfiguration> remainingConfigs;
250 };
251 private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>();
252
253 public void setSink(ITestInvocationListener sink) {
254 mSink = sink;
255 }
256
257 public void setCurrentConfig(BatchRunConfiguration runConfig) {
258 mRunConfig = runConfig;
259 }
260
261 /**
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700262 * Get currently processed test id, or null if not currently processing a test case
263 */
264 public TestIdentifier getCurrentTestId() {
265 return mCurrentTestId;
266 }
267
268 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800269 * Forward result to sink
270 */
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700271 private void forwardFinalizedPendingResult(TestIdentifier testId) {
272 if (mRemainingTests.contains(testId)) {
273 final PendingResult result = mPendingResults.get(testId);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800274
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700275 mPendingResults.remove(testId);
276 mRemainingTests.remove(testId);
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700277
278 // Forward results to the sink
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700279 mSink.testStarted(testId);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800280
281 // Test Log
282 if (mLogData) {
283 for (Map.Entry<BatchRunConfiguration, String> entry :
284 result.testLogs.entrySet()) {
285 final ByteArrayInputStreamSource source
286 = new ByteArrayInputStreamSource(entry.getValue().getBytes());
287
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700288 mSink.testLog(testId.getClassName() + "." + testId.getTestName() + "@"
289 + entry.getKey().getId(), LogDataType.XML, source);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800290
291 source.cancel();
292 }
293 }
294
295 // Error message
296 if (!result.allInstancesPassed) {
297 final StringBuilder errorLog = new StringBuilder();
298
299 for (Map.Entry<BatchRunConfiguration, String> entry :
300 result.errorMessages.entrySet()) {
301 if (errorLog.length() > 0) {
302 errorLog.append('\n');
303 }
304 errorLog.append(String.format("=== with config %s ===\n",
305 entry.getKey().getId()));
306 errorLog.append(entry.getValue());
307 }
308
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700309 mSink.testFailed(testId, errorLog.toString());
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800310 }
311
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800312 final Map<String, String> emptyMap = Collections.emptyMap();
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700313 mSink.testEnded(testId, emptyMap);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800314 }
315 }
316
317 /**
318 * Declare existence of a test and instances
319 */
320 public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) {
321 // Test instances cannot change at runtime, ignore if we have already set this
322 if (!mPendingResults.containsKey(testId)) {
323 final PendingResult pendingResult = new PendingResult();
324 pendingResult.allInstancesPassed = true;
325 pendingResult.testLogs = new LinkedHashMap<>();
326 pendingResult.errorMessages = new LinkedHashMap<>();
327 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument
328 mPendingResults.put(testId, pendingResult);
329 }
330 }
331
332 /**
333 * Query if test instance has not yet been executed
334 */
335 public boolean isPendingTestInstance(TestIdentifier testId,
336 BatchRunConfiguration config) {
337 final PendingResult result = mPendingResults.get(testId);
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700338 if (result == null) {
339 // test is not in the current working batch of the runner, i.e. it cannot be
340 // "partially" completed.
341 if (!mRemainingTests.contains(testId)) {
342 // The test has been fully executed. Not pending.
343 return false;
344 } else {
345 // Test has not yet been executed. Check if such instance exists
346 return mTestInstances.get(testId).contains(config);
347 }
348 } else {
349 // could be partially completed, check this particular config
350 return result.remainingConfigs.contains(config);
351 }
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800352 }
353
354 /**
355 * Fake execution of an instance with current config
356 */
357 public void skipTest(TestIdentifier testId) {
358 final PendingResult result = mPendingResults.get(testId);
359
360 result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE);
361 result.remainingConfigs.remove(mRunConfig);
362
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700363 // Pending result finished, report result
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800364 if (result.remainingConfigs.isEmpty()) {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700365 forwardFinalizedPendingResult(testId);
366 }
367 }
368
369 /**
370 * Fake failure of an instance with current config
371 */
372 public void abortTest(TestIdentifier testId, String errorMessage) {
373 final PendingResult result = mPendingResults.get(testId);
374
375 // Mark as executed
376 result.allInstancesPassed = false;
377 result.errorMessages.put(mRunConfig, errorMessage);
378 result.remainingConfigs.remove(mRunConfig);
379
380 // Pending result finished, report result
381 if (result.remainingConfigs.isEmpty()) {
382 forwardFinalizedPendingResult(testId);
383 }
384
385 if (testId.equals(mCurrentTestId)) {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800386 mCurrentTestId = null;
387 }
388 }
389
390 /**
391 * Handles beginning of dEQP session.
392 */
393 private void handleBeginSession(Map<String, String> values) {
394 // ignore
395 }
396
397 /**
398 * Handles end of dEQP session.
399 */
400 private void handleEndSession(Map<String, String> values) {
401 // ignore
402 }
403
404 /**
405 * Handles beginning of dEQP testcase.
406 */
407 private void handleBeginTestCase(Map<String, String> values) {
408 mCurrentTestId = pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath"));
409 mCurrentTestLog = "";
410 mGotTestResult = false;
411
412 // mark instance as started
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700413 if (mPendingResults.get(mCurrentTestId) != null) {
414 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig);
415 } else {
416 CLog.w("Got unexpected start of %s", mCurrentTestId);
417 }
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800418 }
419
420 /**
421 * Handles end of dEQP testcase.
422 */
423 private void handleEndTestCase(Map<String, String> values) {
424 final PendingResult result = mPendingResults.get(mCurrentTestId);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800425
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700426 if (result != null) {
427 if (!mGotTestResult) {
428 result.allInstancesPassed = false;
429 result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE);
430 }
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800431
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700432 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) {
433 result.testLogs.put(mRunConfig, mCurrentTestLog);
434 }
435
436 // Pending result finished, report result
437 if (result.remainingConfigs.isEmpty()) {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700438 forwardFinalizedPendingResult(mCurrentTestId);
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700439 }
440 } else {
441 CLog.w("Got unexpected end of %s", mCurrentTestId);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800442 }
443 mCurrentTestId = null;
444 }
445
446 /**
447 * Handles dEQP testcase result.
448 */
449 private void handleTestCaseResult(Map<String, String> values) {
450 String code = values.get("dEQP-TestCaseResult-Code");
451 String details = values.get("dEQP-TestCaseResult-Details");
452
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700453 if (mPendingResults.get(mCurrentTestId) == null) {
454 CLog.w("Got unexpected result for %s", mCurrentTestId);
455 mGotTestResult = true;
456 return;
457 }
458
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800459 if (code.compareTo("Pass") == 0) {
460 mGotTestResult = true;
461 } else if (code.compareTo("NotSupported") == 0) {
462 mGotTestResult = true;
463 } else if (code.compareTo("QualityWarning") == 0) {
464 mGotTestResult = true;
465 } else if (code.compareTo("CompatibilityWarning") == 0) {
466 mGotTestResult = true;
467 } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0
468 || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0
469 || code.compareTo("Timeout") == 0) {
470 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
471 mPendingResults.get(mCurrentTestId)
472 .errorMessages.put(mRunConfig, code + ": " + details);
473 mGotTestResult = true;
474 } else {
475 String codeError = "Unknown result code: " + code;
476 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
477 mPendingResults.get(mCurrentTestId)
478 .errorMessages.put(mRunConfig, codeError + ": " + details);
479 mGotTestResult = true;
480 }
481 }
482
483 /**
484 * Handles terminated dEQP testcase.
485 */
486 private void handleTestCaseTerminate(Map<String, String> values) {
487 final PendingResult result = mPendingResults.get(mCurrentTestId);
488
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700489 if (result != null) {
490 String reason = values.get("dEQP-TerminateTestCase-Reason");
491 mPendingResults.get(mCurrentTestId).allInstancesPassed = false;
492 mPendingResults.get(mCurrentTestId)
493 .errorMessages.put(mRunConfig, "Terminated: " + reason);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800494
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700495 // Pending result finished, report result
496 if (result.remainingConfigs.isEmpty()) {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700497 forwardFinalizedPendingResult(mCurrentTestId);
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700498 }
499 } else {
500 CLog.w("Got unexpected termination of %s", mCurrentTestId);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800501 }
502
503 mCurrentTestId = null;
504 mGotTestResult = true;
505 }
506
507 /**
508 * Handles dEQP testlog data.
509 */
510 private void handleTestLogData(Map<String, String> values) {
511 mCurrentTestLog = mCurrentTestLog + values.get("dEQP-TestLogData-Log");
512 }
513
514 /**
515 * Handles new instrumentation status message.
516 */
517 public void handleStatus(Map<String, String> values) {
518 String eventType = values.get("dEQP-EventType");
519
520 if (eventType == null) {
521 return;
522 }
523
524 if (eventType.compareTo("BeginSession") == 0) {
525 handleBeginSession(values);
526 } else if (eventType.compareTo("EndSession") == 0) {
527 handleEndSession(values);
528 } else if (eventType.compareTo("BeginTestCase") == 0) {
529 handleBeginTestCase(values);
530 } else if (eventType.compareTo("EndTestCase") == 0) {
531 handleEndTestCase(values);
532 } else if (eventType.compareTo("TestCaseResult") == 0) {
533 handleTestCaseResult(values);
534 } else if (eventType.compareTo("TerminateTestCase") == 0) {
535 handleTestCaseTerminate(values);
536 } else if (eventType.compareTo("TestLogData") == 0) {
537 handleTestLogData(values);
538 }
539 }
540
541 /**
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700542 * Signal listener that batch ended and forget incomplete results.
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800543 */
544 public void endBatch() {
545 // end open test if when stream ends
546 if (mCurrentTestId != null) {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700547 // Current instance was removed from remainingConfigs when case
548 // started. Mark current instance as pending.
549 if (mPendingResults.get(mCurrentTestId) != null) {
550 mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig);
551 } else {
552 CLog.w("Got unexpected internal state of %s", mCurrentTestId);
Pyry Haulos4eab57a2015-04-03 10:43:42 -0700553 }
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800554 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700555 mCurrentTestId = null;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800556 }
557 }
558
559 /**
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300560 * dEQP instrumentation parser
561 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800562 private static class InstrumentationParser extends MultiLineReceiver {
563 private TestInstanceResultListener mListener;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300564
565 private Map<String, String> mValues;
566 private String mCurrentName;
567 private String mCurrentValue;
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700568 private int mResultCode;
569 private boolean mGotExitValue = false;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300570
571
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800572 public InstrumentationParser(TestInstanceResultListener listener) {
573 mListener = listener;
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300574 }
575
576 /**
577 * {@inheritDoc}
578 */
579 @Override
580 public void processNewLines(String[] lines) {
Stuart Scottfef5e2c2014-09-05 17:37:18 -0700581 for (String line : lines) {
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300582 if (mValues == null) mValues = new HashMap<String, String>();
583
584 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) {
585 if (mCurrentName != null) {
586 mValues.put(mCurrentName, mCurrentValue);
587
588 mCurrentName = null;
589 mCurrentValue = null;
590 }
591
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800592 mListener.handleStatus(mValues);
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300593 mValues = null;
594 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
595 if (mCurrentName != null) {
596 mValues.put(mCurrentName, mCurrentValue);
597
598 mCurrentValue = null;
599 mCurrentName = null;
600 }
601
602 String prefix = "INSTRUMENTATION_STATUS: ";
603 int nameBegin = prefix.length();
604 int nameEnd = line.indexOf('=');
605 int valueBegin = nameEnd + 1;
606
607 mCurrentName = line.substring(nameBegin, nameEnd);
608 mCurrentValue = line.substring(valueBegin);
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700609 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
610 try {
611 mResultCode = Integer.parseInt(line.substring(22));
612 mGotExitValue = true;
613 } catch (NumberFormatException ex) {
614 CLog.w("Instrumentation code format unexpected");
615 }
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300616 } else if (mCurrentValue != null) {
617 mCurrentValue = mCurrentValue + line;
618 }
619 }
620 }
621
622 /**
623 * {@inheritDoc}
624 */
625 @Override
626 public void done() {
627 if (mCurrentName != null) {
628 mValues.put(mCurrentName, mCurrentValue);
629
630 mCurrentName = null;
631 mCurrentValue = null;
632 }
633
634 if (mValues != null) {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800635 mListener.handleStatus(mValues);
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300636 mValues = null;
637 }
638 }
639
640 /**
641 * {@inheritDoc}
642 */
643 @Override
644 public boolean isCancelled() {
645 return false;
646 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700647
648 /**
649 * Returns whether target instrumentation exited normally.
650 */
651 public boolean wasSuccessful() {
652 return mGotExitValue;
653 }
654
655 /**
656 * Returns Instrumentation return code
657 */
658 public int getResultCode() {
659 return mResultCode;
660 }
Mika Isojärvif1e23ee2014-08-25 13:17:58 +0300661 }
662
663 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800664 * dEQP platfom query instrumentation parser
665 */
666 private static class PlatformQueryInstrumentationParser extends MultiLineReceiver {
667 private Map<String,String> mResultMap = new LinkedHashMap<>();
668 private int mResultCode;
669 private boolean mGotExitValue = false;
670
671 /**
672 * {@inheritDoc}
673 */
674 @Override
675 public void processNewLines(String[] lines) {
676 for (String line : lines) {
677 if (line.startsWith("INSTRUMENTATION_RESULT: ")) {
678 final String parts[] = line.substring(24).split("=",2);
679 if (parts.length == 2) {
680 mResultMap.put(parts[0], parts[1]);
681 } else {
682 CLog.w("Instrumentation status format unexpected");
683 }
684 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
685 try {
686 mResultCode = Integer.parseInt(line.substring(22));
687 mGotExitValue = true;
688 } catch (NumberFormatException ex) {
689 CLog.w("Instrumentation code format unexpected");
690 }
691 }
692 }
693 }
694
695 /**
696 * {@inheritDoc}
697 */
698 @Override
699 public boolean isCancelled() {
700 return false;
701 }
702
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700703 /**
704 * Returns whether target instrumentation exited normally.
705 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800706 public boolean wasSuccessful() {
707 return mGotExitValue;
708 }
709
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -0700710 /**
711 * Returns Instrumentation return code
712 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800713 public int getResultCode() {
714 return mResultCode;
715 }
716
717 public Map<String,String> getResultMap() {
718 return mResultMap;
719 }
720 }
721
722 /**
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700723 * Interface for sleeping.
724 *
725 * Exposed for unit testing
726 */
727 public static interface ISleepProvider {
728 public void sleep(int milliseconds);
729 };
730
731 private static class SleepProvider implements ISleepProvider {
732 public void sleep(int milliseconds) {
733 try {
734 Thread.sleep(milliseconds);
735 } catch (InterruptedException ex) {
736 }
737 }
738 };
739
740 /**
741 * Interface for failure recovery.
742 *
743 * Exposed for unit testing
744 */
745 public static interface IRecovery {
746 /**
747 * Sets the sleep provider IRecovery works on
748 */
749 public void setSleepProvider(ISleepProvider sleepProvider);
750
751 /**
752 * Sets the device IRecovery works on
753 */
754 public void setDevice(ITestDevice device);
755
756 /**
757 * Informs Recovery that test execution has progressed since the last recovery
758 */
759 public void onExecutionProgressed();
760
761 /**
762 * Tries to recover device after failed refused connection.
763 *
764 * @throws DeviceNotAvailableException if recovery did not succeed
765 */
766 public void recoverConnectionRefused() throws DeviceNotAvailableException;
767
768 /**
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700769 * Tries to recover device after abnormal execution termination or link failure.
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700770 *
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700771 * @param progressedSinceLastCall true if test execution has progressed since last call
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700772 * @throws DeviceNotAvailableException if recovery did not succeed
773 */
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700774 public void recoverComLinkKilled() throws DeviceNotAvailableException;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700775 };
776
777 /**
778 * State machine for execution failure recovery.
779 *
780 * Exposed for unit testing
781 */
782 public static class Recovery implements IRecovery {
783 private int RETRY_COOLDOWN_MS = 6000; // 6 seconds
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700784 private int PROCESS_KILL_WAIT_MS = 1000; // 1 second
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700785
786 private static enum MachineState {
787 WAIT, // recover by waiting
788 RECOVER, // recover by calling recover()
789 REBOOT, // recover by rebooting
790 FAIL, // cannot recover
791 };
792
793 private MachineState mState = MachineState.WAIT;
794 private ITestDevice mDevice;
795 private ISleepProvider mSleepProvider;
796
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700797 private static class ProcessKillFailureException extends Exception {
798 }
799
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700800 /**
801 * {@inheritDoc}
802 */
803 public void setSleepProvider(ISleepProvider sleepProvider) {
804 mSleepProvider = sleepProvider;
805 }
806
807 /**
808 * {@inheritDoc}
809 */
810 @Override
811 public void setDevice(ITestDevice device) {
812 mDevice = device;
813 }
814
815 /**
816 * {@inheritDoc}
817 */
818 @Override
819 public void onExecutionProgressed() {
820 mState = MachineState.WAIT;
821 }
822
823 /**
824 * {@inheritDoc}
825 */
826 @Override
827 public void recoverConnectionRefused() throws DeviceNotAvailableException {
828 switch (mState) {
829 case WAIT: // not a valid stratedy for connection refusal, fallthrough
830 case RECOVER:
831 // First failure, just try to recover
832 CLog.w("ADB connection failed, trying to recover");
833 mState = MachineState.REBOOT; // the next step is to reboot
834
835 try {
836 recoverDevice();
837 } catch (DeviceNotAvailableException ex) {
838 // chain forward
839 recoverConnectionRefused();
840 }
841 break;
842
843 case REBOOT:
844 // Second failure in a row, try to reboot
845 CLog.w("ADB connection failed after recovery, rebooting device");
846 mState = MachineState.FAIL; // the next step is to fail
847
848 try {
849 rebootDevice();
850 } catch (DeviceNotAvailableException ex) {
851 // chain forward
852 recoverConnectionRefused();
853 }
854 break;
855
856 case FAIL:
857 // Third failure in a row, just fail
858 CLog.w("Cannot recover ADB connection");
859 throw new DeviceNotAvailableException("failed to connect after reboot");
860 }
861 }
862
863 /**
864 * {@inheritDoc}
865 */
866 @Override
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700867 public void recoverComLinkKilled() throws DeviceNotAvailableException {
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700868 switch (mState) {
869 case WAIT:
870 // First failure, just try to wait and try again
871 CLog.w("ADB link failed, retrying after a cooldown period");
872 mState = MachineState.RECOVER; // the next step is to recover the device
873
874 waitCooldown();
875
876 // even if the link to deqp on-device process was killed, the process might
877 // still be alive. Locate and terminate such unwanted processes.
878 try {
879 killDeqpProcess();
880 } catch (DeviceNotAvailableException ex) {
881 // chain forward
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700882 recoverComLinkKilled();
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700883 } catch (ProcessKillFailureException ex) {
884 // chain forward
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700885 recoverComLinkKilled();
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700886 }
887 break;
888
889 case RECOVER:
890 // Second failure, just try to recover
891 CLog.w("ADB link failed, trying to recover");
892 mState = MachineState.REBOOT; // the next step is to reboot
893
894 try {
895 recoverDevice();
896 killDeqpProcess();
897 } catch (DeviceNotAvailableException ex) {
898 // chain forward
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700899 recoverComLinkKilled();
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700900 } catch (ProcessKillFailureException ex) {
901 // chain forward
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700902 recoverComLinkKilled();
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700903 }
904 break;
905
906 case REBOOT:
907 // Third failure in a row, try to reboot
908 CLog.w("ADB link failed after recovery, rebooting device");
909 mState = MachineState.FAIL; // the next step is to fail
910
911 try {
912 rebootDevice();
913 } catch (DeviceNotAvailableException ex) {
914 // chain forward
Pyry Haulos84d67ea2015-07-27 11:14:44 -0700915 recoverComLinkKilled();
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700916 }
917 break;
918
919 case FAIL:
920 // Fourth failure in a row, just fail
921 CLog.w("Cannot recover ADB connection");
922 throw new DeviceNotAvailableException("link killed after reboot");
923 }
924 }
925
926 private void waitCooldown() {
927 mSleepProvider.sleep(RETRY_COOLDOWN_MS);
928 }
929
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700930 private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException {
931 final List<Integer> pids = new ArrayList<Integer>(2);
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700932 final String processes = mDevice.executeShellCommand("ps | grep com.drawelements");
933 final String[] lines = processes.split("(\\r|\\n)+");
934 for (String line : lines) {
935 final String[] fields = line.split("\\s+");
936 if (fields.length < 2) {
937 continue;
938 }
939
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700940 try {
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700941 final int processId = Integer.parseInt(fields[1], 10);
942 pids.add(processId);
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700943 } catch (NumberFormatException ex) {
944 continue;
945 }
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700946 }
947 return pids;
948 }
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700949
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700950 private void killDeqpProcess() throws DeviceNotAvailableException,
951 ProcessKillFailureException {
952 for (Integer processId : getDeqpProcessPids()) {
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700953 mDevice.executeShellCommand(String.format("kill -9 %d", processId));
954 }
Jarkko Pöyry41c41d52015-04-13 12:14:29 -0700955
956 mSleepProvider.sleep(PROCESS_KILL_WAIT_MS);
957
958 // check that processes actually died
959 if (getDeqpProcessPids().iterator().hasNext()) {
960 // a process is still alive, killing failed
961 throw new ProcessKillFailureException();
962 }
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -0700963 }
964
965 public void recoverDevice() throws DeviceNotAvailableException {
966 // Work around the API. We need to call recoverDevice() on the test device and
967 // we know that mDevice is a TestDevice. However even though the recoverDevice()
968 // method is public suggesting it should be publicly accessible, the class itself
969 // and its super-interface (IManagedTestDevice) are package-private.
970 final Method recoverDeviceMethod;
971 try {
972 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice");
973 recoverDeviceMethod.setAccessible(true);
974 } catch (NoSuchMethodException ex) {
975 throw new AssertionError("Test device must have recoverDevice()");
976 }
977
978 try {
979 recoverDeviceMethod.invoke(mDevice);
980 } catch (InvocationTargetException ex) {
981 if (ex.getCause() instanceof DeviceNotAvailableException) {
982 throw (DeviceNotAvailableException)ex.getCause();
983 } else if (ex.getCause() instanceof RuntimeException) {
984 throw (RuntimeException)ex.getCause();
985 } else {
986 throw new AssertionError("unexpected throw", ex);
987 }
988 } catch (IllegalAccessException ex) {
989 throw new AssertionError("unexpected throw", ex);
990 }
991 }
992
993 private void rebootDevice() throws DeviceNotAvailableException {
994 mDevice.reboot();
995 }
996 };
997
998 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -0800999 * Parse map of instance arguments to map of BatchRunConfigurations
1000 */
1001 private static Map<TestIdentifier, Set<BatchRunConfiguration>> parseTestInstances(
1002 Collection<TestIdentifier> tests,
1003 Map<TestIdentifier, List<Map<String,String>>> testInstances) {
1004 final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new HashMap<>();
1005 for (final TestIdentifier test : tests) {
1006 final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>();
1007 if (testInstances.get(test).isEmpty()) {
1008 // no instances defined, use default
1009 testInstanceSet.add(DEFAULT_CONFIG);
1010 } else {
1011 for (Map<String, String> instanceArgs : testInstances.get(test)) {
1012 testInstanceSet.add(parseRunConfig(instanceArgs));
1013 }
1014 }
1015 instances.put(test, testInstanceSet);
1016 }
1017 return instances;
1018 }
1019
1020 private static BatchRunConfiguration parseRunConfig(Map<String,String> instanceArguments) {
1021 final String glConfig;
1022 final String rotation;
1023 final String surfaceType;
1024
1025 if (instanceArguments.containsKey("glconfig")) {
1026 glConfig = instanceArguments.get("glconfig");
1027 } else {
1028 glConfig = DEFAULT_CONFIG.getGlConfig();
1029 }
1030 if (instanceArguments.containsKey("rotation")) {
1031 rotation = instanceArguments.get("rotation");
1032 } else {
1033 rotation = DEFAULT_CONFIG.getRotation();
1034 }
1035 if (instanceArguments.containsKey("surfaceType")) {
1036 surfaceType = instanceArguments.get("surfaceType");
1037 } else {
1038 surfaceType = DEFAULT_CONFIG.getSurfaceType();
1039 }
1040
1041 return new BatchRunConfiguration(glConfig, rotation, surfaceType);
1042 }
1043
1044 private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) {
1045 return mTestInstances.get(testId);
1046 }
1047
1048 /**
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001049 * Converts dEQP testcase path to TestIdentifier.
1050 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001051 private static TestIdentifier pathToIdentifier(String testPath) {
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001052 String[] components = testPath.split("\\.");
1053 String name = components[components.length - 1];
1054 String className = null;
1055
1056 for (int i = 0; i < components.length - 1; i++) {
1057 if (className == null) {
1058 className = components[i];
1059 } else {
1060 className = className + "." + components[i];
1061 }
1062 }
1063
1064 return new TestIdentifier(className, name);
1065 }
1066
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001067 private String getId() {
1068 return AbiUtils.createId(mAbi.getName(), mPackageName);
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001069 }
1070
1071 /**
1072 * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute.
1073 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001074 private static String generateTestCaseTrieFromPaths(Collection<String> tests) {
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001075 String result = "{";
1076 boolean first = true;
1077
1078 // Add testcases to results
1079 for (Iterator<String> iter = tests.iterator(); iter.hasNext();) {
1080 String test = iter.next();
1081 String[] components = test.split("\\.");
1082
1083 if (components.length == 1) {
1084 if (!first) {
1085 result = result + ",";
1086 }
1087 first = false;
1088
1089 result += components[0];
1090 iter.remove();
1091 }
1092 }
1093
1094 if (!tests.isEmpty()) {
Stuart Scottfef5e2c2014-09-05 17:37:18 -07001095 HashMap<String, ArrayList<String> > testGroups = new HashMap<>();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001096
1097 // Collect all sub testgroups
1098 for (String test : tests) {
1099 String[] components = test.split("\\.");
1100 ArrayList<String> testGroup = testGroups.get(components[0]);
1101
1102 if (testGroup == null) {
Stuart Scottfef5e2c2014-09-05 17:37:18 -07001103 testGroup = new ArrayList<String>();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001104 testGroups.put(components[0], testGroup);
1105 }
1106
1107 testGroup.add(test.substring(components[0].length()+1));
1108 }
1109
1110 for (String testGroup : testGroups.keySet()) {
1111 if (!first) {
1112 result = result + ",";
1113 }
1114
1115 first = false;
1116 result = result + testGroup
1117 + generateTestCaseTrieFromPaths(testGroups.get(testGroup));
1118 }
1119 }
1120
1121 return result + "}";
1122 }
1123
1124 /**
Stuart Scottfef5e2c2014-09-05 17:37:18 -07001125 * Generates testcase trie from TestIdentifiers.
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001126 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001127 private static String generateTestCaseTrie(Collection<TestIdentifier> tests) {
Stuart Scottfef5e2c2014-09-05 17:37:18 -07001128 ArrayList<String> testPaths = new ArrayList<String>();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001129
1130 for (TestIdentifier test : tests) {
1131 testPaths.add(test.getClassName() + "." + test.getTestName());
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001132 }
1133
1134 return generateTestCaseTrieFromPaths(testPaths);
1135 }
1136
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001137 private static class TestBatch {
1138 public BatchRunConfiguration config;
1139 public List<TestIdentifier> tests;
1140 }
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001141
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001142 private TestBatch selectRunBatch() {
1143 return selectRunBatch(mRemainingTests, null);
1144 }
1145
1146 /**
1147 * Creates a TestBatch from the given tests or null if not tests remaining.
1148 *
1149 * @param pool List of tests to select from
1150 * @param requiredConfig Select only instances with pending requiredConfig, or null to select
1151 * any run configuration.
1152 */
1153 private TestBatch selectRunBatch(Collection<TestIdentifier> pool,
1154 BatchRunConfiguration requiredConfig) {
1155 // select one test (leading test) that is going to be executed and then pack along as many
1156 // other compatible instances as possible.
1157
1158 TestIdentifier leadingTest = null;
1159 for (TestIdentifier test : pool) {
1160 if (!mRemainingTests.contains(test)) {
1161 continue;
1162 }
1163 if (requiredConfig != null &&
1164 !mInstanceListerner.isPendingTestInstance(test, requiredConfig)) {
1165 continue;
1166 }
1167 leadingTest = test;
1168 break;
1169 }
1170
1171 // no remaining tests?
1172 if (leadingTest == null) {
1173 return null;
1174 }
1175
1176 BatchRunConfiguration leadingTestConfig = null;
1177 if (requiredConfig != null) {
1178 leadingTestConfig = requiredConfig;
1179 } else {
1180 for (BatchRunConfiguration runConfig : getTestRunConfigs(leadingTest)) {
1181 if (mInstanceListerner.isPendingTestInstance(leadingTest, runConfig)) {
1182 leadingTestConfig = runConfig;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001183 break;
1184 }
1185 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001186 }
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001187
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001188 // test pending <=> test has a pending config
1189 if (leadingTestConfig == null) {
1190 throw new AssertionError("search postcondition failed");
1191 }
1192
1193 final int leadingInstability = getTestInstabilityRating(leadingTest);
1194
1195 final TestBatch runBatch = new TestBatch();
1196 runBatch.config = leadingTestConfig;
1197 runBatch.tests = new ArrayList<>();
1198 runBatch.tests.add(leadingTest);
1199
1200 for (TestIdentifier test : pool) {
1201 if (test == leadingTest) {
1202 // do not re-select the leading tests
1203 continue;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001204 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001205 if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) {
1206 // select only compatible
1207 continue;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001208 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001209 if (getTestInstabilityRating(test) != leadingInstability) {
1210 // pack along only cases in the same stability category. Packing more dangerous
1211 // tests along jeopardizes the stability of this run. Packing more stable tests
1212 // along jeopardizes their stability rating.
1213 continue;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001214 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001215 if (runBatch.tests.size() >= getBatchSizeLimitForInstability(leadingInstability)) {
1216 // batch size is limited.
1217 break;
1218 }
1219 runBatch.tests.add(test);
1220 }
1221
1222 return runBatch;
1223 }
1224
1225 private int getBatchNumPendingCases(TestBatch batch) {
1226 int numPending = 0;
1227 for (TestIdentifier test : batch.tests) {
1228 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1229 ++numPending;
1230 }
1231 }
1232 return numPending;
1233 }
1234
1235 private int getBatchSizeLimitForInstability(int batchInstabilityRating) {
1236 // reduce group size exponentially down to one
1237 return Math.max(1, TESTCASE_BATCH_LIMIT / (1 << batchInstabilityRating));
1238 }
1239
1240 private int getTestInstabilityRating(TestIdentifier testId) {
1241 if (mTestInstabilityRatings.containsKey(testId)) {
1242 return mTestInstabilityRatings.get(testId);
1243 } else {
1244 return 0;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001245 }
1246 }
1247
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001248 private void recordTestInstability(TestIdentifier testId) {
1249 mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1);
1250 }
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001251
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001252 private void clearTestInstability(TestIdentifier testId) {
1253 mTestInstabilityRatings.put(testId, 0);
1254 }
1255
1256 /**
1257 * Executes all tests on the device.
1258 */
1259 private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException {
1260 for (;;) {
1261 TestBatch batch = selectRunBatch();
1262
1263 if (batch == null) {
1264 break;
1265 }
1266
1267 runTestRunBatch(batch);
1268 }
1269 }
1270
1271 /**
1272 * Runs a TestBatch by either faking it or executing it on a device.
1273 */
1274 private void runTestRunBatch(TestBatch batch) throws DeviceNotAvailableException,
1275 CapabilityQueryFailureException {
1276 // prepare instance listener
1277 mInstanceListerner.setCurrentConfig(batch.config);
1278 for (TestIdentifier test : batch.tests) {
1279 mInstanceListerner.setTestInstances(test, getTestRunConfigs(test));
1280 }
1281
1282 // execute only if config is executable, else fake results
1283 if (isSupportedRunConfiguration(batch.config)) {
1284 executeTestRunBatch(batch);
1285 } else {
1286 fakePassTestRunBatch(batch);
1287 }
1288 }
1289
1290 private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig)
1291 throws DeviceNotAvailableException, CapabilityQueryFailureException {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001292 // orientation support
1293 if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) {
1294 final Set<String> features = getDeviceFeatures(mDevice);
1295
1296 if (isPortraitClassRotation(runConfig.getRotation()) &&
1297 !features.contains(FEATURE_PORTRAIT)) {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001298 return false;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001299 }
1300 if (isLandscapeClassRotation(runConfig.getRotation()) &&
1301 !features.contains(FEATURE_LANDSCAPE)) {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001302 return false;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001303 }
1304 }
1305
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001306 if (isOpenGlEsPackage()) {
1307 // renderability support for OpenGL ES tests
1308 return isSupportedGlesRenderConfig(runConfig);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001309 } else {
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001310 return true;
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001311 }
1312 }
1313
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001314 private static final class AdbComLinkOpenError extends Exception {
1315 public AdbComLinkOpenError(String description, Throwable inner) {
1316 super(description, inner);
1317 }
1318 };
1319 private static final class AdbComLinkKilledError extends Exception {
1320 public AdbComLinkKilledError(String description, Throwable inner) {
1321 super(description, inner);
1322 }
1323 };
1324
1325 /**
1326 * Executes a given command in adb shell
1327 *
1328 * @throws AdbComLinkOpenError if connection cannot be established.
1329 * @throws AdbComLinkKilledError if established connection is killed prematurely.
1330 */
1331 private void executeShellCommandAndReadOutput(final String command,
1332 final IShellOutputReceiver receiver)
1333 throws AdbComLinkOpenError, AdbComLinkKilledError {
1334 try {
1335 mDevice.getIDevice().executeShellCommand(command, receiver,
1336 UNRESPOSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS);
1337 } catch (TimeoutException ex) {
1338 // Opening connection timed out
1339 throw new AdbComLinkOpenError("opening connection timed out", ex);
1340 } catch (AdbCommandRejectedException ex) {
1341 // Command rejected
1342 throw new AdbComLinkOpenError("command rejected", ex);
1343 } catch (IOException ex) {
1344 // shell command channel killed
1345 throw new AdbComLinkKilledError("command link killed", ex);
1346 } catch (ShellCommandUnresponsiveException ex) {
1347 // shell command halted
1348 throw new AdbComLinkKilledError("command link hung", ex);
1349 }
1350 }
1351
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001352 /**
1353 * Executes given test batch on a device
1354 */
1355 private void executeTestRunBatch(TestBatch batch) throws DeviceNotAvailableException {
1356 // attempt full run once
1357 executeTestRunBatchRun(batch);
1358
1359 // split remaining tests to two sub batches and execute both. This will terminate
1360 // since executeTestRunBatchRun will always progress for a batch of size 1.
1361 final ArrayList<TestIdentifier> pendingTests = new ArrayList<>();
1362
1363 for (TestIdentifier test : batch.tests) {
1364 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1365 pendingTests.add(test);
1366 }
1367 }
1368
1369 final int divisorNdx = pendingTests.size() / 2;
1370 final List<TestIdentifier> headList = pendingTests.subList(0, divisorNdx);
1371 final List<TestIdentifier> tailList = pendingTests.subList(divisorNdx, pendingTests.size());
1372
1373 // head
1374 for (;;) {
1375 TestBatch subBatch = selectRunBatch(headList, batch.config);
1376
1377 if (subBatch == null) {
1378 break;
1379 }
1380
1381 executeTestRunBatch(subBatch);
1382 }
1383
1384 // tail
1385 for (;;) {
1386 TestBatch subBatch = selectRunBatch(tailList, batch.config);
1387
1388 if (subBatch == null) {
1389 break;
1390 }
1391
1392 executeTestRunBatch(subBatch);
1393 }
1394
1395 if (getBatchNumPendingCases(batch) != 0) {
1396 throw new AssertionError("executeTestRunBatch postcondition failed");
1397 }
1398 }
1399
1400 /**
1401 * Runs one execution pass over the given batch.
1402 *
1403 * Tries to run the batch. Always makes progress (executes instances or modifies stability
1404 * scores).
1405 */
1406 private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException {
1407 if (getBatchNumPendingCases(batch) != batch.tests.size()) {
1408 throw new AssertionError("executeTestRunBatchRun precondition failed");
1409 }
1410
Jarkko Pöyry35d54ee2015-05-12 15:28:58 -07001411 checkInterrupted(); // throws if interrupted
1412
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001413 final String testCases = generateTestCaseTrie(batch.tests);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001414
1415 mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME);
Pyry Haulos84d67ea2015-07-27 11:14:44 -07001416 mDevice.executeShellCommand("rm " + LOG_FILE_NAME);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001417 mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME);
1418
1419 final String instrumentationName =
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001420 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation";
Mika Isojärvia86997b2014-09-24 15:12:59 +03001421
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001422 final StringBuilder deqpCmdLine = new StringBuilder();
1423 deqpCmdLine.append("--deqp-caselist-file=");
1424 deqpCmdLine.append(CASE_LIST_FILE_NAME);
1425 deqpCmdLine.append(" ");
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001426 deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config));
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001427
Kalle Raita81decde2015-02-19 13:28:09 -08001428 // If we are not logging data, do not bother outputting the images from the test exe.
1429 if (!mLogData) {
1430 deqpCmdLine.append(" --deqp-log-images=disable");
1431 }
1432
Pyry Haulos97a49bc2015-04-03 10:48:49 -07001433 deqpCmdLine.append(" --deqp-watchdog=enable");
1434
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001435 final String command = String.format(
1436 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\""
1437 + " -e deqpLogData \"%s\" %s",
1438 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(),
1439 mLogData, instrumentationName);
1440
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001441 final int numRemainingInstancesBefore = getNumRemainingInstances();
1442 final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner);
1443 Throwable interruptingError = null;
1444
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001445 try {
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001446 executeShellCommandAndReadOutput(command, parser);
1447 } catch (Throwable ex) {
1448 interruptingError = ex;
1449 } finally {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001450 parser.flush();
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001451 }
1452
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001453 final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null ||
1454 getNumRemainingInstances() < numRemainingInstancesBefore;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001455
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001456 if (progressedSinceLastCall) {
1457 mDeviceRecovery.onExecutionProgressed();
1458 }
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001459
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001460 // interrupted, try to recover
1461 if (interruptingError != null) {
1462 if (interruptingError instanceof AdbComLinkOpenError) {
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001463 mDeviceRecovery.recoverConnectionRefused();
1464 } else if (interruptingError instanceof AdbComLinkKilledError) {
Pyry Haulos84d67ea2015-07-27 11:14:44 -07001465 mDeviceRecovery.recoverComLinkKilled();
Jarkko Pöyry35d54ee2015-05-12 15:28:58 -07001466 } else if (interruptingError instanceof RunInterruptedException) {
1467 // external run interruption request. Terminate immediately.
1468 throw (RunInterruptedException)interruptingError;
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001469 } else {
1470 CLog.e(interruptingError);
1471 throw new RuntimeException(interruptingError);
1472 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001473
1474 // recoverXXX did not throw => recovery succeeded
1475 } else if (!parser.wasSuccessful()) {
Pyry Haulos84d67ea2015-07-27 11:14:44 -07001476 mDeviceRecovery.recoverComLinkKilled();
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001477 // recoverXXX did not throw => recovery succeeded
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001478 }
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001479
1480 // Progress guarantees.
1481 if (batch.tests.size() == 1) {
1482 final TestIdentifier onlyTest = batch.tests.iterator().next();
1483 final boolean wasTestExecuted =
1484 !mInstanceListerner.isPendingTestInstance(onlyTest, batch.config) &&
1485 mInstanceListerner.getCurrentTestId() == null;
1486 final boolean wasLinkFailure = !parser.wasSuccessful() || interruptingError != null;
1487
1488 // Link failures can be caused by external events, require at least two observations
1489 // until bailing.
1490 if (!wasTestExecuted && (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) {
1491 recordTestInstability(onlyTest);
1492 // If we cannot finish the test, mark the case as a crash.
1493 //
1494 // If we couldn't even start the test, fail the test instance as non-executable.
1495 // This is required so that a consistently crashing or non-existent tests will
1496 // not cause futile (non-terminating) re-execution attempts.
1497 if (mInstanceListerner.getCurrentTestId() != null) {
1498 mInstanceListerner.abortTest(onlyTest, INCOMPLETE_LOG_MESSAGE);
1499 } else {
1500 mInstanceListerner.abortTest(onlyTest, NOT_EXECUTABLE_LOG_MESSAGE);
1501 }
1502 } else if (wasTestExecuted) {
1503 clearTestInstability(onlyTest);
1504 }
1505 }
1506 else
1507 {
1508 // Analyze results to update test stability ratings. If there is no interrupting test
1509 // logged, increase instability rating of all remaining tests. If there is a
1510 // interrupting test logged, increase only its instability rating.
1511 //
1512 // A successful run of tests clears instability rating.
1513 if (mInstanceListerner.getCurrentTestId() == null) {
1514 for (TestIdentifier test : batch.tests) {
1515 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1516 recordTestInstability(test);
1517 } else {
1518 clearTestInstability(test);
1519 }
1520 }
1521 } else {
1522 recordTestInstability(mInstanceListerner.getCurrentTestId());
1523 for (TestIdentifier test : batch.tests) {
1524 // \note: isPendingTestInstance is false for getCurrentTestId. Current ID is
1525 // considered 'running' and will be restored to 'pending' in endBatch().
1526 if (!test.equals(mInstanceListerner.getCurrentTestId()) &&
1527 !mInstanceListerner.isPendingTestInstance(test, batch.config)) {
1528 clearTestInstability(test);
1529 }
1530 }
1531 }
1532 }
1533
1534 mInstanceListerner.endBatch();
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001535 }
1536
1537 private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) {
1538 final StringBuilder deqpCmdLine = new StringBuilder();
1539 if (!runConfig.getGlConfig().isEmpty()) {
1540 deqpCmdLine.append("--deqp-gl-config-name=");
1541 deqpCmdLine.append(runConfig.getGlConfig());
1542 }
1543 if (!runConfig.getRotation().isEmpty()) {
1544 if (deqpCmdLine.length() != 0) {
1545 deqpCmdLine.append(" ");
1546 }
1547 deqpCmdLine.append("--deqp-screen-rotation=");
1548 deqpCmdLine.append(runConfig.getRotation());
1549 }
1550 if (!runConfig.getSurfaceType().isEmpty()) {
1551 if (deqpCmdLine.length() != 0) {
1552 deqpCmdLine.append(" ");
1553 }
1554 deqpCmdLine.append("--deqp-surface-type=");
1555 deqpCmdLine.append(runConfig.getSurfaceType());
1556 }
1557 return deqpCmdLine.toString();
1558 }
1559
Jarkko Pöyryb5b4cfd2015-04-03 17:34:40 -07001560 private int getNumRemainingInstances() {
1561 int retVal = 0;
1562 for (TestIdentifier testId : mRemainingTests) {
1563 // If case is in current working set, sum only not yet executed instances.
1564 // If case is not in current working set, sum all instances (since they are not yet
1565 // executed).
1566 if (mInstanceListerner.mPendingResults.containsKey(testId)) {
1567 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size();
1568 } else {
1569 retVal += mTestInstances.get(testId).size();
1570 }
1571 }
1572 return retVal;
1573 }
1574
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001575 /**
Jarkko Pöyry35d54ee2015-05-12 15:28:58 -07001576 * Checks if this execution has been marked as interrupted and throws if it has.
1577 */
1578 private void checkInterrupted() throws RunInterruptedException {
1579 // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly
1580 // by sleeping a value <= 0.
1581 mRunUtil.sleep(0);
1582 }
1583
1584 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001585 * Pass given batch tests without running it
1586 */
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001587 private void fakePassTestRunBatch(TestBatch batch) {
1588 for (TestIdentifier test : batch.tests) {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001589 CLog.d("Skipping test '%s' invocation in config '%s'", test.toString(),
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001590 batch.config.getId());
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001591 mInstanceListerner.skipTest(test);
1592 }
1593 }
1594
1595 /**
1596 * Pass all remaining tests without running them
1597 */
1598 private void fakePassTests(ITestInvocationListener listener) {
1599 Map <String, String> emptyMap = Collections.emptyMap();
1600 for (TestIdentifier test : mRemainingTests) {
1601 CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString());
1602 listener.testStarted(test);
1603 listener.testEnded(test, emptyMap);
1604 }
1605 mRemainingTests.clear();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001606 }
1607
1608 /**
Mika Isojärvi69bd10f2014-09-10 13:55:05 +03001609 * Check if device supports OpenGL ES version.
1610 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001611 private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion,
1612 int requiredMinorVersion) throws DeviceNotAvailableException {
Mika Isojärvi69bd10f2014-09-10 13:55:05 +03001613 String roOpenglesVersion = device.getProperty("ro.opengles.version");
1614
1615 if (roOpenglesVersion == null)
1616 return false;
1617
1618 int intValue = Integer.parseInt(roOpenglesVersion);
1619
1620 int majorVersion = ((intValue & 0xffff0000) >> 16);
1621 int minorVersion = (intValue & 0xffff);
1622
1623 return (majorVersion > requiredMajorVersion)
1624 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion);
1625 }
1626
1627 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001628 * Query if rendertarget is supported
1629 */
1630 private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)
1631 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1632 // query if configuration is supported
1633 final StringBuilder configCommandLine =
1634 new StringBuilder(getRunConfigDisplayCmdLine(runConfig));
1635 if (configCommandLine.length() != 0) {
1636 configCommandLine.append(" ");
1637 }
1638 configCommandLine.append("--deqp-gl-major-version=");
1639 configCommandLine.append(getGlesMajorVersion());
1640 configCommandLine.append(" --deqp-gl-minor-version=");
1641 configCommandLine.append(getGlesMinorVersion());
1642
Jarkko Pöyry3327b9f2015-04-09 15:09:07 -07001643 final String commandLine = configCommandLine.toString();
1644
1645 // check for cached result first
1646 if (mConfigQuerySupportCache.containsKey(commandLine)) {
1647 return mConfigQuerySupportCache.get(commandLine);
1648 }
1649
1650 final boolean supported = queryIsSupportedConfigCommandLine(commandLine);
1651 mConfigQuerySupportCache.put(commandLine, supported);
1652 return supported;
1653 }
1654
1655 private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine)
1656 throws DeviceNotAvailableException, CapabilityQueryFailureException {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001657 final String instrumentationName =
1658 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation";
1659 final String command = String.format(
1660 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\""
1661 + " %s",
Jarkko Pöyry3327b9f2015-04-09 15:09:07 -07001662 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001663
1664 final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser();
1665 mDevice.executeShellCommand(command, parser);
1666 parser.flush();
1667
1668 if (parser.wasSuccessful() && parser.getResultCode() == 0 &&
1669 parser.getResultMap().containsKey("Supported")) {
1670 if ("Yes".equals(parser.getResultMap().get("Supported"))) {
1671 return true;
1672 } else if ("No".equals(parser.getResultMap().get("Supported"))) {
1673 return false;
1674 } else {
1675 CLog.e("Capability query did not return a result");
1676 throw new CapabilityQueryFailureException();
1677 }
1678 } else if (parser.wasSuccessful()) {
1679 CLog.e("Failed to run capability query. Code: %d, Result: %s",
1680 parser.getResultCode(), parser.getResultMap().toString());
1681 throw new CapabilityQueryFailureException();
1682 } else {
1683 CLog.e("Failed to run capability query");
1684 throw new CapabilityQueryFailureException();
1685 }
1686 }
1687
1688 /**
1689 * Return feature set supported by the device
1690 */
1691 private Set<String> getDeviceFeatures(ITestDevice device)
1692 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1693 if (mDeviceFeatures == null) {
1694 mDeviceFeatures = queryDeviceFeatures(device);
1695 }
1696 return mDeviceFeatures;
1697 }
1698
1699 /**
1700 * Query feature set supported by the device
1701 */
1702 private static Set<String> queryDeviceFeatures(ITestDevice device)
1703 throws DeviceNotAvailableException, CapabilityQueryFailureException {
1704 // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures
1705 // TODO: Move this logic to ITestDevice.
1706 String command = "pm list features";
1707 String commandOutput = device.executeShellCommand(command);
1708
1709 // Extract the id of the new user.
1710 HashSet<String> availableFeatures = new HashSet<>();
1711 for (String feature: commandOutput.split("\\s+")) {
1712 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
1713 String[] tokens = feature.split(":");
1714 if (tokens.length < 2 || !"feature".equals(tokens[0])) {
1715 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]);
1716 throw new CapabilityQueryFailureException();
1717 }
1718 availableFeatures.add(tokens[1]);
1719 }
1720 return availableFeatures;
1721 }
1722
1723 private boolean isPortraitClassRotation(String rotation) {
1724 return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) ||
1725 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation);
1726 }
1727
1728 private boolean isLandscapeClassRotation(String rotation) {
1729 return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) ||
1730 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation);
1731 }
1732
1733 /**
Jarkko Pöyrya4b51ad2014-10-01 19:12:41 -07001734 * Install dEQP OnDevice Package
1735 */
1736 private void installTestApk() throws DeviceNotAvailableException {
1737 try {
1738 File apkFile = mCtsBuild.getTestApp(DEQP_ONDEVICE_APK);
1739 String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
1740 String errorCode = getDevice().installPackage(apkFile, true, options);
1741 if (errorCode != null) {
1742 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode);
1743 }
1744 } catch (FileNotFoundException e) {
1745 CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK);
1746 }
1747 }
1748
1749 /**
1750 * Uninstall dEQP OnDevice Package
1751 */
1752 private void uninstallTestApk() throws DeviceNotAvailableException {
1753 getDevice().uninstallPackage(DEQP_ONDEVICE_PKG);
1754 }
1755
1756 /**
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001757 * Parse gl nature from package name
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001758 */
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001759 private boolean isOpenGlEsPackage() {
1760 if ("dEQP-GLES2".equals(mName) || "dEQP-GLES3".equals(mName) ||
1761 "dEQP-GLES31".equals(mName)) {
1762 return true;
1763 } else if ("dEQP-EGL".equals(mName)) {
1764 return false;
Mika Isojärvi69bd10f2014-09-10 13:55:05 +03001765 } else {
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001766 throw new IllegalStateException("dEQP runner was created with illegal name");
1767 }
1768 }
Mika Isojärvi69bd10f2014-09-10 13:55:05 +03001769
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001770 /**
1771 * Check GL support (based on package name)
1772 */
1773 private boolean isSupportedGles() throws DeviceNotAvailableException {
1774 return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion());
1775 }
Mika Isojärvi69bd10f2014-09-10 13:55:05 +03001776
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001777 /**
1778 * Get GL major version (based on package name)
1779 */
1780 private int getGlesMajorVersion() throws DeviceNotAvailableException {
1781 if ("dEQP-GLES2".equals(mName)) {
1782 return 2;
1783 } else if ("dEQP-GLES3".equals(mName)) {
1784 return 3;
1785 } else if ("dEQP-GLES31".equals(mName)) {
1786 return 3;
1787 } else {
1788 throw new IllegalStateException("getGlesMajorVersion called for non gles pkg");
1789 }
1790 }
1791
1792 /**
1793 * Get GL minor version (based on package name)
1794 */
1795 private int getGlesMinorVersion() throws DeviceNotAvailableException {
1796 if ("dEQP-GLES2".equals(mName)) {
1797 return 0;
1798 } else if ("dEQP-GLES3".equals(mName)) {
1799 return 0;
1800 } else if ("dEQP-GLES31".equals(mName)) {
1801 return 1;
1802 } else {
1803 throw new IllegalStateException("getGlesMinorVersion called for non gles pkg");
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001804 }
1805 }
1806
1807 /**
1808 * {@inheritDoc}
1809 */
1810 @Override
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001811 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
1812 final Map<String, String> emptyMap = Collections.emptyMap();
1813 final boolean isSupportedApi = !isOpenGlEsPackage() || isSupportedGles();
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001814
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001815 listener.testRunStarted(getId(), mRemainingTests.size());
1816
1817 try {
1818 if (isSupportedApi) {
1819 // Make sure there is no pre-existing package form earlier interrupted test run.
1820 uninstallTestApk();
1821 installTestApk();
1822
1823 mInstanceListerner.setSink(listener);
Jarkko Pöyry0b10cb32015-05-01 21:42:22 -07001824 mDeviceRecovery.setDevice(mDevice);
Jarkko Pöyry91d00e02015-01-15 18:02:15 -08001825 runTests();
1826
1827 uninstallTestApk();
1828 } else {
1829 // Pass all tests if OpenGL ES version is not supported
1830 fakePassTests(listener);
1831 }
1832 } catch (CapabilityQueryFailureException ex) {
1833 // Platform is not behaving correctly, for example crashing when trying to create
1834 // a window. Instead of silenty failing, signal failure by leaving the rest of the
1835 // test cases in "NotExecuted" state
1836 uninstallTestApk();
1837 }
1838
1839 listener.testRunEnded(0, emptyMap);
Mika Isojärvif1e23ee2014-08-25 13:17:58 +03001840 }
1841}