1 package com.android.cts.tradefed.testtype; 2 3 import com.android.cts.tradefed.build.CtsBuildHelper; 4 import com.android.cts.util.AbiUtils; 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 = 60000; // one minute 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 { 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 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 } 315 } 316 317 /** 318 * Declare existence of a test and instances 319 */ setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs)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 */ isPendingTestInstance(TestIdentifier testId, BatchRunConfiguration config)335 public boolean isPendingTestInstance(TestIdentifier testId, 336 BatchRunConfiguration config) { 337 final PendingResult result = mPendingResults.get(testId); 338 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 } 352 } 353 354 /** 355 * Fake execution of an instance with current config 356 */ skipTest(TestIdentifier testId)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 363 // Pending result finished, report result 364 if (result.remainingConfigs.isEmpty()) { 365 forwardFinalizedPendingResult(testId); 366 } 367 } 368 369 /** 370 * Fake failure of an instance with current config 371 */ abortTest(TestIdentifier testId, String errorMessage)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)) { 386 mCurrentTestId = null; 387 } 388 } 389 390 /** 391 * Handles beginning of dEQP session. 392 */ handleBeginSession(Map<String, String> values)393 private void handleBeginSession(Map<String, String> values) { 394 // ignore 395 } 396 397 /** 398 * Handles end of dEQP session. 399 */ handleEndSession(Map<String, String> values)400 private void handleEndSession(Map<String, String> values) { 401 // ignore 402 } 403 404 /** 405 * Handles beginning of dEQP testcase. 406 */ handleBeginTestCase(Map<String, String> values)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 413 if (mPendingResults.get(mCurrentTestId) != null) { 414 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig); 415 } else { 416 CLog.w("Got unexpected start of %s", mCurrentTestId); 417 } 418 } 419 420 /** 421 * Handles end of dEQP testcase. 422 */ handleEndTestCase(Map<String, String> values)423 private void handleEndTestCase(Map<String, String> values) { 424 final PendingResult result = mPendingResults.get(mCurrentTestId); 425 426 if (result != null) { 427 if (!mGotTestResult) { 428 result.allInstancesPassed = false; 429 result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE); 430 } 431 432 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()) { 438 forwardFinalizedPendingResult(mCurrentTestId); 439 } 440 } else { 441 CLog.w("Got unexpected end of %s", mCurrentTestId); 442 } 443 mCurrentTestId = null; 444 } 445 446 /** 447 * Handles dEQP testcase result. 448 */ handleTestCaseResult(Map<String, String> values)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 453 if (mPendingResults.get(mCurrentTestId) == null) { 454 CLog.w("Got unexpected result for %s", mCurrentTestId); 455 mGotTestResult = true; 456 return; 457 } 458 459 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 */ handleTestCaseTerminate(Map<String, String> values)486 private void handleTestCaseTerminate(Map<String, String> values) { 487 final PendingResult result = mPendingResults.get(mCurrentTestId); 488 489 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); 494 495 // Pending result finished, report result 496 if (result.remainingConfigs.isEmpty()) { 497 forwardFinalizedPendingResult(mCurrentTestId); 498 } 499 } else { 500 CLog.w("Got unexpected termination of %s", mCurrentTestId); 501 } 502 503 mCurrentTestId = null; 504 mGotTestResult = true; 505 } 506 507 /** 508 * Handles dEQP testlog data. 509 */ handleTestLogData(Map<String, String> values)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 */ handleStatus(Map<String, String> values)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 /** 542 * Signal listener that batch ended and forget incomplete results. 543 */ endBatch()544 public void endBatch() { 545 // end open test if when stream ends 546 if (mCurrentTestId != null) { 547 // 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); 553 } 554 } 555 mCurrentTestId = null; 556 } 557 } 558 559 /** 560 * dEQP instrumentation parser 561 */ 562 private static class InstrumentationParser extends MultiLineReceiver { 563 private TestInstanceResultListener mListener; 564 565 private Map<String, String> mValues; 566 private String mCurrentName; 567 private String mCurrentValue; 568 private int mResultCode; 569 private boolean mGotExitValue = false; 570 571 InstrumentationParser(TestInstanceResultListener listener)572 public InstrumentationParser(TestInstanceResultListener listener) { 573 mListener = listener; 574 } 575 576 /** 577 * {@inheritDoc} 578 */ 579 @Override processNewLines(String[] lines)580 public void processNewLines(String[] lines) { 581 for (String line : lines) { 582 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 592 mListener.handleStatus(mValues); 593 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); 609 } 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 } 616 } else if (mCurrentValue != null) { 617 mCurrentValue = mCurrentValue + line; 618 } 619 } 620 } 621 622 /** 623 * {@inheritDoc} 624 */ 625 @Override done()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) { 635 mListener.handleStatus(mValues); 636 mValues = null; 637 } 638 } 639 640 /** 641 * {@inheritDoc} 642 */ 643 @Override isCancelled()644 public boolean isCancelled() { 645 return false; 646 } 647 648 /** 649 * Returns whether target instrumentation exited normally. 650 */ wasSuccessful()651 public boolean wasSuccessful() { 652 return mGotExitValue; 653 } 654 655 /** 656 * Returns Instrumentation return code 657 */ getResultCode()658 public int getResultCode() { 659 return mResultCode; 660 } 661 } 662 663 /** 664 * 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 processNewLines(String[] lines)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 isCancelled()699 public boolean isCancelled() { 700 return false; 701 } 702 703 /** 704 * Returns whether target instrumentation exited normally. 705 */ wasSuccessful()706 public boolean wasSuccessful() { 707 return mGotExitValue; 708 } 709 710 /** 711 * Returns Instrumentation return code 712 */ getResultCode()713 public int getResultCode() { 714 return mResultCode; 715 } 716 getResultMap()717 public Map<String,String> getResultMap() { 718 return mResultMap; 719 } 720 } 721 722 /** 723 * Interface for sleeping. 724 * 725 * Exposed for unit testing 726 */ 727 public static interface ISleepProvider { sleep(int milliseconds)728 public void sleep(int milliseconds); 729 }; 730 731 private static class SleepProvider implements ISleepProvider { sleep(int milliseconds)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 */ setSleepProvider(ISleepProvider sleepProvider)749 public void setSleepProvider(ISleepProvider sleepProvider); 750 751 /** 752 * Sets the device IRecovery works on 753 */ setDevice(ITestDevice device)754 public void setDevice(ITestDevice device); 755 756 /** 757 * Informs Recovery that test execution has progressed since the last recovery 758 */ onExecutionProgressed()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 */ recoverConnectionRefused()766 public void recoverConnectionRefused() throws DeviceNotAvailableException; 767 768 /** 769 * Tries to recover device after abnormal execution termination or link failure. 770 * 771 * @param progressedSinceLastCall true if test execution has progressed since last call 772 * @throws DeviceNotAvailableException if recovery did not succeed 773 */ recoverComLinkKilled()774 public void recoverComLinkKilled() throws DeviceNotAvailableException; 775 }; 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 784 private int PROCESS_KILL_WAIT_MS = 1000; // 1 second 785 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 797 private static class ProcessKillFailureException extends Exception { 798 } 799 800 /** 801 * {@inheritDoc} 802 */ setSleepProvider(ISleepProvider sleepProvider)803 public void setSleepProvider(ISleepProvider sleepProvider) { 804 mSleepProvider = sleepProvider; 805 } 806 807 /** 808 * {@inheritDoc} 809 */ 810 @Override setDevice(ITestDevice device)811 public void setDevice(ITestDevice device) { 812 mDevice = device; 813 } 814 815 /** 816 * {@inheritDoc} 817 */ 818 @Override onExecutionProgressed()819 public void onExecutionProgressed() { 820 mState = MachineState.WAIT; 821 } 822 823 /** 824 * {@inheritDoc} 825 */ 826 @Override recoverConnectionRefused()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 recoverComLinkKilled()867 public void recoverComLinkKilled() throws DeviceNotAvailableException { 868 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 882 recoverComLinkKilled(); 883 } catch (ProcessKillFailureException ex) { 884 // chain forward 885 recoverComLinkKilled(); 886 } 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 899 recoverComLinkKilled(); 900 } catch (ProcessKillFailureException ex) { 901 // chain forward 902 recoverComLinkKilled(); 903 } 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 915 recoverComLinkKilled(); 916 } 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 waitCooldown()926 private void waitCooldown() { 927 mSleepProvider.sleep(RETRY_COOLDOWN_MS); 928 } 929 getDeqpProcessPids()930 private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException { 931 final List<Integer> pids = new ArrayList<Integer>(2); 932 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 940 try { 941 final int processId = Integer.parseInt(fields[1], 10); 942 pids.add(processId); 943 } catch (NumberFormatException ex) { 944 continue; 945 } 946 } 947 return pids; 948 } 949 killDeqpProcess()950 private void killDeqpProcess() throws DeviceNotAvailableException, 951 ProcessKillFailureException { 952 for (Integer processId : getDeqpProcessPids()) { 953 mDevice.executeShellCommand(String.format("kill -9 %d", processId)); 954 } 955 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 } 963 } 964 recoverDevice()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 rebootDevice()993 private void rebootDevice() throws DeviceNotAvailableException { 994 mDevice.reboot(); 995 } 996 }; 997 998 /** 999 * Parse map of instance arguments to map of BatchRunConfigurations 1000 */ parseTestInstances( Collection<TestIdentifier> tests, Map<TestIdentifier, List<Map<String,String>>> testInstances)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 parseRunConfig(Map<String,String> instanceArguments)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 getTestRunConfigs(TestIdentifier testId)1044 private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) { 1045 return mTestInstances.get(testId); 1046 } 1047 1048 /** 1049 * Converts dEQP testcase path to TestIdentifier. 1050 */ pathToIdentifier(String testPath)1051 private static TestIdentifier pathToIdentifier(String testPath) { 1052 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 getId()1067 private String getId() { 1068 return AbiUtils.createId(mAbi.getName(), mPackageName); 1069 } 1070 1071 /** 1072 * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute. 1073 */ generateTestCaseTrieFromPaths(Collection<String> tests)1074 private static String generateTestCaseTrieFromPaths(Collection<String> tests) { 1075 String result = "{"; 1076 boolean first = true; 1077 1078 // Add testcases to results 1079 for (Iterator<String> iter = tests.iterator(); iter.hasNext();) { 1080 String test = iter.next(); 1081 String[] components = test.split("\\."); 1082 1083 if (components.length == 1) { 1084 if (!first) { 1085 result = result + ","; 1086 } 1087 first = false; 1088 1089 result += components[0]; 1090 iter.remove(); 1091 } 1092 } 1093 1094 if (!tests.isEmpty()) { 1095 HashMap<String, ArrayList<String> > testGroups = new HashMap<>(); 1096 1097 // Collect all sub testgroups 1098 for (String test : tests) { 1099 String[] components = test.split("\\."); 1100 ArrayList<String> testGroup = testGroups.get(components[0]); 1101 1102 if (testGroup == null) { 1103 testGroup = new ArrayList<String>(); 1104 testGroups.put(components[0], testGroup); 1105 } 1106 1107 testGroup.add(test.substring(components[0].length()+1)); 1108 } 1109 1110 for (String testGroup : testGroups.keySet()) { 1111 if (!first) { 1112 result = result + ","; 1113 } 1114 1115 first = false; 1116 result = result + testGroup 1117 + generateTestCaseTrieFromPaths(testGroups.get(testGroup)); 1118 } 1119 } 1120 1121 return result + "}"; 1122 } 1123 1124 /** 1125 * Generates testcase trie from TestIdentifiers. 1126 */ generateTestCaseTrie(Collection<TestIdentifier> tests)1127 private static String generateTestCaseTrie(Collection<TestIdentifier> tests) { 1128 ArrayList<String> testPaths = new ArrayList<String>(); 1129 1130 for (TestIdentifier test : tests) { 1131 testPaths.add(test.getClassName() + "." + test.getTestName()); 1132 } 1133 1134 return generateTestCaseTrieFromPaths(testPaths); 1135 } 1136 1137 private static class TestBatch { 1138 public BatchRunConfiguration config; 1139 public List<TestIdentifier> tests; 1140 } 1141 selectRunBatch()1142 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 */ selectRunBatch(Collection<TestIdentifier> pool, BatchRunConfiguration requiredConfig)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; 1183 break; 1184 } 1185 } 1186 } 1187 1188 // 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; 1204 } 1205 if (!mInstanceListerner.isPendingTestInstance(test, leadingTestConfig)) { 1206 // select only compatible 1207 continue; 1208 } 1209 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; 1214 } 1215 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 getBatchNumPendingCases(TestBatch batch)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 getBatchSizeLimitForInstability(int batchInstabilityRating)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 getTestInstabilityRating(TestIdentifier testId)1240 private int getTestInstabilityRating(TestIdentifier testId) { 1241 if (mTestInstabilityRatings.containsKey(testId)) { 1242 return mTestInstabilityRatings.get(testId); 1243 } else { 1244 return 0; 1245 } 1246 } 1247 recordTestInstability(TestIdentifier testId)1248 private void recordTestInstability(TestIdentifier testId) { 1249 mTestInstabilityRatings.put(testId, getTestInstabilityRating(testId) + 1); 1250 } 1251 clearTestInstability(TestIdentifier testId)1252 private void clearTestInstability(TestIdentifier testId) { 1253 mTestInstabilityRatings.put(testId, 0); 1254 } 1255 1256 /** 1257 * Executes all tests on the device. 1258 */ runTests()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 */ runTestRunBatch(TestBatch batch)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 isSupportedRunConfiguration(BatchRunConfiguration runConfig)1290 private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig) 1291 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1292 // orientation support 1293 if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) { 1294 final Set<String> features = getDeviceFeatures(mDevice); 1295 1296 if (isPortraitClassRotation(runConfig.getRotation()) && 1297 !features.contains(FEATURE_PORTRAIT)) { 1298 return false; 1299 } 1300 if (isLandscapeClassRotation(runConfig.getRotation()) && 1301 !features.contains(FEATURE_LANDSCAPE)) { 1302 return false; 1303 } 1304 } 1305 1306 if (isOpenGlEsPackage()) { 1307 // renderability support for OpenGL ES tests 1308 return isSupportedGlesRenderConfig(runConfig); 1309 } else { 1310 return true; 1311 } 1312 } 1313 1314 private static final class AdbComLinkOpenError extends Exception { AdbComLinkOpenError(String description, Throwable inner)1315 public AdbComLinkOpenError(String description, Throwable inner) { 1316 super(description, inner); 1317 } 1318 }; 1319 private static final class AdbComLinkKilledError extends Exception { AdbComLinkKilledError(String description, Throwable inner)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 */ executeShellCommandAndReadOutput(final String command, final IShellOutputReceiver receiver)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 1352 /** 1353 * Executes given test batch on a device 1354 */ executeTestRunBatch(TestBatch batch)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 */ executeTestRunBatchRun(TestBatch batch)1406 private void executeTestRunBatchRun(TestBatch batch) throws DeviceNotAvailableException { 1407 if (getBatchNumPendingCases(batch) != batch.tests.size()) { 1408 throw new AssertionError("executeTestRunBatchRun precondition failed"); 1409 } 1410 1411 checkInterrupted(); // throws if interrupted 1412 1413 final String testCases = generateTestCaseTrie(batch.tests); 1414 1415 mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME); 1416 mDevice.executeShellCommand("rm " + LOG_FILE_NAME); 1417 mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME); 1418 1419 final String instrumentationName = 1420 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation"; 1421 1422 final StringBuilder deqpCmdLine = new StringBuilder(); 1423 deqpCmdLine.append("--deqp-caselist-file="); 1424 deqpCmdLine.append(CASE_LIST_FILE_NAME); 1425 deqpCmdLine.append(" "); 1426 deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.config)); 1427 1428 // 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 1433 deqpCmdLine.append(" --deqp-watchdog=enable"); 1434 1435 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 1441 final int numRemainingInstancesBefore = getNumRemainingInstances(); 1442 final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner); 1443 Throwable interruptingError = null; 1444 1445 try { 1446 executeShellCommandAndReadOutput(command, parser); 1447 } catch (Throwable ex) { 1448 interruptingError = ex; 1449 } finally { 1450 parser.flush(); 1451 } 1452 1453 final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null || 1454 getNumRemainingInstances() < numRemainingInstancesBefore; 1455 1456 if (progressedSinceLastCall) { 1457 mDeviceRecovery.onExecutionProgressed(); 1458 } 1459 1460 // interrupted, try to recover 1461 if (interruptingError != null) { 1462 if (interruptingError instanceof AdbComLinkOpenError) { 1463 mDeviceRecovery.recoverConnectionRefused(); 1464 } else if (interruptingError instanceof AdbComLinkKilledError) { 1465 mDeviceRecovery.recoverComLinkKilled(); 1466 } else if (interruptingError instanceof RunInterruptedException) { 1467 // external run interruption request. Terminate immediately. 1468 throw (RunInterruptedException)interruptingError; 1469 } else { 1470 CLog.e(interruptingError); 1471 throw new RuntimeException(interruptingError); 1472 } 1473 1474 // recoverXXX did not throw => recovery succeeded 1475 } else if (!parser.wasSuccessful()) { 1476 mDeviceRecovery.recoverComLinkKilled(); 1477 // recoverXXX did not throw => recovery succeeded 1478 } 1479 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(); 1535 } 1536 getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig)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 getNumRemainingInstances()1560 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 1575 /** 1576 * Checks if this execution has been marked as interrupted and throws if it has. 1577 */ checkInterrupted()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 /** 1585 * Pass given batch tests without running it 1586 */ fakePassTestRunBatch(TestBatch batch)1587 private void fakePassTestRunBatch(TestBatch batch) { 1588 for (TestIdentifier test : batch.tests) { 1589 CLog.d("Skipping test '%s' invocation in config '%s'", test.toString(), 1590 batch.config.getId()); 1591 mInstanceListerner.skipTest(test); 1592 } 1593 } 1594 1595 /** 1596 * Pass all remaining tests without running them 1597 */ fakePassTests(ITestInvocationListener listener)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(); 1606 } 1607 1608 /** 1609 * Check if device supports OpenGL ES version. 1610 */ isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)1611 private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion, 1612 int requiredMinorVersion) throws DeviceNotAvailableException { 1613 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 /** 1628 * Query if rendertarget is supported 1629 */ isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)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 1643 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 queryIsSupportedConfigCommandLine(String deqpCommandLine)1655 private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine) 1656 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1657 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", 1662 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName); 1663 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 */ getDeviceFeatures(ITestDevice device)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 */ queryDeviceFeatures(ITestDevice device)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 isPortraitClassRotation(String rotation)1723 private boolean isPortraitClassRotation(String rotation) { 1724 return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) || 1725 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation); 1726 } 1727 isLandscapeClassRotation(String rotation)1728 private boolean isLandscapeClassRotation(String rotation) { 1729 return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) || 1730 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation); 1731 } 1732 1733 /** 1734 * Install dEQP OnDevice Package 1735 */ installTestApk()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 */ uninstallTestApk()1752 private void uninstallTestApk() throws DeviceNotAvailableException { 1753 getDevice().uninstallPackage(DEQP_ONDEVICE_PKG); 1754 } 1755 1756 /** 1757 * Parse gl nature from package name 1758 */ isOpenGlEsPackage()1759 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; 1765 } else { 1766 throw new IllegalStateException("dEQP runner was created with illegal name"); 1767 } 1768 } 1769 1770 /** 1771 * Check GL support (based on package name) 1772 */ isSupportedGles()1773 private boolean isSupportedGles() throws DeviceNotAvailableException { 1774 return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion()); 1775 } 1776 1777 /** 1778 * Get GL major version (based on package name) 1779 */ getGlesMajorVersion()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 */ getGlesMinorVersion()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"); 1804 } 1805 } 1806 1807 /** 1808 * {@inheritDoc} 1809 */ 1810 @Override run(ITestInvocationListener listener)1811 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 1812 final Map<String, String> emptyMap = Collections.emptyMap(); 1813 final boolean isSupportedApi = !isOpenGlEsPackage() || isSupportedGles(); 1814 1815 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); 1824 mDeviceRecovery.setDevice(mDevice); 1825 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); 1840 } 1841 } 1842