1 package com.android.cts.tradefed.testtype; 2 3 import com.android.compatibility.common.util.AbiUtils; 4 import com.android.cts.tradefed.build.CtsBuildHelper; 5 import com.android.ddmlib.AdbCommandRejectedException; 6 import com.android.ddmlib.IShellOutputReceiver; 7 import com.android.ddmlib.MultiLineReceiver; 8 import com.android.ddmlib.ShellCommandUnresponsiveException; 9 import com.android.ddmlib.TimeoutException; 10 import com.android.ddmlib.testrunner.TestIdentifier; 11 import com.android.tradefed.build.IBuildInfo; 12 import com.android.tradefed.device.DeviceNotAvailableException; 13 import com.android.tradefed.device.ITestDevice; 14 import com.android.tradefed.log.LogUtil.CLog; 15 import com.android.tradefed.result.ByteArrayInputStreamSource; 16 import com.android.tradefed.result.ITestInvocationListener; 17 import com.android.tradefed.result.LogDataType; 18 import com.android.tradefed.testtype.IAbi; 19 import com.android.tradefed.testtype.IBuildReceiver; 20 import com.android.tradefed.testtype.IDeviceTest; 21 import com.android.tradefed.testtype.IRemoteTest; 22 import com.android.tradefed.util.IRunUtil; 23 import com.android.tradefed.util.RunInterruptedException; 24 import com.android.tradefed.util.RunUtil; 25 26 import java.io.File; 27 import java.io.FileNotFoundException; 28 import java.io.IOException; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.InvocationTargetException; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.Iterator; 37 import java.util.LinkedHashMap; 38 import java.util.LinkedHashSet; 39 import java.util.LinkedList; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import java.util.concurrent.TimeUnit; 44 45 /** 46 * Test runner for dEQP tests 47 * 48 * Supports running drawElements Quality Program tests found under external/deqp. 49 */ 50 public 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"; 54 private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log"; 55 private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped"; 56 private static final String NOT_EXECUTABLE_LOG_MESSAGE = "Abort: Test cannot be executed"; 57 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"; 61 62 private static final int TESTCASE_BATCH_LIMIT = 1000; 63 private static final BatchRunConfiguration DEFAULT_CONFIG = 64 new BatchRunConfiguration("rgba8888d24s8", "unspecified", "window"); 65 66 private static final int UNRESPOSIVE_CMD_TIMEOUT_MS = 10*60*1000; // ten minutes 67 68 private final String mPackageName; 69 private final String mName; 70 private final Collection<TestIdentifier> mRemainingTests; 71 private final Map<TestIdentifier, Set<BatchRunConfiguration>> mTestInstances; 72 private final TestInstanceResultListener mInstanceListerner = new TestInstanceResultListener(); 73 private final Map<TestIdentifier, Integer> mTestInstabilityRatings; 74 private IAbi mAbi; 75 private CtsBuildHelper mCtsBuild; 76 private boolean mLogData = false; 77 private ITestDevice mDevice; 78 private Set<String> mDeviceFeatures; 79 private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>(); 80 private IRunUtil mRunUtil = RunUtil.getDefault(); 81 82 private IRecovery mDeviceRecovery = new Recovery(); 83 { mDeviceRecovery.setSleepProvider(new SleepProvider())84 mDeviceRecovery.setSleepProvider(new SleepProvider()); 85 } 86 DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests, Map<TestIdentifier, List<Map<String,String>>> testInstances)87 public DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests, 88 Map<TestIdentifier, List<Map<String,String>>> testInstances) { 89 mPackageName = packageName; 90 mName = name; 91 mRemainingTests = new LinkedList<>(tests); // avoid modifying arguments 92 mTestInstances = parseTestInstances(tests, testInstances); 93 mTestInstabilityRatings = new HashMap<>(); 94 } 95 96 /** 97 * @param abi the ABI to run the test on 98 */ setAbi(IAbi abi)99 public void setAbi(IAbi abi) { 100 mAbi = abi; 101 } 102 103 /** 104 * {@inheritDoc} 105 */ 106 @Override setBuild(IBuildInfo buildInfo)107 public void setBuild(IBuildInfo buildInfo) { 108 mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo); 109 } 110 111 /** 112 * Set the CTS build container. 113 * <p/> 114 * Exposed so unit tests can mock the provided build. 115 * 116 * @param buildHelper 117 */ setBuildHelper(CtsBuildHelper buildHelper)118 public void setBuildHelper(CtsBuildHelper buildHelper) { 119 mCtsBuild = buildHelper; 120 } 121 122 /** 123 * Enable or disable raw dEQP test log collection. 124 */ setCollectLogs(boolean logData)125 public void setCollectLogs(boolean logData) { 126 mLogData = logData; 127 } 128 129 /** 130 * {@inheritDoc} 131 */ 132 @Override setDevice(ITestDevice device)133 public void setDevice(ITestDevice device) { 134 mDevice = device; 135 } 136 137 /** 138 * {@inheritDoc} 139 */ 140 @Override getDevice()141 public ITestDevice getDevice() { 142 return mDevice; 143 } 144 145 /** 146 * Set recovery handler. 147 * 148 * Exposed for unit testing. 149 */ setRecovery(IRecovery deviceRecovery)150 public void setRecovery(IRecovery deviceRecovery) { 151 mDeviceRecovery = deviceRecovery; 152 } 153 154 /** 155 * Set IRunUtil. 156 * 157 * Exposed for unit testing. 158 */ setRunUtil(IRunUtil runUtil)159 public void setRunUtil(IRunUtil runUtil) { 160 mRunUtil = runUtil; 161 } 162 163 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 BatchRunConfiguration(String glConfig, String rotation, String surfaceType)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 */ getId()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 */ getGlConfig()198 public String getGlConfig() { 199 return mGlConfig; 200 } 201 202 /** 203 * Get the screen rotation used in this configuration. 204 */ getRotation()205 public String getRotation() { 206 return mRotation; 207 } 208 209 /** 210 * Get the surface type used in this configuration. 211 */ getSurfaceType()212 public String getSurfaceType() { 213 return mSurfaceType; 214 } 215 216 @Override equals(Object other)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 hashCode()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 boolean allInstancesPassed; 246 Map<BatchRunConfiguration, String> testLogs; 247 Map<BatchRunConfiguration, String> errorMessages; 248 Set<BatchRunConfiguration> remainingConfigs; 249 } 250 251 private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>(); 252 setSink(ITestInvocationListener sink)253 public void setSink(ITestInvocationListener sink) { 254 mSink = sink; 255 } 256 setCurrentConfig(BatchRunConfiguration runConfig)257 public void setCurrentConfig(BatchRunConfiguration runConfig) { 258 mRunConfig = runConfig; 259 } 260 261 /** 262 * Get currently processed test id, or null if not currently processing a test case 263 */ getCurrentTestId()264 public TestIdentifier getCurrentTestId() { 265 return mCurrentTestId; 266 } 267 268 /** 269 * Forward result to sink 270 */ forwardFinalizedPendingResult(TestIdentifier testId)271 private void forwardFinalizedPendingResult(TestIdentifier testId) { 272 if (mRemainingTests.contains(testId)) { 273 final PendingResult result = mPendingResults.get(testId); 274 275 mPendingResults.remove(testId); 276 mRemainingTests.remove(testId); 277 278 // Forward results to the sink 279 mSink.testStarted(testId); 280 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 288 mSink.testLog(testId.getClassName() + "." + testId.getTestName() + "@" 289 + entry.getKey().getId(), LogDataType.XML, source); 290 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 309 mSink.testFailed(testId, errorLog.toString()); 310 } 311 312 final Map<String, String> emptyMap = Collections.emptyMap(); 313 mSink.testEnded(testId, emptyMap); 314 } else { 315 CLog.w("Finalization for non-pending case %s", testId); 316 } 317 } 318 319 /** 320 * Declare existence of a test and instances 321 */ setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs)322 public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) { 323 // Test instances cannot change at runtime, ignore if we have already set this 324 if (!mPendingResults.containsKey(testId)) { 325 final PendingResult pendingResult = new PendingResult(); 326 pendingResult.allInstancesPassed = true; 327 pendingResult.testLogs = new LinkedHashMap<>(); 328 pendingResult.errorMessages = new LinkedHashMap<>(); 329 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument 330 mPendingResults.put(testId, pendingResult); 331 } 332 } 333 334 /** 335 * Query if test instance has not yet been executed 336 */ isPendingTestInstance(TestIdentifier testId, BatchRunConfiguration config)337 public boolean isPendingTestInstance(TestIdentifier testId, 338 BatchRunConfiguration config) { 339 final PendingResult result = mPendingResults.get(testId); 340 if (result == null) { 341 // test is not in the current working batch of the runner, i.e. it cannot be 342 // "partially" completed. 343 if (!mRemainingTests.contains(testId)) { 344 // The test has been fully executed. Not pending. 345 return false; 346 } else { 347 // Test has not yet been executed. Check if such instance exists 348 return mTestInstances.get(testId).contains(config); 349 } 350 } else { 351 // could be partially completed, check this particular config 352 return result.remainingConfigs.contains(config); 353 } 354 } 355 356 /** 357 * Fake execution of an instance with current config 358 */ skipTest(TestIdentifier testId)359 public void skipTest(TestIdentifier testId) { 360 final PendingResult result = mPendingResults.get(testId); 361 362 result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE); 363 result.remainingConfigs.remove(mRunConfig); 364 365 // Pending result finished, report result 366 if (result.remainingConfigs.isEmpty()) { 367 forwardFinalizedPendingResult(testId); 368 } 369 } 370 371 /** 372 * Fake failure of an instance with current config 373 */ abortTest(TestIdentifier testId, String errorMessage)374 public void abortTest(TestIdentifier testId, String errorMessage) { 375 final PendingResult result = mPendingResults.get(testId); 376 377 CLog.i("Test %s aborted with message %s", testId, errorMessage); 378 379 // Mark as executed 380 result.allInstancesPassed = false; 381 result.errorMessages.put(mRunConfig, errorMessage); 382 result.remainingConfigs.remove(mRunConfig); 383 384 // Pending result finished, report result 385 if (result.remainingConfigs.isEmpty()) { 386 forwardFinalizedPendingResult(testId); 387 } 388 389 if (testId.equals(mCurrentTestId)) { 390 mCurrentTestId = null; 391 } 392 } 393 394 /** 395 * Handles beginning of dEQP session. 396 */ handleBeginSession(Map<String, String> values)397 private boolean handleBeginSession(Map<String, String> values) { 398 // ignore 399 return true; 400 } 401 402 /** 403 * Handle session info 404 */ handleSessionInfo(Map<String, String> values)405 private boolean handleSessionInfo(Map<String, String> values) { 406 // ignore 407 return true; 408 } 409 410 /** 411 * Handles end of dEQP session. 412 */ handleEndSession(Map<String, String> values)413 private boolean handleEndSession(Map<String, String> values) { 414 // ignore 415 return true; 416 } 417 418 /** 419 * Handles beginning of dEQP testcase. 420 */ handleBeginTestCase(Map<String, String> values)421 private boolean handleBeginTestCase(Map<String, String> values) { 422 String casePath = values.get("dEQP-BeginTestCase-TestCasePath"); 423 424 if (mCurrentTestId != null) { 425 CLog.w("Got unexpected start of %s, so aborting", mCurrentTestId); 426 abortTest(mCurrentTestId, INCOMPLETE_LOG_MESSAGE); 427 mCurrentTestId = null; 428 } 429 430 mCurrentTestLog = ""; 431 mGotTestResult = false; 432 433 if (casePath == null) { 434 CLog.w("Got null case path for test case begin event. Current test ID: %s", mCurrentTestId); 435 mCurrentTestId = null; 436 return false; 437 } 438 439 mCurrentTestId = pathToIdentifier(casePath); 440 441 if (mPendingResults.get(mCurrentTestId) == null) { 442 CLog.w("Got unexpected start of %s", mCurrentTestId); 443 } 444 return true; 445 } 446 447 /** 448 * Handles end of dEQP testcase. 449 */ handleEndTestCase(Map<String, String> values)450 private boolean handleEndTestCase(Map<String, String> values) { 451 final PendingResult result = mPendingResults.get(mCurrentTestId); 452 453 if (result != null) { 454 if (!mGotTestResult) { 455 result.allInstancesPassed = false; 456 result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE); 457 CLog.i("Test %s failed as it ended before receiving result.", mCurrentTestId); 458 } 459 result.remainingConfigs.remove(mRunConfig); 460 461 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) { 462 result.testLogs.put(mRunConfig, mCurrentTestLog); 463 } 464 465 // Pending result finished, report result 466 if (result.remainingConfigs.isEmpty()) { 467 forwardFinalizedPendingResult(mCurrentTestId); 468 } 469 } else { 470 CLog.w("Got unexpected end of %s", mCurrentTestId); 471 } 472 mCurrentTestId = null; 473 return true; 474 } 475 476 /** 477 * Handles dEQP testcase result. 478 */ handleTestCaseResult(Map<String, String> values)479 private boolean handleTestCaseResult(Map<String, String> values) { 480 String code = values.get("dEQP-TestCaseResult-Code"); 481 if (code == null) { 482 return false; 483 } 484 485 String details = values.get("dEQP-TestCaseResult-Details"); 486 487 if (mPendingResults.get(mCurrentTestId) == null) { 488 CLog.w("Got unexpected result for %s", mCurrentTestId); 489 mGotTestResult = true; 490 return true; 491 } 492 493 if (code.compareTo("Pass") == 0) { 494 mGotTestResult = true; 495 } else if (code.compareTo("NotSupported") == 0) { 496 mGotTestResult = true; 497 } else if (code.compareTo("QualityWarning") == 0) { 498 mGotTestResult = true; 499 } else if (code.compareTo("CompatibilityWarning") == 0) { 500 mGotTestResult = true; 501 } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0 502 || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0 503 || code.compareTo("Timeout") == 0) { 504 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 505 mPendingResults.get(mCurrentTestId) 506 .errorMessages.put(mRunConfig, code + ": " + details); 507 mGotTestResult = true; 508 } else { 509 String codeError = "Unknown result code: " + code; 510 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 511 mPendingResults.get(mCurrentTestId) 512 .errorMessages.put(mRunConfig, codeError + ": " + details); 513 mGotTestResult = true; 514 CLog.e("Got invalid result code '%s' for test %s", code, mCurrentTestId); 515 } 516 return true; 517 } 518 519 /** 520 * Handles terminated dEQP testcase. 521 */ handleTestCaseTerminate(Map<String, String> values)522 private boolean handleTestCaseTerminate(Map<String, String> values) { 523 final PendingResult result = mPendingResults.get(mCurrentTestId); 524 525 if (result != null) { 526 String reason = values.get("dEQP-TerminateTestCase-Reason"); 527 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 528 mPendingResults.get(mCurrentTestId) 529 .errorMessages.put(mRunConfig, "Terminated: " + reason); 530 result.remainingConfigs.remove(mRunConfig); 531 532 // Pending result finished, report result 533 if (result.remainingConfigs.isEmpty()) { 534 forwardFinalizedPendingResult(mCurrentTestId); 535 } 536 } else { 537 CLog.w("Got unexpected termination of %s", mCurrentTestId); 538 } 539 540 mCurrentTestId = null; 541 mGotTestResult = true; 542 return true; 543 } 544 545 /** 546 * Handles dEQP testlog data. 547 */ handleTestLogData(Map<String, String> values)548 private boolean handleTestLogData(Map<String, String> values) { 549 String newLog = values.get("dEQP-TestLogData-Log"); 550 if (newLog == null) { 551 return false; 552 } 553 mCurrentTestLog = mCurrentTestLog + newLog; 554 return true; 555 } 556 557 /** 558 * Handles new instrumentation status message. 559 * @return true if handled correctly, false if missing values. 560 */ handleStatus(Map<String, String> values)561 public boolean handleStatus(Map<String, String> values) { 562 String eventType = values.get("dEQP-EventType"); 563 564 if (eventType == null) { 565 // Not an event, but some other line 566 return true; 567 } 568 569 if (eventType.compareTo("BeginSession") == 0) { 570 return handleBeginSession(values); 571 } else if (eventType.compareTo("SessionInfo") == 0) { 572 return handleSessionInfo(values); 573 } else if (eventType.compareTo("EndSession") == 0) { 574 return handleEndSession(values); 575 } else if (eventType.compareTo("BeginTestCase") == 0) { 576 return handleBeginTestCase(values); 577 } else if (eventType.compareTo("EndTestCase") == 0) { 578 return handleEndTestCase(values); 579 } else if (eventType.compareTo("TestCaseResult") == 0) { 580 return handleTestCaseResult(values); 581 } else if (eventType.compareTo("TerminateTestCase") == 0) { 582 return handleTestCaseTerminate(values); 583 } else if (eventType.compareTo("TestLogData") == 0) { 584 return handleTestLogData(values); 585 } 586 CLog.e("Unknown event type (%s)", eventType); 587 return false; 588 } 589 590 /** 591 * Signal listener that batch ended and forget incomplete results. 592 */ endBatch()593 public void endBatch() { 594 // end open test if when stream ends 595 if (mCurrentTestId != null) { 596 // Current instance was removed from remainingConfigs when case 597 // started. Mark current instance as pending. 598 CLog.i("Batch ended with test '%s' current", mCurrentTestId); 599 if (mPendingResults.get(mCurrentTestId) != null) { 600 mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig); 601 } else { 602 CLog.w("Got unexpected internal state of %s", mCurrentTestId); 603 } 604 } 605 mCurrentTestId = null; 606 } 607 } 608 609 /** 610 * dEQP instrumentation parser 611 */ 612 private static class InstrumentationParser extends MultiLineReceiver { 613 private TestInstanceResultListener mListener; 614 615 private Map<String, String> mValues; 616 private String mCurrentName; 617 private String mCurrentValue; 618 private int mResultCode; 619 private boolean mGotExitValue = false; 620 private boolean mParseSuccessful = true; 621 622 InstrumentationParser(TestInstanceResultListener listener)623 public InstrumentationParser(TestInstanceResultListener listener) { 624 mListener = listener; 625 } 626 627 /** 628 * {@inheritDoc} 629 */ 630 @Override processNewLines(String[] lines)631 public void processNewLines(String[] lines) { 632 for (String line : lines) { 633 if (mValues == null) mValues = new HashMap<String, String>(); 634 635 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) { 636 if (mCurrentName != null) { 637 mValues.put(mCurrentName, mCurrentValue); 638 639 mCurrentName = null; 640 mCurrentValue = null; 641 } 642 643 mParseSuccessful &= mListener.handleStatus(mValues); 644 mValues = null; 645 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) { 646 if (mCurrentName != null) { 647 mValues.put(mCurrentName, mCurrentValue); 648 649 mCurrentValue = null; 650 mCurrentName = null; 651 } 652 653 String prefix = "INSTRUMENTATION_STATUS: "; 654 int nameBegin = prefix.length(); 655 int nameEnd = line.indexOf('='); 656 if (nameEnd < 0) { 657 CLog.e("Line does not contain value. Logcat interrupted? (%s)", line); 658 mCurrentValue = null; 659 mCurrentName = null; 660 mParseSuccessful = false; 661 return; 662 } else { 663 int valueBegin = nameEnd + 1; 664 mCurrentName = line.substring(nameBegin, nameEnd); 665 mCurrentValue = line.substring(valueBegin); 666 } 667 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) { 668 try { 669 mResultCode = Integer.parseInt(line.substring(22)); 670 mGotExitValue = true; 671 } catch (NumberFormatException ex) { 672 CLog.e("Instrumentation code format unexpected"); 673 mParseSuccessful = false; 674 return; 675 } 676 } else if (mCurrentValue != null) { 677 mCurrentValue = mCurrentValue + line; 678 } 679 } 680 } 681 682 /** 683 * {@inheritDoc} 684 */ 685 @Override done()686 public void done() { 687 if (mCurrentName != null) { 688 mValues.put(mCurrentName, mCurrentValue); 689 690 mCurrentName = null; 691 mCurrentValue = null; 692 } 693 694 if (mValues != null) { 695 mParseSuccessful &= mListener.handleStatus(mValues); 696 mValues = null; 697 } 698 } 699 700 /** 701 * {@inheritDoc} 702 */ 703 @Override isCancelled()704 public boolean isCancelled() { 705 return false; 706 } 707 708 /** 709 * Returns whether target instrumentation exited normally. 710 */ wasSuccessful()711 public boolean wasSuccessful() { 712 return mGotExitValue && mParseSuccessful; 713 } 714 715 /** 716 * Returns Instrumentation return code 717 */ getResultCode()718 public int getResultCode() { 719 return mResultCode; 720 } 721 } 722 723 /** 724 * dEQP platfom query instrumentation parser 725 */ 726 private static class PlatformQueryInstrumentationParser extends MultiLineReceiver { 727 private Map<String,String> mResultMap = new LinkedHashMap<>(); 728 private int mResultCode; 729 private boolean mGotExitValue = false; 730 731 /** 732 * {@inheritDoc} 733 */ 734 @Override processNewLines(String[] lines)735 public void processNewLines(String[] lines) { 736 for (String line : lines) { 737 if (line.startsWith("INSTRUMENTATION_RESULT: ")) { 738 final String parts[] = line.substring(24).split("=",2); 739 if (parts.length == 2) { 740 mResultMap.put(parts[0], parts[1]); 741 } else { 742 CLog.w("Instrumentation status format unexpected"); 743 } 744 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) { 745 try { 746 mResultCode = Integer.parseInt(line.substring(22)); 747 mGotExitValue = true; 748 } catch (NumberFormatException ex) { 749 CLog.w("Instrumentation code format unexpected"); 750 } 751 } 752 } 753 } 754 755 /** 756 * {@inheritDoc} 757 */ 758 @Override isCancelled()759 public boolean isCancelled() { 760 return false; 761 } 762 763 /** 764 * Returns whether target instrumentation exited normally. 765 */ wasSuccessful()766 public boolean wasSuccessful() { 767 return mGotExitValue; 768 } 769 770 /** 771 * Returns Instrumentation return code 772 */ getResultCode()773 public int getResultCode() { 774 return mResultCode; 775 } 776 getResultMap()777 public Map<String,String> getResultMap() { 778 return mResultMap; 779 } 780 } 781 782 /** 783 * Interface for sleeping. 784 * 785 * Exposed for unit testing 786 */ 787 public static interface ISleepProvider { sleep(int milliseconds)788 public void sleep(int milliseconds); 789 } 790 791 private static class SleepProvider implements ISleepProvider { sleep(int milliseconds)792 public void sleep(int milliseconds) { 793 try { 794 Thread.sleep(milliseconds); 795 } catch (InterruptedException ex) { 796 } 797 } 798 } 799 800 /** 801 * Interface for failure recovery. 802 * 803 * Exposed for unit testing 804 */ 805 public static interface IRecovery { 806 /** 807 * Sets the sleep provider IRecovery works on 808 */ setSleepProvider(ISleepProvider sleepProvider)809 public void setSleepProvider(ISleepProvider sleepProvider); 810 811 /** 812 * Sets the device IRecovery works on 813 */ setDevice(ITestDevice device)814 public void setDevice(ITestDevice device); 815 816 /** 817 * Informs Recovery that test execution has progressed since the last recovery 818 */ onExecutionProgressed()819 public void onExecutionProgressed(); 820 821 /** 822 * Tries to recover device after failed refused connection. 823 * 824 * @throws DeviceNotAvailableException if recovery did not succeed 825 */ recoverConnectionRefused()826 public void recoverConnectionRefused() throws DeviceNotAvailableException; 827 828 /** 829 * Tries to recover device after abnormal execution termination or link failure. 830 * 831 * @param progressedSinceLastCall true if test execution has progressed since last call 832 * @throws DeviceNotAvailableException if recovery did not succeed 833 */ recoverComLinkKilled()834 public void recoverComLinkKilled() throws DeviceNotAvailableException; 835 }; 836 837 /** 838 * State machine for execution failure recovery. 839 * 840 * Exposed for unit testing 841 */ 842 public static class Recovery implements IRecovery { 843 private int RETRY_COOLDOWN_MS = 6000; // 6 seconds 844 private int PROCESS_KILL_WAIT_MS = 1000; // 1 second 845 846 private static enum MachineState { 847 WAIT, // recover by waiting 848 RECOVER, // recover by calling recover() 849 REBOOT, // recover by rebooting 850 FAIL, // cannot recover 851 }; 852 853 private MachineState mState = MachineState.WAIT; 854 private ITestDevice mDevice; 855 private ISleepProvider mSleepProvider; 856 857 private static class ProcessKillFailureException extends Exception { 858 } 859 860 /** 861 * {@inheritDoc} 862 */ setSleepProvider(ISleepProvider sleepProvider)863 public void setSleepProvider(ISleepProvider sleepProvider) { 864 mSleepProvider = sleepProvider; 865 } 866 867 /** 868 * {@inheritDoc} 869 */ 870 @Override setDevice(ITestDevice device)871 public void setDevice(ITestDevice device) { 872 mDevice = device; 873 } 874 875 /** 876 * {@inheritDoc} 877 */ 878 @Override onExecutionProgressed()879 public void onExecutionProgressed() { 880 mState = MachineState.WAIT; 881 } 882 883 /** 884 * {@inheritDoc} 885 */ 886 @Override recoverConnectionRefused()887 public void recoverConnectionRefused() throws DeviceNotAvailableException { 888 switch (mState) { 889 case WAIT: // not a valid stratedy for connection refusal, fallthrough 890 case RECOVER: 891 // First failure, just try to recover 892 CLog.w("ADB connection failed, trying to recover"); 893 mState = MachineState.REBOOT; // the next step is to reboot 894 895 try { 896 recoverDevice(); 897 } catch (DeviceNotAvailableException ex) { 898 // chain forward 899 recoverConnectionRefused(); 900 } 901 break; 902 903 case REBOOT: 904 // Second failure in a row, try to reboot 905 CLog.w("ADB connection failed after recovery, rebooting device"); 906 mState = MachineState.FAIL; // the next step is to fail 907 908 try { 909 rebootDevice(); 910 } catch (DeviceNotAvailableException ex) { 911 // chain forward 912 recoverConnectionRefused(); 913 } 914 break; 915 916 case FAIL: 917 // Third failure in a row, just fail 918 CLog.w("Cannot recover ADB connection"); 919 throw new DeviceNotAvailableException("failed to connect after reboot"); 920 } 921 } 922 923 /** 924 * {@inheritDoc} 925 */ 926 @Override recoverComLinkKilled()927 public void recoverComLinkKilled() throws DeviceNotAvailableException { 928 switch (mState) { 929 case WAIT: 930 // First failure, just try to wait and try again 931 CLog.w("ADB link failed, retrying after a cooldown period"); 932 mState = MachineState.RECOVER; // the next step is to recover the device 933 934 waitCooldown(); 935 936 // even if the link to deqp on-device process was killed, the process might 937 // still be alive. Locate and terminate such unwanted processes. 938 try { 939 killDeqpProcess(); 940 } catch (DeviceNotAvailableException ex) { 941 // chain forward 942 recoverComLinkKilled(); 943 } catch (ProcessKillFailureException ex) { 944 // chain forward 945 recoverComLinkKilled(); 946 } 947 break; 948 949 case RECOVER: 950 // Second failure, just try to recover 951 CLog.w("ADB link failed, trying to recover"); 952 mState = MachineState.REBOOT; // the next step is to reboot 953 954 try { 955 recoverDevice(); 956 killDeqpProcess(); 957 } catch (DeviceNotAvailableException ex) { 958 // chain forward 959 recoverComLinkKilled(); 960 } catch (ProcessKillFailureException ex) { 961 // chain forward 962 recoverComLinkKilled(); 963 } 964 break; 965 966 case REBOOT: 967 // Third failure in a row, try to reboot 968 CLog.w("ADB link failed after recovery, rebooting device"); 969 mState = MachineState.FAIL; // the next step is to fail 970 971 try { 972 rebootDevice(); 973 } catch (DeviceNotAvailableException ex) { 974 // chain forward 975 recoverComLinkKilled(); 976 } 977 break; 978 979 case FAIL: 980 // Fourth failure in a row, just fail 981 CLog.w("Cannot recover ADB connection"); 982 throw new DeviceNotAvailableException("link killed after reboot"); 983 } 984 } 985 waitCooldown()986 private void waitCooldown() { 987 mSleepProvider.sleep(RETRY_COOLDOWN_MS); 988 } 989 getDeqpProcessPids()990 private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException { 991 final List<Integer> pids = new ArrayList<Integer>(2); 992 final String processes = mDevice.executeShellCommand("ps | grep com.drawelements"); 993 final String[] lines = processes.split("(\\r|\\n)+"); 994 for (String line : lines) { 995 final String[] fields = line.split("\\s+"); 996 if (fields.length < 2) { 997 continue; 998 } 999 1000 try { 1001 final int processId = Integer.parseInt(fields[1], 10); 1002 pids.add(processId); 1003 } catch (NumberFormatException ex) { 1004 continue; 1005 } 1006 } 1007 return pids; 1008 } 1009 killDeqpProcess()1010 private void killDeqpProcess() throws DeviceNotAvailableException, 1011 ProcessKillFailureException { 1012 for (Integer processId : getDeqpProcessPids()) { 1013 CLog.i("Killing deqp device process with ID %d", processId); 1014 mDevice.executeShellCommand(String.format("kill -9 %d", processId)); 1015 } 1016 1017 mSleepProvider.sleep(PROCESS_KILL_WAIT_MS); 1018 1019 // check that processes actually died 1020 if (getDeqpProcessPids().iterator().hasNext()) { 1021 // a process is still alive, killing failed 1022 CLog.w("Failed to kill all deqp processes on device"); 1023 throw new ProcessKillFailureException(); 1024 } 1025 } 1026 recoverDevice()1027 public void recoverDevice() throws DeviceNotAvailableException { 1028 // Work around the API. We need to call recoverDevice() on the test device and 1029 // we know that mDevice is a TestDevice. However even though the recoverDevice() 1030 // method is public suggesting it should be publicly accessible, the class itself 1031 // and its super-interface (IManagedTestDevice) are package-private. 1032 final Method recoverDeviceMethod; 1033 try { 1034 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice"); 1035 recoverDeviceMethod.setAccessible(true); 1036 } catch (NoSuchMethodException ex) { 1037 throw new AssertionError("Test device must have recoverDevice()"); 1038 } 1039 1040 try { 1041 recoverDeviceMethod.invoke(mDevice); 1042 } catch (InvocationTargetException ex) { 1043 if (ex.getCause() instanceof DeviceNotAvailableException) { 1044 throw (DeviceNotAvailableException)ex.getCause(); 1045 } else if (ex.getCause() instanceof RuntimeException) { 1046 throw (RuntimeException)ex.getCause(); 1047 } else { 1048 throw new AssertionError("unexpected throw", ex); 1049 } 1050 } catch (IllegalAccessException ex) { 1051 throw new AssertionError("unexpected throw", ex); 1052 } 1053 } 1054 rebootDevice()1055 private void rebootDevice() throws DeviceNotAvailableException { 1056 mDevice.reboot(); 1057 } 1058 } 1059 1060 /** 1061 * Parse map of instance arguments to map of BatchRunConfigurations 1062 */ parseTestInstances( Collection<TestIdentifier> tests, Map<TestIdentifier, List<Map<String,String>>> testInstances)1063 private static Map<TestIdentifier, Set<BatchRunConfiguration>> parseTestInstances( 1064 Collection<TestIdentifier> tests, 1065 Map<TestIdentifier, List<Map<String,String>>> testInstances) { 1066 final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new HashMap<>(); 1067 for (final TestIdentifier test : tests) { 1068 final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>(); 1069 if (testInstances.get(test).isEmpty()) { 1070 // no instances defined, use default 1071 testInstanceSet.add(DEFAULT_CONFIG); 1072 } else { 1073 for (Map<String, String> instanceArgs : testInstances.get(test)) { 1074 testInstanceSet.add(parseRunConfig(instanceArgs)); 1075 } 1076 } 1077 instances.put(test, testInstanceSet); 1078 } 1079 return instances; 1080 } 1081 parseRunConfig(Map<String,String> instanceArguments)1082 private static BatchRunConfiguration parseRunConfig(Map<String,String> instanceArguments) { 1083 final String glConfig; 1084 final String rotation; 1085 final String surfaceType; 1086 1087 if (instanceArguments.containsKey("glconfig")) { 1088 glConfig = instanceArguments.get("glconfig"); 1089 } else { 1090 glConfig = DEFAULT_CONFIG.getGlConfig(); 1091 } 1092 if (instanceArguments.containsKey("rotation")) { 1093 rotation = instanceArguments.get("rotation"); 1094 } else { 1095 rotation = DEFAULT_CONFIG.getRotation(); 1096 } 1097 if (instanceArguments.containsKey("surfaceType")) { 1098 surfaceType = instanceArguments.get("surfaceType"); 1099 } else { 1100 surfaceType = DEFAULT_CONFIG.getSurfaceType(); 1101 } 1102 1103 return new BatchRunConfiguration(glConfig, rotation, surfaceType); 1104 } 1105 getTestRunConfigs(TestIdentifier testId)1106 private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) { 1107 return mTestInstances.get(testId); 1108 } 1109 1110 /** 1111 * Converts dEQP testcase path to TestIdentifier. 1112 */ pathToIdentifier(String testPath)1113 private static TestIdentifier pathToIdentifier(String testPath) { 1114 String[] components = testPath.split("\\."); 1115 String name = components[components.length - 1]; 1116 String className = null; 1117 1118 for (int i = 0; i < components.length - 1; i++) { 1119 if (className == null) { 1120 className = components[i]; 1121 } else { 1122 className = className + "." + components[i]; 1123 } 1124 } 1125 1126 return new TestIdentifier(className, name); 1127 } 1128 getId()1129 private String getId() { 1130 return AbiUtils.createId(mAbi.getName(), mPackageName); 1131 } 1132 1133 /** 1134 * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute. 1135 */ generateTestCaseTrieFromPaths(Collection<String> tests)1136 private static String generateTestCaseTrieFromPaths(Collection<String> tests) { 1137 String result = "{"; 1138 boolean first = true; 1139 1140 // Add testcases to results 1141 for (Iterator<String> iter = tests.iterator(); iter.hasNext();) { 1142 String test = iter.next(); 1143 String[] components = test.split("\\."); 1144 1145 if (components.length == 1) { 1146 if (!first) { 1147 result = result + ","; 1148 } 1149 first = false; 1150 1151 result += components[0]; 1152 iter.remove(); 1153 } 1154 } 1155 1156 if (!tests.isEmpty()) { 1157 HashMap<String, ArrayList<String> > testGroups = new HashMap<>(); 1158 1159 // Collect all sub testgroups 1160 for (String test : tests) { 1161 String[] components = test.split("\\."); 1162 ArrayList<String> testGroup = testGroups.get(components[0]); 1163 1164 if (testGroup == null) { 1165 testGroup = new ArrayList<String>(); 1166 testGroups.put(components[0], testGroup); 1167 } 1168 1169 testGroup.add(test.substring(components[0].length()+1)); 1170 } 1171 1172 for (String testGroup : testGroups.keySet()) { 1173 if (!first) { 1174 result = result + ","; 1175 } 1176 1177 first = false; 1178 result = result + testGroup 1179 + generateTestCaseTrieFromPaths(testGroups.get(testGroup)); 1180 } 1181 } 1182 1183 return result + "}"; 1184 } 1185 1186 /** 1187 * Generates testcase trie from TestIdentifiers. 1188 */ generateTestCaseTrie(Collection<TestIdentifier> tests)1189 private static String generateTestCaseTrie(Collection<TestIdentifier> tests) { 1190 ArrayList<String> testPaths = new ArrayList<String>(); 1191 1192 for (TestIdentifier test : tests) { 1193 testPaths.add(test.getClassName() + "." + test.getTestName()); 1194 } 1195 1196 return generateTestCaseTrieFromPaths(testPaths); 1197 } 1198 1199 private static class TestBatch { 1200 public BatchRunConfiguration config; 1201 public List<TestIdentifier> tests; 1202 } 1203 selectRunBatch()1204 private TestBatch selectRunBatch() { 1205 return selectRunBatch(mRemainingTests, null); 1206 } 1207 1208 /** 1209 * Creates a TestBatch from the given tests or null if not tests remaining. 1210 * 1211 * @param pool List of tests to select from 1212 * @param requiredConfig Select only instances with pending requiredConfig, or null to select 1213 * any run configuration. 1214 */ selectRunBatch(Collection<TestIdentifier> pool, BatchRunConfiguration requiredConfig)1215 private TestBatch selectRunBatch(Collection<TestIdentifier> pool, 1216 BatchRunConfiguration requiredConfig) { 1217 // select one test (leading test) that is going to be executed and then pack along as many 1218 // other compatible instances as possible. 1219 1220 TestIdentifier leadingTest = null; 1221 for (TestIdentifier test : pool) { 1222 if (!mRemainingTests.contains(test)) { 1223 continue; 1224 } 1225 if (requiredConfig != null && 1226 !mInstanceListerner.isPendingTestInstance(test, requiredConfig)) { 1227 continue; 1228 } 1229 leadingTest = test; 1230 break; 1231 } 1232 1233 // no remaining tests? 1234 if (leadingTest == null) { 1235 return null; 1236 } 1237 1238 BatchRunConfiguration leadingTestConfig = null; 1239 if (requiredConfig != null) { 1240 leadingTestConfig = requiredConfig; 1241 } else { 1242 for (BatchRunConfiguration runConfig : getTestRunConfigs(leadingTest)) { 1243 if (mInstanceListerner.isPendingTestInstance(leadingTest, runConfig)) { 1244 leadingTestConfig = runConfig; 1245 break; 1246 } 1247 } 1248 } 1249 1250 // test pending <=> test has a pending config 1251 if (leadingTestConfig == null) { 1252 throw new AssertionError("search postcondition failed"); 1253 } 1254 1255 final int leadingInstability = getTestInstabilityRating(leadingTest); 1256 1257 final TestBatch runBatch = new TestBatch(); 1258 runBatch.config = leadingTestConfig; 1259 runBatch.tests = new ArrayList<>(); 1260 runBatch.tests.add(leadingTest); 1261 1262 for (TestIdentifier test : pool) { 1263 if (test == leadingTest) { 1264 // do not re-select the leading tests 1265 continue; 1266 } 1267 if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) { 1268 // select only compatible 1269 continue; 1270 } 1271 if (getTestInstabilityRating(test) != leadingInstability) { 1272 // pack along only cases in the same stability category. Packing more dangerous 1273 // tests along jeopardizes the stability of this run. Packing more stable tests 1274 // along jeopardizes their stability rating. 1275 continue; 1276 } 1277 if (runBatch.tests.size() >= getBatchSizeLimitForInstability(leadingInstability)) { 1278 // batch size is limited. 1279 break; 1280 } 1281 runBatch.tests.add(test); 1282 } 1283 1284 return runBatch; 1285 } 1286 getBatchNumPendingCases(TestBatch batch)1287 private int getBatchNumPendingCases(TestBatch batch) { 1288 int numPending = 0; 1289 for (TestIdentifier test : batch.tests) { 1290 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) { 1291 ++numPending; 1292 } 1293 } 1294 return numPending; 1295 } 1296 getBatchSizeLimitForInstability(int batchInstabilityRating)1297 private int getBatchSizeLimitForInstability(int batchInstabilityRating) { 1298 // reduce group size exponentially down to one 1299 return Math.max(1, TESTCASE_BATCH_LIMIT / (1 << batchInstabilityRating)); 1300 } 1301 getTestInstabilityRating(TestIdentifier testId)1302 private int getTestInstabilityRating(TestIdentifier testId) { 1303 if (mTestInstabilityRatings.containsKey(testId)) { 1304 return mTestInstabilityRatings.get(testId); 1305 } else { 1306 return 0; 1307 } 1308 } 1309 recordTestInstability(TestIdentifier testId)1310 private void recordTestInstability(TestIdentifier testId) { 1311 mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1); 1312 } 1313 clearTestInstability(TestIdentifier testId)1314 private void clearTestInstability(TestIdentifier testId) { 1315 mTestInstabilityRatings.put(testId, 0); 1316 } 1317 1318 /** 1319 * Executes all tests on the device. 1320 */ runTests()1321 private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException { 1322 for (;;) { 1323 TestBatch batch = selectRunBatch(); 1324 1325 if (batch == null) { 1326 break; 1327 } 1328 1329 runTestRunBatch(batch); 1330 } 1331 } 1332 1333 /** 1334 * Runs a TestBatch by either faking it or executing it on a device. 1335 */ runTestRunBatch(TestBatch batch)1336 private void runTestRunBatch(TestBatch batch) throws DeviceNotAvailableException, 1337 CapabilityQueryFailureException { 1338 // prepare instance listener 1339 mInstanceListerner.setCurrentConfig(batch.config); 1340 for (TestIdentifier test : batch.tests) { 1341 mInstanceListerner.setTestInstances(test, getTestRunConfigs(test)); 1342 } 1343 1344 // execute only if config is executable, else fake results 1345 if (isSupportedRunConfiguration(batch.config)) { 1346 executeTestRunBatch(batch); 1347 } else { 1348 fakePassTestRunBatch(batch); 1349 } 1350 } 1351 isSupportedRunConfiguration(BatchRunConfiguration runConfig)1352 private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig) 1353 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1354 // orientation support 1355 if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) { 1356 final Set<String> features = getDeviceFeatures(mDevice); 1357 1358 if (isPortraitClassRotation(runConfig.getRotation()) && 1359 !features.contains(FEATURE_PORTRAIT)) { 1360 return false; 1361 } 1362 if (isLandscapeClassRotation(runConfig.getRotation()) && 1363 !features.contains(FEATURE_LANDSCAPE)) { 1364 return false; 1365 } 1366 } 1367 1368 if (isOpenGlEsPackage()) { 1369 // renderability support for OpenGL ES tests 1370 return isSupportedGlesRenderConfig(runConfig); 1371 } else { 1372 return true; 1373 } 1374 } 1375 1376 private static final class AdbComLinkOpenError extends Exception { AdbComLinkOpenError(String description, Throwable inner)1377 public AdbComLinkOpenError(String description, Throwable inner) { 1378 super(description, inner); 1379 } 1380 } 1381 1382 private static final class AdbComLinkKilledError extends Exception { AdbComLinkKilledError(String description, Throwable inner)1383 public AdbComLinkKilledError(String description, Throwable inner) { 1384 super(description, inner); 1385 } 1386 } 1387 1388 /** 1389 * Executes a given command in adb shell 1390 * 1391 * @throws AdbComLinkOpenError if connection cannot be established. 1392 * @throws AdbComLinkKilledError if established connection is killed prematurely. 1393 */ executeShellCommandAndReadOutput(final String command, final IShellOutputReceiver receiver)1394 private void executeShellCommandAndReadOutput(final String command, 1395 final IShellOutputReceiver receiver) 1396 throws AdbComLinkOpenError, AdbComLinkKilledError { 1397 try { 1398 mDevice.getIDevice().executeShellCommand(command, receiver, 1399 UNRESPOSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS); 1400 } catch (TimeoutException ex) { 1401 // Opening connection timed out 1402 CLog.e("Opening connection timed out for command: '%s'", command); 1403 throw new AdbComLinkOpenError("opening connection timed out", ex); 1404 } catch (AdbCommandRejectedException ex) { 1405 // Command rejected 1406 CLog.e("Device rejected command: '%s'", command); 1407 throw new AdbComLinkOpenError("command rejected", ex); 1408 } catch (IOException ex) { 1409 // shell command channel killed 1410 CLog.e("Channel died for command: '%s'", command); 1411 throw new AdbComLinkKilledError("command link killed", ex); 1412 } catch (ShellCommandUnresponsiveException ex) { 1413 // shell command halted 1414 CLog.e("No output from command in %d ms: '%s'", UNRESPOSIVE_CMD_TIMEOUT_MS, command); 1415 throw new AdbComLinkKilledError("command link hung", ex); 1416 } 1417 } 1418 1419 /** 1420 * Executes given test batch on a device 1421 */ executeTestRunBatch(TestBatch batch)1422 private void executeTestRunBatch(TestBatch batch) throws DeviceNotAvailableException { 1423 // attempt full run once 1424 executeTestRunBatchRun(batch); 1425 1426 // split remaining tests to two sub batches and execute both. This will terminate 1427 // since executeTestRunBatchRun will always progress for a batch of size 1. 1428 final ArrayList<TestIdentifier> pendingTests = new ArrayList<>(); 1429 1430 for (TestIdentifier test : batch.tests) { 1431 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) { 1432 pendingTests.add(test); 1433 } 1434 } 1435 1436 final int divisorNdx = pendingTests.size() / 2; 1437 final List<TestIdentifier> headList = pendingTests.subList(0, divisorNdx); 1438 final List<TestIdentifier> tailList = pendingTests.subList(divisorNdx, pendingTests.size()); 1439 1440 // head 1441 for (;;) { 1442 TestBatch subBatch = selectRunBatch(headList, batch.config); 1443 1444 if (subBatch == null) { 1445 break; 1446 } 1447 1448 executeTestRunBatch(subBatch); 1449 } 1450 1451 // tail 1452 for (;;) { 1453 TestBatch subBatch = selectRunBatch(tailList, batch.config); 1454 1455 if (subBatch == null) { 1456 break; 1457 } 1458 1459 executeTestRunBatch(subBatch); 1460 } 1461 1462 if (getBatchNumPendingCases(batch) != 0) { 1463 throw new AssertionError("executeTestRunBatch postcondition failed"); 1464 } 1465 } 1466 1467 /** 1468 * Runs one execution pass over the given batch. 1469 * 1470 * Tries to run the batch. Always makes progress (executes instances or modifies stability 1471 * scores). 1472 */ executeTestRunBatchRun(TestBatch batch)1473 private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException { 1474 if (getBatchNumPendingCases(batch) != batch.tests.size()) { 1475 throw new AssertionError("executeTestRunBatchRun precondition failed"); 1476 } 1477 1478 checkInterrupted(); // throws if interrupted 1479 1480 final String testCases = generateTestCaseTrie(batch.tests); 1481 1482 mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME); 1483 mDevice.executeShellCommand("rm " + LOG_FILE_NAME); 1484 mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME); 1485 1486 final String instrumentationName = 1487 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation"; 1488 1489 final StringBuilder deqpCmdLine = new StringBuilder(); 1490 deqpCmdLine.append("--deqp-caselist-file="); 1491 deqpCmdLine.append(CASE_LIST_FILE_NAME); 1492 deqpCmdLine.append(" "); 1493 deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config)); 1494 1495 // If we are not logging data, do not bother outputting the images from the test exe. 1496 if (!mLogData) { 1497 deqpCmdLine.append(" --deqp-log-images=disable"); 1498 } 1499 1500 deqpCmdLine.append(" --deqp-watchdog=enable"); 1501 1502 final String command = String.format( 1503 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\"" 1504 + " -e deqpLogData \"%s\" %s", 1505 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(), 1506 mLogData, instrumentationName); 1507 1508 final int numRemainingInstancesBefore = getNumRemainingInstances(); 1509 final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner); 1510 Throwable interruptingError = null; 1511 1512 try { 1513 CLog.d("Running command '%s'", command); 1514 executeShellCommandAndReadOutput(command, parser); 1515 parser.flush(); 1516 } catch (Throwable ex) { 1517 CLog.w("Instrumented call threw '%s'", ex.getMessage()); 1518 interruptingError = ex; 1519 } 1520 1521 final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null || 1522 getNumRemainingInstances() < numRemainingInstancesBefore; 1523 1524 if (progressedSinceLastCall) { 1525 mDeviceRecovery.onExecutionProgressed(); 1526 } 1527 1528 // interrupted, try to recover 1529 if (interruptingError != null) { 1530 if (interruptingError instanceof AdbComLinkOpenError) { 1531 CLog.i("Recovering from comm link error"); 1532 mDeviceRecovery.recoverConnectionRefused(); 1533 } else if (interruptingError instanceof AdbComLinkKilledError) { 1534 CLog.i("Recovering from comm link killed"); 1535 mDeviceRecovery.recoverComLinkKilled(); 1536 } else if (interruptingError instanceof RunInterruptedException) { 1537 // external run interruption request. Terminate immediately. 1538 CLog.i("Run termination requested. Throwing forward."); 1539 throw (RunInterruptedException)interruptingError; 1540 } else { 1541 CLog.e(interruptingError); 1542 throw new RuntimeException(interruptingError); 1543 } 1544 1545 // recoverXXX did not throw => recovery succeeded 1546 } else if (!parser.wasSuccessful()) { 1547 CLog.i("Parse not successful. Will attempt comm link recovery."); 1548 mDeviceRecovery.recoverComLinkKilled(); 1549 // recoverXXX did not throw => recovery succeeded 1550 } 1551 1552 // Progress guarantees. 1553 if (batch.tests.size() == 1) { 1554 final TestIdentifier onlyTest = batch.tests.iterator().next(); 1555 final boolean wasTestExecuted = 1556 !mInstanceListerner.isPendingTestInstance(onlyTest, batch.config) && 1557 mInstanceListerner.getCurrentTestId() == null; 1558 final boolean wasLinkFailure = !parser.wasSuccessful() || interruptingError != null; 1559 1560 // Link failures can be caused by external events, require at least two observations 1561 // until bailing. 1562 if (!wasTestExecuted && (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) { 1563 recordTestInstability(onlyTest); 1564 // If we cannot finish the test, mark the case as a crash. 1565 // 1566 // If we couldn't even start the test, fail the test instance as non-executable. 1567 // This is required so that a consistently crashing or non-existent tests will 1568 // not cause futile (non-terminating) re-execution attempts. 1569 if (mInstanceListerner.getCurrentTestId() != null) { 1570 CLog.w("Test '%s' started, but not completed", onlyTest); 1571 mInstanceListerner.abortTest(onlyTest, INCOMPLETE_LOG_MESSAGE); 1572 } else { 1573 CLog.w("Test '%s' could not start", onlyTest); 1574 mInstanceListerner.abortTest(onlyTest, NOT_EXECUTABLE_LOG_MESSAGE); 1575 } 1576 } else if (wasTestExecuted) { 1577 clearTestInstability(onlyTest); 1578 } 1579 } 1580 else 1581 { 1582 // Analyze results to update test stability ratings. If there is no interrupting test 1583 // logged, increase instability rating of all remaining tests. If there is a 1584 // interrupting test logged, increase only its instability rating. 1585 // 1586 // A successful run of tests clears instability rating. 1587 if (mInstanceListerner.getCurrentTestId() == null) { 1588 for (TestIdentifier test : batch.tests) { 1589 if (mInstanceListerner.isPendingTestInstance(test, batch.config)) { 1590 recordTestInstability(test); 1591 } else { 1592 clearTestInstability(test); 1593 } 1594 } 1595 } else { 1596 recordTestInstability(mInstanceListerner.getCurrentTestId()); 1597 for (TestIdentifier test : batch.tests) { 1598 // \note: isPendingTestInstance is false for getCurrentTestId. Current ID is 1599 // considered 'running' and will be restored to 'pending' in endBatch(). 1600 if (!test.equals(mInstanceListerner.getCurrentTestId()) && 1601 !mInstanceListerner.isPendingTestInstance(test, batch.config)) { 1602 clearTestInstability(test); 1603 } 1604 } 1605 } 1606 } 1607 1608 mInstanceListerner.endBatch(); 1609 } 1610 getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig)1611 private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) { 1612 final StringBuilder deqpCmdLine = new StringBuilder(); 1613 if (!runConfig.getGlConfig().isEmpty()) { 1614 deqpCmdLine.append("--deqp-gl-config-name="); 1615 deqpCmdLine.append(runConfig.getGlConfig()); 1616 } 1617 if (!runConfig.getRotation().isEmpty()) { 1618 if (deqpCmdLine.length() != 0) { 1619 deqpCmdLine.append(" "); 1620 } 1621 deqpCmdLine.append("--deqp-screen-rotation="); 1622 deqpCmdLine.append(runConfig.getRotation()); 1623 } 1624 if (!runConfig.getSurfaceType().isEmpty()) { 1625 if (deqpCmdLine.length() != 0) { 1626 deqpCmdLine.append(" "); 1627 } 1628 deqpCmdLine.append("--deqp-surface-type="); 1629 deqpCmdLine.append(runConfig.getSurfaceType()); 1630 } 1631 return deqpCmdLine.toString(); 1632 } 1633 getNumRemainingInstances()1634 private int getNumRemainingInstances() { 1635 int retVal = 0; 1636 for (TestIdentifier testId : mRemainingTests) { 1637 // If case is in current working set, sum only not yet executed instances. 1638 // If case is not in current working set, sum all instances (since they are not yet 1639 // executed). 1640 if (mInstanceListerner.mPendingResults.containsKey(testId)) { 1641 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size(); 1642 } else { 1643 retVal += mTestInstances.get(testId).size(); 1644 } 1645 } 1646 return retVal; 1647 } 1648 1649 /** 1650 * Checks if this execution has been marked as interrupted and throws if it has. 1651 */ checkInterrupted()1652 private void checkInterrupted() throws RunInterruptedException { 1653 // Work around the API. RunUtil::checkInterrupted is private but we can call it indirectly 1654 // by sleeping a value <= 0. 1655 mRunUtil.sleep(0); 1656 } 1657 1658 /** 1659 * Pass given batch tests without running it 1660 */ fakePassTestRunBatch(TestBatch batch)1661 private void fakePassTestRunBatch(TestBatch batch) { 1662 for (TestIdentifier test : batch.tests) { 1663 CLog.d("Skipping test '%s' invocation in config '%s'", test.toString(), 1664 batch.config.getId()); 1665 mInstanceListerner.skipTest(test); 1666 } 1667 } 1668 1669 /** 1670 * Pass all remaining tests without running them 1671 */ fakePassTests(ITestInvocationListener listener)1672 private void fakePassTests(ITestInvocationListener listener) { 1673 Map <String, String> emptyMap = Collections.emptyMap(); 1674 for (TestIdentifier test : mRemainingTests) { 1675 CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString()); 1676 listener.testStarted(test); 1677 listener.testEnded(test, emptyMap); 1678 } 1679 mRemainingTests.clear(); 1680 } 1681 1682 /** 1683 * Check if device supports OpenGL ES version. 1684 */ isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)1685 private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion, 1686 int requiredMinorVersion) throws DeviceNotAvailableException { 1687 String roOpenglesVersion = device.getProperty("ro.opengles.version"); 1688 1689 if (roOpenglesVersion == null) 1690 return false; 1691 1692 int intValue = Integer.parseInt(roOpenglesVersion); 1693 1694 int majorVersion = ((intValue & 0xffff0000) >> 16); 1695 int minorVersion = (intValue & 0xffff); 1696 1697 return (majorVersion > requiredMajorVersion) 1698 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion); 1699 } 1700 1701 /** 1702 * Query if rendertarget is supported 1703 */ isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)1704 private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig) 1705 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1706 // query if configuration is supported 1707 final StringBuilder configCommandLine = 1708 new StringBuilder(getRunConfigDisplayCmdLine(runConfig)); 1709 if (configCommandLine.length() != 0) { 1710 configCommandLine.append(" "); 1711 } 1712 configCommandLine.append("--deqp-gl-major-version="); 1713 configCommandLine.append(getGlesMajorVersion()); 1714 configCommandLine.append(" --deqp-gl-minor-version="); 1715 configCommandLine.append(getGlesMinorVersion()); 1716 1717 final String commandLine = configCommandLine.toString(); 1718 1719 // check for cached result first 1720 if (mConfigQuerySupportCache.containsKey(commandLine)) { 1721 return mConfigQuerySupportCache.get(commandLine); 1722 } 1723 1724 final boolean supported = queryIsSupportedConfigCommandLine(commandLine); 1725 mConfigQuerySupportCache.put(commandLine, supported); 1726 return supported; 1727 } 1728 queryIsSupportedConfigCommandLine(String deqpCommandLine)1729 private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine) 1730 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1731 final String instrumentationName = 1732 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation"; 1733 final String command = String.format( 1734 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\"" 1735 + " %s", 1736 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName); 1737 1738 final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser(); 1739 mDevice.executeShellCommand(command, parser); 1740 parser.flush(); 1741 1742 if (parser.wasSuccessful() && parser.getResultCode() == 0 && 1743 parser.getResultMap().containsKey("Supported")) { 1744 if ("Yes".equals(parser.getResultMap().get("Supported"))) { 1745 return true; 1746 } else if ("No".equals(parser.getResultMap().get("Supported"))) { 1747 return false; 1748 } else { 1749 CLog.e("Capability query did not return a result"); 1750 throw new CapabilityQueryFailureException(); 1751 } 1752 } else if (parser.wasSuccessful()) { 1753 CLog.e("Failed to run capability query. Code: %d, Result: %s", 1754 parser.getResultCode(), parser.getResultMap().toString()); 1755 throw new CapabilityQueryFailureException(); 1756 } else { 1757 CLog.e("Failed to run capability query"); 1758 throw new CapabilityQueryFailureException(); 1759 } 1760 } 1761 1762 /** 1763 * Return feature set supported by the device 1764 */ getDeviceFeatures(ITestDevice device)1765 private Set<String> getDeviceFeatures(ITestDevice device) 1766 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1767 if (mDeviceFeatures == null) { 1768 mDeviceFeatures = queryDeviceFeatures(device); 1769 } 1770 return mDeviceFeatures; 1771 } 1772 1773 /** 1774 * Query feature set supported by the device 1775 */ queryDeviceFeatures(ITestDevice device)1776 private static Set<String> queryDeviceFeatures(ITestDevice device) 1777 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1778 // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures 1779 // TODO: Move this logic to ITestDevice. 1780 String command = "pm list features"; 1781 String commandOutput = device.executeShellCommand(command); 1782 1783 // Extract the id of the new user. 1784 HashSet<String> availableFeatures = new HashSet<>(); 1785 for (String feature: commandOutput.split("\\s+")) { 1786 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 1787 String[] tokens = feature.split(":"); 1788 if (tokens.length < 2 || !"feature".equals(tokens[0])) { 1789 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]); 1790 throw new CapabilityQueryFailureException(); 1791 } 1792 availableFeatures.add(tokens[1]); 1793 } 1794 return availableFeatures; 1795 } 1796 isPortraitClassRotation(String rotation)1797 private boolean isPortraitClassRotation(String rotation) { 1798 return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) || 1799 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation); 1800 } 1801 isLandscapeClassRotation(String rotation)1802 private boolean isLandscapeClassRotation(String rotation) { 1803 return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) || 1804 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation); 1805 } 1806 1807 /** 1808 * Install dEQP OnDevice Package 1809 */ installTestApk()1810 private void installTestApk() throws DeviceNotAvailableException { 1811 try { 1812 File apkFile = mCtsBuild.getTestApp(DEQP_ONDEVICE_APK); 1813 String[] options = {AbiUtils.createAbiFlag(mAbi.getName())}; 1814 String errorCode = getDevice().installPackage(apkFile, true, options); 1815 if (errorCode != null) { 1816 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode); 1817 } 1818 } catch (FileNotFoundException e) { 1819 CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK); 1820 } 1821 } 1822 1823 /** 1824 * Uninstall dEQP OnDevice Package 1825 */ uninstallTestApk()1826 private void uninstallTestApk() throws DeviceNotAvailableException { 1827 getDevice().uninstallPackage(DEQP_ONDEVICE_PKG); 1828 } 1829 1830 /** 1831 * Parse gl nature from package name 1832 */ isOpenGlEsPackage()1833 private boolean isOpenGlEsPackage() { 1834 if ("dEQP-GLES2".equals(mName) || "dEQP-GLES3".equals(mName) || 1835 "dEQP-GLES31".equals(mName)) { 1836 return true; 1837 } else if ("dEQP-EGL".equals(mName)) { 1838 return false; 1839 } else { 1840 throw new IllegalStateException("dEQP runner was created with illegal name"); 1841 } 1842 } 1843 1844 /** 1845 * Check GL support (based on package name) 1846 */ isSupportedGles()1847 private boolean isSupportedGles() throws DeviceNotAvailableException { 1848 return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion()); 1849 } 1850 1851 /** 1852 * Get GL major version (based on package name) 1853 */ getGlesMajorVersion()1854 private int getGlesMajorVersion() throws DeviceNotAvailableException { 1855 if ("dEQP-GLES2".equals(mName)) { 1856 return 2; 1857 } else if ("dEQP-GLES3".equals(mName)) { 1858 return 3; 1859 } else if ("dEQP-GLES31".equals(mName)) { 1860 return 3; 1861 } else { 1862 throw new IllegalStateException("getGlesMajorVersion called for non gles pkg"); 1863 } 1864 } 1865 1866 /** 1867 * Get GL minor version (based on package name) 1868 */ getGlesMinorVersion()1869 private int getGlesMinorVersion() throws DeviceNotAvailableException { 1870 if ("dEQP-GLES2".equals(mName)) { 1871 return 0; 1872 } else if ("dEQP-GLES3".equals(mName)) { 1873 return 0; 1874 } else if ("dEQP-GLES31".equals(mName)) { 1875 return 1; 1876 } else { 1877 throw new IllegalStateException("getGlesMinorVersion called for non gles pkg"); 1878 } 1879 } 1880 1881 /** 1882 * {@inheritDoc} 1883 */ 1884 @Override run(ITestInvocationListener listener)1885 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 1886 final Map<String, String> emptyMap = Collections.emptyMap(); 1887 final boolean isSupportedApi = !isOpenGlEsPackage() || isSupportedGles(); 1888 1889 listener.testRunStarted(getId(), mRemainingTests.size()); 1890 1891 try { 1892 if (isSupportedApi) { 1893 // Make sure there is no pre-existing package form earlier interrupted test run. 1894 uninstallTestApk(); 1895 installTestApk(); 1896 1897 mInstanceListerner.setSink(listener); 1898 mDeviceRecovery.setDevice(mDevice); 1899 runTests(); 1900 1901 uninstallTestApk(); 1902 } else { 1903 // Pass all tests if OpenGL ES version is not supported 1904 CLog.i("Package %s not supported by the device. Tests trivially pass.", mPackageName); 1905 fakePassTests(listener); 1906 } 1907 } catch (CapabilityQueryFailureException ex) { 1908 // Platform is not behaving correctly, for example crashing when trying to create 1909 // a window. Instead of silenty failing, signal failure by leaving the rest of the 1910 // test cases in "NotExecuted" state 1911 uninstallTestApk(); 1912 } 1913 1914 listener.testRunEnded(0, emptyMap); 1915 } 1916 } 1917