DeqpTestRunner.java revision 41c41d5d3d501f716954c8f4001c92000411ae34
1package com.android.cts.tradefed.testtype; 2 3import com.android.cts.tradefed.build.CtsBuildHelper; 4import com.android.cts.util.AbiUtils; 5import com.android.ddmlib.AdbCommandRejectedException; 6import com.android.ddmlib.IShellOutputReceiver; 7import com.android.ddmlib.MultiLineReceiver; 8import com.android.ddmlib.ShellCommandUnresponsiveException; 9import com.android.ddmlib.TimeoutException; 10import com.android.ddmlib.testrunner.TestIdentifier; 11import com.android.tradefed.build.IBuildInfo; 12import com.android.tradefed.device.DeviceNotAvailableException; 13import com.android.tradefed.device.ITestDevice; 14import com.android.tradefed.log.LogUtil.CLog; 15import com.android.tradefed.result.ByteArrayInputStreamSource; 16import com.android.tradefed.result.ITestInvocationListener; 17import com.android.tradefed.result.LogDataType; 18import com.android.tradefed.testtype.IAbi; 19import com.android.tradefed.testtype.IBuildReceiver; 20import com.android.tradefed.testtype.IDeviceTest; 21import com.android.tradefed.testtype.IRemoteTest; 22 23import java.io.File; 24import java.io.FileNotFoundException; 25import java.io.IOException; 26import java.lang.reflect.Method; 27import java.lang.reflect.InvocationTargetException; 28import java.util.ArrayList; 29import java.util.Collection; 30import java.util.Collections; 31import java.util.HashMap; 32import java.util.HashSet; 33import java.util.Iterator; 34import java.util.LinkedHashMap; 35import java.util.LinkedHashSet; 36import java.util.LinkedList; 37import java.util.List; 38import java.util.Map; 39import java.util.Set; 40import java.util.concurrent.TimeUnit; 41 42/** 43 * Test runner for dEQP tests 44 * 45 * Supports running drawElements Quality Program tests found under external/deqp. 46 */ 47public class DeqpTestRunner implements IBuildReceiver, IDeviceTest, IRemoteTest { 48 49 private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk"; 50 private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp"; 51 private static final String INCOMPLETE_LOG_MESSAGE = "Crash: Incomplete test log"; 52 private static final String DEVICE_LOST_MESSAGE = "Crash: Device lost"; 53 private static final String SKIPPED_INSTANCE_LOG_MESSAGE = "Configuration skipped"; 54 private static final String CASE_LIST_FILE_NAME = "/sdcard/dEQP-TestCaseList.txt"; 55 private static final String LOG_FILE_NAME = "/sdcard/TestLog.qpa"; 56 public static final String FEATURE_LANDSCAPE = "android.hardware.screen.landscape"; 57 public static final String FEATURE_PORTRAIT = "android.hardware.screen.portrait"; 58 59 private static final int TESTCASE_BATCH_LIMIT = 1000; 60 private static final BatchRunConfiguration DEFAULT_CONFIG = 61 new BatchRunConfiguration("rgba8888d24s8", "unspecified", "window"); 62 63 private static final int UNRESPOSIVE_CMD_TIMEOUT_MS = 60000; // one minute 64 65 private final String mPackageName; 66 private final String mName; 67 private final Collection<TestIdentifier> mRemainingTests; 68 private final Map<TestIdentifier, Set<BatchRunConfiguration>> mTestInstances; 69 private final TestInstanceResultListener mInstanceListerner = new TestInstanceResultListener(); 70 private IAbi mAbi; 71 private CtsBuildHelper mCtsBuild; 72 private boolean mLogData = false; 73 private ITestDevice mDevice; 74 private Set<String> mDeviceFeatures; 75 private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>(); 76 77 private IRecovery mDeviceRecovery = new Recovery(); 78 { 79 mDeviceRecovery.setSleepProvider(new SleepProvider()); 80 } 81 82 public DeqpTestRunner(String packageName, String name, Collection<TestIdentifier> tests, 83 Map<TestIdentifier, List<Map<String,String>>> testInstances) { 84 mPackageName = packageName; 85 mName = name; 86 mRemainingTests = new LinkedList<>(tests); // avoid modifying arguments 87 mTestInstances = parseTestInstances(tests, testInstances); 88 } 89 90 /** 91 * @param abi the ABI to run the test on 92 */ 93 public void setAbi(IAbi abi) { 94 mAbi = abi; 95 } 96 97 /** 98 * {@inheritDoc} 99 */ 100 @Override 101 public void setBuild(IBuildInfo buildInfo) { 102 mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo); 103 } 104 105 /** 106 * Set the CTS build container. 107 * <p/> 108 * Exposed so unit tests can mock the provided build. 109 * 110 * @param buildHelper 111 */ 112 public void setBuildHelper(CtsBuildHelper buildHelper) { 113 mCtsBuild = buildHelper; 114 } 115 116 /** 117 * Enable or disable raw dEQP test log collection. 118 */ 119 public void setCollectLogs(boolean logData) { 120 mLogData = logData; 121 } 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public void setDevice(ITestDevice device) { 128 mDevice = device; 129 } 130 131 /** 132 * {@inheritDoc} 133 */ 134 @Override 135 public ITestDevice getDevice() { 136 return mDevice; 137 } 138 139 /** 140 * Set recovery handler. 141 * 142 * Exposed for unit testing. 143 */ 144 public void setRecovery(IRecovery deviceRecovery) { 145 mDeviceRecovery = deviceRecovery; 146 } 147 148 private static final class CapabilityQueryFailureException extends Exception { 149 }; 150 151 /** 152 * Test configuration of dEPQ test instance execution. 153 * Exposed for unit testing 154 */ 155 public static final class BatchRunConfiguration { 156 public static final String ROTATION_UNSPECIFIED = "unspecified"; 157 public static final String ROTATION_PORTRAIT = "0"; 158 public static final String ROTATION_LANDSCAPE = "90"; 159 public static final String ROTATION_REVERSE_PORTRAIT = "180"; 160 public static final String ROTATION_REVERSE_LANDSCAPE = "270"; 161 162 private final String mGlConfig; 163 private final String mRotation; 164 private final String mSurfaceType; 165 166 public BatchRunConfiguration(String glConfig, String rotation, String surfaceType) { 167 mGlConfig = glConfig; 168 mRotation = rotation; 169 mSurfaceType = surfaceType; 170 } 171 172 /** 173 * Get string that uniquely identifies this config 174 */ 175 public String getId() { 176 return String.format("{glformat=%s,rotation=%s,surfacetype=%s}", 177 mGlConfig, mRotation, mSurfaceType); 178 } 179 180 /** 181 * Get the GL config used in this configuration. 182 */ 183 public String getGlConfig() { 184 return mGlConfig; 185 } 186 187 /** 188 * Get the screen rotation used in this configuration. 189 */ 190 public String getRotation() { 191 return mRotation; 192 } 193 194 /** 195 * Get the surface type used in this configuration. 196 */ 197 public String getSurfaceType() { 198 return mSurfaceType; 199 } 200 201 @Override 202 public boolean equals(Object other) { 203 if (other == null) { 204 return false; 205 } else if (!(other instanceof BatchRunConfiguration)) { 206 return false; 207 } else { 208 return getId().equals(((BatchRunConfiguration)other).getId()); 209 } 210 } 211 212 @Override 213 public int hashCode() { 214 return getId().hashCode(); 215 } 216 } 217 218 /** 219 * dEQP test instance listerer and invocation result forwarded 220 */ 221 private class TestInstanceResultListener { 222 private ITestInvocationListener mSink; 223 private BatchRunConfiguration mRunConfig; 224 225 private TestIdentifier mCurrentTestId; 226 private boolean mGotTestResult; 227 private String mCurrentTestLog; 228 229 private class PendingResult 230 { 231 boolean allInstancesPassed; 232 Map<BatchRunConfiguration, String> testLogs; 233 Map<BatchRunConfiguration, String> errorMessages; 234 Set<BatchRunConfiguration> remainingConfigs; 235 }; 236 private final Map<TestIdentifier, PendingResult> mPendingResults = new HashMap<>(); 237 238 public void setSink(ITestInvocationListener sink) { 239 mSink = sink; 240 } 241 242 public void setCurrentConfig(BatchRunConfiguration runConfig) { 243 mRunConfig = runConfig; 244 } 245 246 /** 247 * Get currently processed test id, or null if not currently processing a test case 248 */ 249 public TestIdentifier getCurrentTestId() { 250 return mCurrentTestId; 251 } 252 253 /** 254 * Forward result to sink 255 */ 256 private void forwardFinalizedPendingResult() { 257 if (mRemainingTests.contains(mCurrentTestId)) { 258 final PendingResult result = mPendingResults.get(mCurrentTestId); 259 260 mPendingResults.remove(mCurrentTestId); 261 mRemainingTests.remove(mCurrentTestId); 262 263 // Forward results to the sink 264 mSink.testStarted(mCurrentTestId); 265 266 // Test Log 267 if (mLogData) { 268 for (Map.Entry<BatchRunConfiguration, String> entry : 269 result.testLogs.entrySet()) { 270 final ByteArrayInputStreamSource source 271 = new ByteArrayInputStreamSource(entry.getValue().getBytes()); 272 273 mSink.testLog(mCurrentTestId.getClassName() + "." 274 + mCurrentTestId.getTestName() + "@" + entry.getKey().getId(), 275 LogDataType.XML, source); 276 277 source.cancel(); 278 } 279 } 280 281 // Error message 282 if (!result.allInstancesPassed) { 283 final StringBuilder errorLog = new StringBuilder(); 284 285 for (Map.Entry<BatchRunConfiguration, String> entry : 286 result.errorMessages.entrySet()) { 287 if (errorLog.length() > 0) { 288 errorLog.append('\n'); 289 } 290 errorLog.append(String.format("=== with config %s ===\n", 291 entry.getKey().getId())); 292 errorLog.append(entry.getValue()); 293 } 294 295 mSink.testFailed(mCurrentTestId, errorLog.toString()); 296 } 297 298 final Map<String, String> emptyMap = Collections.emptyMap(); 299 mSink.testEnded(mCurrentTestId, emptyMap); 300 } 301 } 302 303 /** 304 * Declare existence of a test and instances 305 */ 306 public void setTestInstances(TestIdentifier testId, Set<BatchRunConfiguration> configs) { 307 // Test instances cannot change at runtime, ignore if we have already set this 308 if (!mPendingResults.containsKey(testId)) { 309 final PendingResult pendingResult = new PendingResult(); 310 pendingResult.allInstancesPassed = true; 311 pendingResult.testLogs = new LinkedHashMap<>(); 312 pendingResult.errorMessages = new LinkedHashMap<>(); 313 pendingResult.remainingConfigs = new HashSet<>(configs); // avoid mutating argument 314 mPendingResults.put(testId, pendingResult); 315 } 316 } 317 318 /** 319 * Query if test instance has not yet been executed 320 */ 321 public boolean isPendingTestInstance(TestIdentifier testId, 322 BatchRunConfiguration config) { 323 final PendingResult result = mPendingResults.get(testId); 324 if (result == null) { 325 // test is not in the current working batch of the runner, i.e. it cannot be 326 // "partially" completed. 327 if (!mRemainingTests.contains(testId)) { 328 // The test has been fully executed. Not pending. 329 return false; 330 } else { 331 // Test has not yet been executed. Check if such instance exists 332 return mTestInstances.get(testId).contains(config); 333 } 334 } else { 335 // could be partially completed, check this particular config 336 return result.remainingConfigs.contains(config); 337 } 338 } 339 340 /** 341 * Fake execution of an instance with current config 342 */ 343 public void skipTest(TestIdentifier testId) { 344 final PendingResult result = mPendingResults.get(testId); 345 346 result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE); 347 result.remainingConfigs.remove(mRunConfig); 348 349 if (result.remainingConfigs.isEmpty()) { 350 // fake as if we actually run the test 351 mCurrentTestId = testId; 352 forwardFinalizedPendingResult(); 353 mCurrentTestId = null; 354 } 355 } 356 357 /** 358 * Handles beginning of dEQP session. 359 */ 360 private void handleBeginSession(Map<String, String> values) { 361 // ignore 362 } 363 364 /** 365 * Handles end of dEQP session. 366 */ 367 private void handleEndSession(Map<String, String> values) { 368 // ignore 369 } 370 371 /** 372 * Handles beginning of dEQP testcase. 373 */ 374 private void handleBeginTestCase(Map<String, String> values) { 375 mCurrentTestId = pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath")); 376 mCurrentTestLog = ""; 377 mGotTestResult = false; 378 379 // mark instance as started 380 if (mPendingResults.get(mCurrentTestId) != null) { 381 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig); 382 } else { 383 CLog.w("Got unexpected start of %s", mCurrentTestId); 384 } 385 } 386 387 /** 388 * Handles end of dEQP testcase. 389 */ 390 private void handleEndTestCase(Map<String, String> values) { 391 final PendingResult result = mPendingResults.get(mCurrentTestId); 392 393 if (result != null) { 394 if (!mGotTestResult) { 395 result.allInstancesPassed = false; 396 result.errorMessages.put(mRunConfig, INCOMPLETE_LOG_MESSAGE); 397 } 398 399 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) { 400 result.testLogs.put(mRunConfig, mCurrentTestLog); 401 } 402 403 // Pending result finished, report result 404 if (result.remainingConfigs.isEmpty()) { 405 forwardFinalizedPendingResult(); 406 } 407 } else { 408 CLog.w("Got unexpected end of %s", mCurrentTestId); 409 } 410 mCurrentTestId = null; 411 } 412 413 /** 414 * Handles dEQP testcase result. 415 */ 416 private void handleTestCaseResult(Map<String, String> values) { 417 String code = values.get("dEQP-TestCaseResult-Code"); 418 String details = values.get("dEQP-TestCaseResult-Details"); 419 420 if (mPendingResults.get(mCurrentTestId) == null) { 421 CLog.w("Got unexpected result for %s", mCurrentTestId); 422 mGotTestResult = true; 423 return; 424 } 425 426 if (code.compareTo("Pass") == 0) { 427 mGotTestResult = true; 428 } else if (code.compareTo("NotSupported") == 0) { 429 mGotTestResult = true; 430 } else if (code.compareTo("QualityWarning") == 0) { 431 mGotTestResult = true; 432 } else if (code.compareTo("CompatibilityWarning") == 0) { 433 mGotTestResult = true; 434 } else if (code.compareTo("Fail") == 0 || code.compareTo("ResourceError") == 0 435 || code.compareTo("InternalError") == 0 || code.compareTo("Crash") == 0 436 || code.compareTo("Timeout") == 0) { 437 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 438 mPendingResults.get(mCurrentTestId) 439 .errorMessages.put(mRunConfig, code + ": " + details); 440 mGotTestResult = true; 441 } else { 442 String codeError = "Unknown result code: " + code; 443 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 444 mPendingResults.get(mCurrentTestId) 445 .errorMessages.put(mRunConfig, codeError + ": " + details); 446 mGotTestResult = true; 447 } 448 } 449 450 /** 451 * Handles terminated dEQP testcase. 452 */ 453 private void handleTestCaseTerminate(Map<String, String> values) { 454 final PendingResult result = mPendingResults.get(mCurrentTestId); 455 456 if (result != null) { 457 String reason = values.get("dEQP-TerminateTestCase-Reason"); 458 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 459 mPendingResults.get(mCurrentTestId) 460 .errorMessages.put(mRunConfig, "Terminated: " + reason); 461 462 // Pending result finished, report result 463 if (result.remainingConfigs.isEmpty()) { 464 forwardFinalizedPendingResult(); 465 } 466 } else { 467 CLog.w("Got unexpected termination of %s", mCurrentTestId); 468 } 469 470 mCurrentTestId = null; 471 mGotTestResult = true; 472 } 473 474 /** 475 * Handles dEQP testlog data. 476 */ 477 private void handleTestLogData(Map<String, String> values) { 478 mCurrentTestLog = mCurrentTestLog + values.get("dEQP-TestLogData-Log"); 479 } 480 481 /** 482 * Handles new instrumentation status message. 483 */ 484 public void handleStatus(Map<String, String> values) { 485 String eventType = values.get("dEQP-EventType"); 486 487 if (eventType == null) { 488 return; 489 } 490 491 if (eventType.compareTo("BeginSession") == 0) { 492 handleBeginSession(values); 493 } else if (eventType.compareTo("EndSession") == 0) { 494 handleEndSession(values); 495 } else if (eventType.compareTo("BeginTestCase") == 0) { 496 handleBeginTestCase(values); 497 } else if (eventType.compareTo("EndTestCase") == 0) { 498 handleEndTestCase(values); 499 } else if (eventType.compareTo("TestCaseResult") == 0) { 500 handleTestCaseResult(values); 501 } else if (eventType.compareTo("TerminateTestCase") == 0) { 502 handleTestCaseTerminate(values); 503 } else if (eventType.compareTo("TestLogData") == 0) { 504 handleTestLogData(values); 505 } 506 } 507 508 /** 509 * Signal listener that batch ended to flush incomplete results. 510 */ 511 public void endBatch() { 512 // end open test if when stream ends 513 if (mCurrentTestId != null) { 514 final Map<String, String> emptyMap = Collections.emptyMap(); 515 handleEndTestCase(emptyMap); 516 } 517 } 518 519 /** 520 * Signal listener that device just died. 521 */ 522 public void onDeviceLost() { 523 if (mCurrentTestId != null) { 524 final PendingResult result = mPendingResults.get(mCurrentTestId); 525 526 if (result == null) { 527 CLog.e("Device lost in invalid state: %s", mCurrentTestId); 528 return; 529 } 530 531 // kill current test 532 result.allInstancesPassed = false; 533 result.errorMessages.put(mRunConfig, DEVICE_LOST_MESSAGE); 534 535 if (mLogData && mCurrentTestLog != null && mCurrentTestLog.length() > 0) { 536 result.testLogs.put(mRunConfig, mCurrentTestLog); 537 } 538 539 // finish all pending instances 540 result.remainingConfigs.clear(); 541 forwardFinalizedPendingResult(); 542 mCurrentTestId = null; 543 } 544 } 545 } 546 547 /** 548 * dEQP instrumentation parser 549 */ 550 private static class InstrumentationParser extends MultiLineReceiver { 551 private TestInstanceResultListener mListener; 552 553 private Map<String, String> mValues; 554 private String mCurrentName; 555 private String mCurrentValue; 556 557 558 public InstrumentationParser(TestInstanceResultListener listener) { 559 mListener = listener; 560 } 561 562 /** 563 * {@inheritDoc} 564 */ 565 @Override 566 public void processNewLines(String[] lines) { 567 for (String line : lines) { 568 if (mValues == null) mValues = new HashMap<String, String>(); 569 570 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) { 571 if (mCurrentName != null) { 572 mValues.put(mCurrentName, mCurrentValue); 573 574 mCurrentName = null; 575 mCurrentValue = null; 576 } 577 578 mListener.handleStatus(mValues); 579 mValues = null; 580 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) { 581 if (mCurrentName != null) { 582 mValues.put(mCurrentName, mCurrentValue); 583 584 mCurrentValue = null; 585 mCurrentName = null; 586 } 587 588 String prefix = "INSTRUMENTATION_STATUS: "; 589 int nameBegin = prefix.length(); 590 int nameEnd = line.indexOf('='); 591 int valueBegin = nameEnd + 1; 592 593 mCurrentName = line.substring(nameBegin, nameEnd); 594 mCurrentValue = line.substring(valueBegin); 595 } else if (mCurrentValue != null) { 596 mCurrentValue = mCurrentValue + line; 597 } 598 } 599 } 600 601 /** 602 * {@inheritDoc} 603 */ 604 @Override 605 public void done() { 606 if (mCurrentName != null) { 607 mValues.put(mCurrentName, mCurrentValue); 608 609 mCurrentName = null; 610 mCurrentValue = null; 611 } 612 613 if (mValues != null) { 614 mListener.handleStatus(mValues); 615 mValues = null; 616 } 617 } 618 619 /** 620 * {@inheritDoc} 621 */ 622 @Override 623 public boolean isCancelled() { 624 return false; 625 } 626 } 627 628 /** 629 * dEQP platfom query instrumentation parser 630 */ 631 private static class PlatformQueryInstrumentationParser extends MultiLineReceiver { 632 private Map<String,String> mResultMap = new LinkedHashMap<>(); 633 private int mResultCode; 634 private boolean mGotExitValue = false; 635 636 /** 637 * {@inheritDoc} 638 */ 639 @Override 640 public void processNewLines(String[] lines) { 641 for (String line : lines) { 642 if (line.startsWith("INSTRUMENTATION_RESULT: ")) { 643 final String parts[] = line.substring(24).split("=",2); 644 if (parts.length == 2) { 645 mResultMap.put(parts[0], parts[1]); 646 } else { 647 CLog.w("Instrumentation status format unexpected"); 648 } 649 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) { 650 try { 651 mResultCode = Integer.parseInt(line.substring(22)); 652 mGotExitValue = true; 653 } catch (NumberFormatException ex) { 654 CLog.w("Instrumentation code format unexpected"); 655 } 656 } 657 } 658 } 659 660 /** 661 * {@inheritDoc} 662 */ 663 @Override 664 public boolean isCancelled() { 665 return false; 666 } 667 668 public boolean wasSuccessful() { 669 return mGotExitValue; 670 } 671 672 public int getResultCode() { 673 return mResultCode; 674 } 675 676 public Map<String,String> getResultMap() { 677 return mResultMap; 678 } 679 } 680 681 /** 682 * Interface for sleeping. 683 * 684 * Exposed for unit testing 685 */ 686 public static interface ISleepProvider { 687 public void sleep(int milliseconds); 688 }; 689 690 private static class SleepProvider implements ISleepProvider { 691 public void sleep(int milliseconds) { 692 try { 693 Thread.sleep(milliseconds); 694 } catch (InterruptedException ex) { 695 } 696 } 697 }; 698 699 /** 700 * Interface for failure recovery. 701 * 702 * Exposed for unit testing 703 */ 704 public static interface IRecovery { 705 /** 706 * Sets the sleep provider IRecovery works on 707 */ 708 public void setSleepProvider(ISleepProvider sleepProvider); 709 710 /** 711 * Sets the device IRecovery works on 712 */ 713 public void setDevice(ITestDevice device); 714 715 /** 716 * Informs Recovery that test execution has progressed since the last recovery 717 */ 718 public void onExecutionProgressed(); 719 720 /** 721 * Tries to recover device after failed refused connection. 722 * 723 * @throws DeviceNotAvailableException if recovery did not succeed 724 */ 725 public void recoverConnectionRefused() throws DeviceNotAvailableException; 726 727 /** 728 * Tries to recover device after abnormal execution termination or link failure. 729 * 730 * @param progressedSinceLastCall true if test execution has progressed since last call 731 * @throws DeviceNotAvailableException if recovery did not succeed 732 */ 733 public void recoverComLinkKilled() throws DeviceNotAvailableException; 734 }; 735 736 /** 737 * State machine for execution failure recovery. 738 * 739 * Exposed for unit testing 740 */ 741 public static class Recovery implements IRecovery { 742 private int RETRY_COOLDOWN_MS = 6000; // 6 seconds 743 private int PROCESS_KILL_WAIT_MS = 1000; // 1 second 744 745 private static enum MachineState { 746 WAIT, // recover by waiting 747 RECOVER, // recover by calling recover() 748 REBOOT, // recover by rebooting 749 FAIL, // cannot recover 750 }; 751 752 private MachineState mState = MachineState.WAIT; 753 private ITestDevice mDevice; 754 private ISleepProvider mSleepProvider; 755 756 private static class ProcessKillFailureException extends Exception { 757 } 758 759 /** 760 * {@inheritDoc} 761 */ 762 public void setSleepProvider(ISleepProvider sleepProvider) { 763 mSleepProvider = sleepProvider; 764 } 765 766 /** 767 * {@inheritDoc} 768 */ 769 @Override 770 public void setDevice(ITestDevice device) { 771 mDevice = device; 772 } 773 774 /** 775 * {@inheritDoc} 776 */ 777 @Override 778 public void onExecutionProgressed() { 779 mState = MachineState.WAIT; 780 } 781 782 /** 783 * {@inheritDoc} 784 */ 785 @Override 786 public void recoverConnectionRefused() throws DeviceNotAvailableException { 787 switch (mState) { 788 case WAIT: // not a valid stratedy for connection refusal, fallthrough 789 case RECOVER: 790 // First failure, just try to recover 791 CLog.w("ADB connection failed, trying to recover"); 792 mState = MachineState.REBOOT; // the next step is to reboot 793 794 try { 795 recoverDevice(); 796 } catch (DeviceNotAvailableException ex) { 797 // chain forward 798 recoverConnectionRefused(); 799 } 800 break; 801 802 case REBOOT: 803 // Second failure in a row, try to reboot 804 CLog.w("ADB connection failed after recovery, rebooting device"); 805 mState = MachineState.FAIL; // the next step is to fail 806 807 try { 808 rebootDevice(); 809 } catch (DeviceNotAvailableException ex) { 810 // chain forward 811 recoverConnectionRefused(); 812 } 813 break; 814 815 case FAIL: 816 // Third failure in a row, just fail 817 CLog.w("Cannot recover ADB connection"); 818 throw new DeviceNotAvailableException("failed to connect after reboot"); 819 } 820 } 821 822 /** 823 * {@inheritDoc} 824 */ 825 @Override 826 public void recoverComLinkKilled() throws DeviceNotAvailableException { 827 switch (mState) { 828 case WAIT: 829 // First failure, just try to wait and try again 830 CLog.w("ADB link failed, retrying after a cooldown period"); 831 mState = MachineState.RECOVER; // the next step is to recover the device 832 833 waitCooldown(); 834 835 // even if the link to deqp on-device process was killed, the process might 836 // still be alive. Locate and terminate such unwanted processes. 837 try { 838 killDeqpProcess(); 839 } catch (DeviceNotAvailableException ex) { 840 // chain forward 841 recoverComLinkKilled(); 842 } catch (ProcessKillFailureException ex) { 843 // chain forward 844 recoverComLinkKilled(); 845 } 846 break; 847 848 case RECOVER: 849 // Second failure, just try to recover 850 CLog.w("ADB link failed, trying to recover"); 851 mState = MachineState.REBOOT; // the next step is to reboot 852 853 try { 854 recoverDevice(); 855 killDeqpProcess(); 856 } catch (DeviceNotAvailableException ex) { 857 // chain forward 858 recoverComLinkKilled(); 859 } catch (ProcessKillFailureException ex) { 860 // chain forward 861 recoverComLinkKilled(); 862 } 863 break; 864 865 case REBOOT: 866 // Third failure in a row, try to reboot 867 CLog.w("ADB link failed after recovery, rebooting device"); 868 mState = MachineState.FAIL; // the next step is to fail 869 870 try { 871 rebootDevice(); 872 } catch (DeviceNotAvailableException ex) { 873 // chain forward 874 recoverComLinkKilled(); 875 } 876 break; 877 878 case FAIL: 879 // Fourth failure in a row, just fail 880 CLog.w("Cannot recover ADB connection"); 881 throw new DeviceNotAvailableException("link killed after reboot"); 882 } 883 } 884 885 private void waitCooldown() { 886 mSleepProvider.sleep(RETRY_COOLDOWN_MS); 887 } 888 889 private Iterable<Integer> getDeqpProcessPids() throws DeviceNotAvailableException { 890 final List<Integer> pids = new ArrayList<Integer>(2); 891 final String processes = mDevice.executeShellCommand("ps | grep com.drawelements"); 892 final String[] lines = processes.split("(\\r|\\n)+"); 893 for (String line : lines) { 894 final String[] fields = line.split("\\s+"); 895 if (fields.length < 2) { 896 continue; 897 } 898 899 try { 900 final int processId = Integer.parseInt(fields[1], 10); 901 pids.add(processId); 902 } catch (NumberFormatException ex) { 903 continue; 904 } 905 } 906 return pids; 907 } 908 909 private void killDeqpProcess() throws DeviceNotAvailableException, 910 ProcessKillFailureException { 911 for (Integer processId : getDeqpProcessPids()) { 912 mDevice.executeShellCommand(String.format("kill -9 %d", processId)); 913 } 914 915 mSleepProvider.sleep(PROCESS_KILL_WAIT_MS); 916 917 // check that processes actually died 918 if (getDeqpProcessPids().iterator().hasNext()) { 919 // a process is still alive, killing failed 920 throw new ProcessKillFailureException(); 921 } 922 } 923 924 public void recoverDevice() throws DeviceNotAvailableException { 925 // Work around the API. We need to call recoverDevice() on the test device and 926 // we know that mDevice is a TestDevice. However even though the recoverDevice() 927 // method is public suggesting it should be publicly accessible, the class itself 928 // and its super-interface (IManagedTestDevice) are package-private. 929 final Method recoverDeviceMethod; 930 try { 931 recoverDeviceMethod = mDevice.getClass().getMethod("recoverDevice"); 932 recoverDeviceMethod.setAccessible(true); 933 } catch (NoSuchMethodException ex) { 934 throw new AssertionError("Test device must have recoverDevice()"); 935 } 936 937 try { 938 recoverDeviceMethod.invoke(mDevice); 939 } catch (InvocationTargetException ex) { 940 if (ex.getCause() instanceof DeviceNotAvailableException) { 941 throw (DeviceNotAvailableException)ex.getCause(); 942 } else if (ex.getCause() instanceof RuntimeException) { 943 throw (RuntimeException)ex.getCause(); 944 } else { 945 throw new AssertionError("unexpected throw", ex); 946 } 947 } catch (IllegalAccessException ex) { 948 throw new AssertionError("unexpected throw", ex); 949 } 950 } 951 952 private void rebootDevice() throws DeviceNotAvailableException { 953 mDevice.reboot(); 954 } 955 }; 956 957 /** 958 * Parse map of instance arguments to map of BatchRunConfigurations 959 */ 960 private static Map<TestIdentifier, Set<BatchRunConfiguration>> parseTestInstances( 961 Collection<TestIdentifier> tests, 962 Map<TestIdentifier, List<Map<String,String>>> testInstances) { 963 final Map<TestIdentifier, Set<BatchRunConfiguration>> instances = new HashMap<>(); 964 for (final TestIdentifier test : tests) { 965 final Set<BatchRunConfiguration> testInstanceSet = new LinkedHashSet<>(); 966 if (testInstances.get(test).isEmpty()) { 967 // no instances defined, use default 968 testInstanceSet.add(DEFAULT_CONFIG); 969 } else { 970 for (Map<String, String> instanceArgs : testInstances.get(test)) { 971 testInstanceSet.add(parseRunConfig(instanceArgs)); 972 } 973 } 974 instances.put(test, testInstanceSet); 975 } 976 return instances; 977 } 978 979 private static BatchRunConfiguration parseRunConfig(Map<String,String> instanceArguments) { 980 final String glConfig; 981 final String rotation; 982 final String surfaceType; 983 984 if (instanceArguments.containsKey("glconfig")) { 985 glConfig = instanceArguments.get("glconfig"); 986 } else { 987 glConfig = DEFAULT_CONFIG.getGlConfig(); 988 } 989 if (instanceArguments.containsKey("rotation")) { 990 rotation = instanceArguments.get("rotation"); 991 } else { 992 rotation = DEFAULT_CONFIG.getRotation(); 993 } 994 if (instanceArguments.containsKey("surfaceType")) { 995 surfaceType = instanceArguments.get("surfaceType"); 996 } else { 997 surfaceType = DEFAULT_CONFIG.getSurfaceType(); 998 } 999 1000 return new BatchRunConfiguration(glConfig, rotation, surfaceType); 1001 } 1002 1003 private Set<BatchRunConfiguration> getTestRunConfigs (TestIdentifier testId) { 1004 return mTestInstances.get(testId); 1005 } 1006 1007 /** 1008 * Converts dEQP testcase path to TestIdentifier. 1009 */ 1010 private static TestIdentifier pathToIdentifier(String testPath) { 1011 String[] components = testPath.split("\\."); 1012 String name = components[components.length - 1]; 1013 String className = null; 1014 1015 for (int i = 0; i < components.length - 1; i++) { 1016 if (className == null) { 1017 className = components[i]; 1018 } else { 1019 className = className + "." + components[i]; 1020 } 1021 } 1022 1023 return new TestIdentifier(className, name); 1024 } 1025 1026 private String getId() { 1027 return AbiUtils.createId(mAbi.getName(), mPackageName); 1028 } 1029 1030 /** 1031 * Generates tescase trie from dEQP testcase paths. Used to define which testcases to execute. 1032 */ 1033 private static String generateTestCaseTrieFromPaths(Collection<String> tests) { 1034 String result = "{"; 1035 boolean first = true; 1036 1037 // Add testcases to results 1038 for (Iterator<String> iter = tests.iterator(); iter.hasNext();) { 1039 String test = iter.next(); 1040 String[] components = test.split("\\."); 1041 1042 if (components.length == 1) { 1043 if (!first) { 1044 result = result + ","; 1045 } 1046 first = false; 1047 1048 result += components[0]; 1049 iter.remove(); 1050 } 1051 } 1052 1053 if (!tests.isEmpty()) { 1054 HashMap<String, ArrayList<String> > testGroups = new HashMap<>(); 1055 1056 // Collect all sub testgroups 1057 for (String test : tests) { 1058 String[] components = test.split("\\."); 1059 ArrayList<String> testGroup = testGroups.get(components[0]); 1060 1061 if (testGroup == null) { 1062 testGroup = new ArrayList<String>(); 1063 testGroups.put(components[0], testGroup); 1064 } 1065 1066 testGroup.add(test.substring(components[0].length()+1)); 1067 } 1068 1069 for (String testGroup : testGroups.keySet()) { 1070 if (!first) { 1071 result = result + ","; 1072 } 1073 1074 first = false; 1075 result = result + testGroup 1076 + generateTestCaseTrieFromPaths(testGroups.get(testGroup)); 1077 } 1078 } 1079 1080 return result + "}"; 1081 } 1082 1083 /** 1084 * Generates testcase trie from TestIdentifiers. 1085 */ 1086 private static String generateTestCaseTrie(Collection<TestIdentifier> tests) { 1087 ArrayList<String> testPaths = new ArrayList<String>(); 1088 1089 for (TestIdentifier test : tests) { 1090 testPaths.add(test.getClassName() + "." + test.getTestName()); 1091 } 1092 1093 return generateTestCaseTrieFromPaths(testPaths); 1094 } 1095 1096 /** 1097 * Executes tests on the device. 1098 */ 1099 private void runTests() throws DeviceNotAvailableException, CapabilityQueryFailureException { 1100 mDeviceRecovery.setDevice(mDevice); 1101 1102 while (!mRemainingTests.isEmpty()) { 1103 // select tests for the batch 1104 final ArrayList<TestIdentifier> batchTests = new ArrayList<>(TESTCASE_BATCH_LIMIT); 1105 for (TestIdentifier test : mRemainingTests) { 1106 batchTests.add(test); 1107 if (batchTests.size() >= TESTCASE_BATCH_LIMIT) { 1108 break; 1109 } 1110 } 1111 1112 // find union of all run configurations 1113 final Set<BatchRunConfiguration> allConfigs = new LinkedHashSet<>(); 1114 for (TestIdentifier test : batchTests) { 1115 allConfigs.addAll(getTestRunConfigs(test)); 1116 } 1117 1118 // prepare instance listener 1119 for (TestIdentifier test : batchTests) { 1120 mInstanceListerner.setTestInstances(test, getTestRunConfigs(test)); 1121 } 1122 1123 // run batch for all configurations 1124 for (BatchRunConfiguration runConfig : allConfigs) { 1125 final ArrayList<TestIdentifier> relevantTests = 1126 new ArrayList<>(TESTCASE_BATCH_LIMIT); 1127 1128 // run only for declared run configs and only if test has not already 1129 // been attempted to run 1130 for (TestIdentifier test : batchTests) { 1131 if (mInstanceListerner.isPendingTestInstance(test, runConfig)) { 1132 relevantTests.add(test); 1133 } 1134 } 1135 1136 if (!relevantTests.isEmpty()) { 1137 runTestRunBatch(relevantTests, runConfig); 1138 } 1139 } 1140 } 1141 } 1142 1143 private void runTestRunBatch(Collection<TestIdentifier> tests, BatchRunConfiguration runConfig) 1144 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1145 boolean isSupportedConfig = true; 1146 1147 // orientation support 1148 if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals(runConfig.getRotation())) { 1149 final Set<String> features = getDeviceFeatures(mDevice); 1150 1151 if (isPortraitClassRotation(runConfig.getRotation()) && 1152 !features.contains(FEATURE_PORTRAIT)) { 1153 isSupportedConfig = false; 1154 } 1155 if (isLandscapeClassRotation(runConfig.getRotation()) && 1156 !features.contains(FEATURE_LANDSCAPE)) { 1157 isSupportedConfig = false; 1158 } 1159 } 1160 1161 // renderability support for OpenGL ES tests 1162 if (isSupportedConfig && isOpenGlEsPackage()) { 1163 isSupportedConfig = isSupportedGlesRenderConfig(runConfig); 1164 } 1165 1166 mInstanceListerner.setCurrentConfig(runConfig); 1167 1168 // execute only if config is executable, else fake results 1169 if (isSupportedConfig) { 1170 executeTestRunBatch(tests, runConfig); 1171 } else { 1172 fakePassTestRunBatch(tests, runConfig); 1173 } 1174 } 1175 1176 private static final class AdbComLinkOpenError extends Exception { 1177 public AdbComLinkOpenError(String description, Throwable inner) { 1178 super(description, inner); 1179 } 1180 }; 1181 private static final class AdbComLinkKilledError extends Exception { 1182 public AdbComLinkKilledError(String description, Throwable inner) { 1183 super(description, inner); 1184 } 1185 }; 1186 1187 /** 1188 * Executes a given command in adb shell 1189 * 1190 * @throws AdbComLinkOpenError if connection cannot be established. 1191 * @throws AdbComLinkKilledError if established connection is killed prematurely. 1192 */ 1193 private void executeShellCommandAndReadOutput(final String command, 1194 final IShellOutputReceiver receiver) 1195 throws AdbComLinkOpenError, AdbComLinkKilledError { 1196 try { 1197 mDevice.getIDevice().executeShellCommand(command, receiver, 1198 UNRESPOSIVE_CMD_TIMEOUT_MS, TimeUnit.MILLISECONDS); 1199 } catch (TimeoutException ex) { 1200 // Opening connection timed out 1201 throw new AdbComLinkOpenError("opening connection timed out", ex); 1202 } catch (AdbCommandRejectedException ex) { 1203 // Command rejected 1204 throw new AdbComLinkOpenError("command rejected", ex); 1205 } catch (IOException ex) { 1206 // shell command channel killed 1207 throw new AdbComLinkKilledError("command link killed", ex); 1208 } catch (ShellCommandUnresponsiveException ex) { 1209 // shell command halted 1210 throw new AdbComLinkKilledError("command link hung", ex); 1211 } 1212 } 1213 1214 private void executeTestRunBatch(Collection<TestIdentifier> tests, 1215 BatchRunConfiguration runConfig) throws DeviceNotAvailableException { 1216 final String testCases = generateTestCaseTrie(tests); 1217 1218 mDevice.executeShellCommand("rm " + CASE_LIST_FILE_NAME); 1219 mDevice.executeShellCommand("rm " + LOG_FILE_NAME); 1220 mDevice.pushString(testCases + "\n", CASE_LIST_FILE_NAME); 1221 1222 final String instrumentationName = 1223 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation"; 1224 1225 final StringBuilder deqpCmdLine = new StringBuilder(); 1226 deqpCmdLine.append("--deqp-caselist-file="); 1227 deqpCmdLine.append(CASE_LIST_FILE_NAME); 1228 deqpCmdLine.append(" "); 1229 deqpCmdLine.append(getRunConfigDisplayCmdLine(runConfig)); 1230 1231 // If we are not logging data, do not bother outputting the images from the test exe. 1232 if (!mLogData) { 1233 deqpCmdLine.append(" --deqp-log-images=disable"); 1234 } 1235 1236 deqpCmdLine.append(" --deqp-watchdog=enable"); 1237 1238 final String command = String.format( 1239 "am instrument %s -w -e deqpLogFileName \"%s\" -e deqpCmdLine \"%s\"" 1240 + " -e deqpLogData \"%s\" %s", 1241 AbiUtils.createAbiFlag(mAbi.getName()), LOG_FILE_NAME, deqpCmdLine.toString(), 1242 mLogData, instrumentationName); 1243 1244 final int numRemainingInstancesBefore = getNumRemainingInstances(); 1245 final InstrumentationParser parser = new InstrumentationParser(mInstanceListerner); 1246 Throwable interruptingError = null; 1247 1248 try { 1249 executeShellCommandAndReadOutput(command, parser); 1250 } catch (Throwable ex) { 1251 interruptingError = ex; 1252 } finally { 1253 parser.flush(); 1254 } 1255 1256 try { 1257 final boolean progressedSinceLastCall = 1258 mInstanceListerner.getCurrentTestId() != null || 1259 getNumRemainingInstances() < numRemainingInstancesBefore; 1260 1261 if (progressedSinceLastCall) { 1262 mDeviceRecovery.onExecutionProgressed(); 1263 } 1264 1265 if (interruptingError == null) { 1266 // execution finished successfully, do nothing 1267 } else if (interruptingError instanceof AdbComLinkOpenError) { 1268 mDeviceRecovery.recoverConnectionRefused(); 1269 } else if (interruptingError instanceof AdbComLinkKilledError) { 1270 mDeviceRecovery.recoverComLinkKilled(); 1271 } else { 1272 CLog.e(interruptingError); 1273 throw new RuntimeException(interruptingError); 1274 } 1275 } catch (DeviceNotAvailableException ex) { 1276 // Device lost. We must signal the tradedef by rethrowing this execption. However, 1277 // there is a possiblity that the device loss was caused by the currently run test 1278 // instance. Since CtsTest is unaware of tests with only some instances executed, 1279 // continuing the session after device has recovered will create a new DeqpTestRunner 1280 // with current test in its run queue and this will cause the re-execution of this same 1281 // instance. If the instance reliably can kill the device, the CTS cannot recover. 1282 // 1283 // Prevent this by terminating ALL instances of a tests if any of them causes a device 1284 // loss. 1285 mInstanceListerner.onDeviceLost(); 1286 throw ex; 1287 } finally { 1288 mInstanceListerner.endBatch(); 1289 } 1290 } 1291 1292 private static String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) { 1293 final StringBuilder deqpCmdLine = new StringBuilder(); 1294 if (!runConfig.getGlConfig().isEmpty()) { 1295 deqpCmdLine.append("--deqp-gl-config-name="); 1296 deqpCmdLine.append(runConfig.getGlConfig()); 1297 } 1298 if (!runConfig.getRotation().isEmpty()) { 1299 if (deqpCmdLine.length() != 0) { 1300 deqpCmdLine.append(" "); 1301 } 1302 deqpCmdLine.append("--deqp-screen-rotation="); 1303 deqpCmdLine.append(runConfig.getRotation()); 1304 } 1305 if (!runConfig.getSurfaceType().isEmpty()) { 1306 if (deqpCmdLine.length() != 0) { 1307 deqpCmdLine.append(" "); 1308 } 1309 deqpCmdLine.append("--deqp-surface-type="); 1310 deqpCmdLine.append(runConfig.getSurfaceType()); 1311 } 1312 return deqpCmdLine.toString(); 1313 } 1314 1315 private int getNumRemainingInstances() { 1316 int retVal = 0; 1317 for (TestIdentifier testId : mRemainingTests) { 1318 // If case is in current working set, sum only not yet executed instances. 1319 // If case is not in current working set, sum all instances (since they are not yet 1320 // executed). 1321 if (mInstanceListerner.mPendingResults.containsKey(testId)) { 1322 retVal += mInstanceListerner.mPendingResults.get(testId).remainingConfigs.size(); 1323 } else { 1324 retVal += mTestInstances.get(testId).size(); 1325 } 1326 } 1327 return retVal; 1328 } 1329 1330 /** 1331 * Pass given batch tests without running it 1332 */ 1333 private void fakePassTestRunBatch(Collection<TestIdentifier> tests, 1334 BatchRunConfiguration runConfig) { 1335 for (TestIdentifier test : tests) { 1336 CLog.d("Skipping test '%s' invocation in config '%s'", test.toString(), 1337 runConfig.getId()); 1338 mInstanceListerner.skipTest(test); 1339 } 1340 } 1341 1342 /** 1343 * Pass all remaining tests without running them 1344 */ 1345 private void fakePassTests(ITestInvocationListener listener) { 1346 Map <String, String> emptyMap = Collections.emptyMap(); 1347 for (TestIdentifier test : mRemainingTests) { 1348 CLog.d("Skipping test '%s', Opengl ES version not supported", test.toString()); 1349 listener.testStarted(test); 1350 listener.testEnded(test, emptyMap); 1351 } 1352 mRemainingTests.clear(); 1353 } 1354 1355 /** 1356 * Check if device supports OpenGL ES version. 1357 */ 1358 private static boolean isSupportedGles(ITestDevice device, int requiredMajorVersion, 1359 int requiredMinorVersion) throws DeviceNotAvailableException { 1360 String roOpenglesVersion = device.getProperty("ro.opengles.version"); 1361 1362 if (roOpenglesVersion == null) 1363 return false; 1364 1365 int intValue = Integer.parseInt(roOpenglesVersion); 1366 1367 int majorVersion = ((intValue & 0xffff0000) >> 16); 1368 int minorVersion = (intValue & 0xffff); 1369 1370 return (majorVersion > requiredMajorVersion) 1371 || (majorVersion == requiredMajorVersion && minorVersion >= requiredMinorVersion); 1372 } 1373 1374 /** 1375 * Query if rendertarget is supported 1376 */ 1377 private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig) 1378 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1379 // query if configuration is supported 1380 final StringBuilder configCommandLine = 1381 new StringBuilder(getRunConfigDisplayCmdLine(runConfig)); 1382 if (configCommandLine.length() != 0) { 1383 configCommandLine.append(" "); 1384 } 1385 configCommandLine.append("--deqp-gl-major-version="); 1386 configCommandLine.append(getGlesMajorVersion()); 1387 configCommandLine.append(" --deqp-gl-minor-version="); 1388 configCommandLine.append(getGlesMinorVersion()); 1389 1390 final String commandLine = configCommandLine.toString(); 1391 1392 // check for cached result first 1393 if (mConfigQuerySupportCache.containsKey(commandLine)) { 1394 return mConfigQuerySupportCache.get(commandLine); 1395 } 1396 1397 final boolean supported = queryIsSupportedConfigCommandLine(commandLine); 1398 mConfigQuerySupportCache.put(commandLine, supported); 1399 return supported; 1400 } 1401 1402 private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine) 1403 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1404 final String instrumentationName = 1405 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation"; 1406 final String command = String.format( 1407 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\"" 1408 + " %s", 1409 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, instrumentationName); 1410 1411 final PlatformQueryInstrumentationParser parser = new PlatformQueryInstrumentationParser(); 1412 mDevice.executeShellCommand(command, parser); 1413 parser.flush(); 1414 1415 if (parser.wasSuccessful() && parser.getResultCode() == 0 && 1416 parser.getResultMap().containsKey("Supported")) { 1417 if ("Yes".equals(parser.getResultMap().get("Supported"))) { 1418 return true; 1419 } else if ("No".equals(parser.getResultMap().get("Supported"))) { 1420 return false; 1421 } else { 1422 CLog.e("Capability query did not return a result"); 1423 throw new CapabilityQueryFailureException(); 1424 } 1425 } else if (parser.wasSuccessful()) { 1426 CLog.e("Failed to run capability query. Code: %d, Result: %s", 1427 parser.getResultCode(), parser.getResultMap().toString()); 1428 throw new CapabilityQueryFailureException(); 1429 } else { 1430 CLog.e("Failed to run capability query"); 1431 throw new CapabilityQueryFailureException(); 1432 } 1433 } 1434 1435 /** 1436 * Return feature set supported by the device 1437 */ 1438 private Set<String> getDeviceFeatures(ITestDevice device) 1439 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1440 if (mDeviceFeatures == null) { 1441 mDeviceFeatures = queryDeviceFeatures(device); 1442 } 1443 return mDeviceFeatures; 1444 } 1445 1446 /** 1447 * Query feature set supported by the device 1448 */ 1449 private static Set<String> queryDeviceFeatures(ITestDevice device) 1450 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1451 // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures 1452 // TODO: Move this logic to ITestDevice. 1453 String command = "pm list features"; 1454 String commandOutput = device.executeShellCommand(command); 1455 1456 // Extract the id of the new user. 1457 HashSet<String> availableFeatures = new HashSet<>(); 1458 for (String feature: commandOutput.split("\\s+")) { 1459 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 1460 String[] tokens = feature.split(":"); 1461 if (tokens.length < 2 || !"feature".equals(tokens[0])) { 1462 CLog.e("Failed parse features. Unexpect format on line \"%s\"", tokens[0]); 1463 throw new CapabilityQueryFailureException(); 1464 } 1465 availableFeatures.add(tokens[1]); 1466 } 1467 return availableFeatures; 1468 } 1469 1470 private boolean isPortraitClassRotation(String rotation) { 1471 return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) || 1472 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation); 1473 } 1474 1475 private boolean isLandscapeClassRotation(String rotation) { 1476 return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) || 1477 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation); 1478 } 1479 1480 /** 1481 * Install dEQP OnDevice Package 1482 */ 1483 private void installTestApk() throws DeviceNotAvailableException { 1484 try { 1485 File apkFile = mCtsBuild.getTestApp(DEQP_ONDEVICE_APK); 1486 String[] options = {AbiUtils.createAbiFlag(mAbi.getName())}; 1487 String errorCode = getDevice().installPackage(apkFile, true, options); 1488 if (errorCode != null) { 1489 CLog.e("Failed to install %s. Reason: %s", DEQP_ONDEVICE_APK, errorCode); 1490 } 1491 } catch (FileNotFoundException e) { 1492 CLog.e("Could not find test apk %s", DEQP_ONDEVICE_APK); 1493 } 1494 } 1495 1496 /** 1497 * Uninstall dEQP OnDevice Package 1498 */ 1499 private void uninstallTestApk() throws DeviceNotAvailableException { 1500 getDevice().uninstallPackage(DEQP_ONDEVICE_PKG); 1501 } 1502 1503 /** 1504 * Parse gl nature from package name 1505 */ 1506 private boolean isOpenGlEsPackage() { 1507 if ("dEQP-GLES2".equals(mName) || "dEQP-GLES3".equals(mName) || 1508 "dEQP-GLES31".equals(mName)) { 1509 return true; 1510 } else if ("dEQP-EGL".equals(mName)) { 1511 return false; 1512 } else { 1513 throw new IllegalStateException("dEQP runner was created with illegal name"); 1514 } 1515 } 1516 1517 /** 1518 * Check GL support (based on package name) 1519 */ 1520 private boolean isSupportedGles() throws DeviceNotAvailableException { 1521 return isSupportedGles(mDevice, getGlesMajorVersion(), getGlesMinorVersion()); 1522 } 1523 1524 /** 1525 * Get GL major version (based on package name) 1526 */ 1527 private int getGlesMajorVersion() throws DeviceNotAvailableException { 1528 if ("dEQP-GLES2".equals(mName)) { 1529 return 2; 1530 } else if ("dEQP-GLES3".equals(mName)) { 1531 return 3; 1532 } else if ("dEQP-GLES31".equals(mName)) { 1533 return 3; 1534 } else { 1535 throw new IllegalStateException("getGlesMajorVersion called for non gles pkg"); 1536 } 1537 } 1538 1539 /** 1540 * Get GL minor version (based on package name) 1541 */ 1542 private int getGlesMinorVersion() throws DeviceNotAvailableException { 1543 if ("dEQP-GLES2".equals(mName)) { 1544 return 0; 1545 } else if ("dEQP-GLES3".equals(mName)) { 1546 return 0; 1547 } else if ("dEQP-GLES31".equals(mName)) { 1548 return 1; 1549 } else { 1550 throw new IllegalStateException("getGlesMinorVersion called for non gles pkg"); 1551 } 1552 } 1553 1554 /** 1555 * {@inheritDoc} 1556 */ 1557 @Override 1558 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 1559 final Map<String, String> emptyMap = Collections.emptyMap(); 1560 final boolean isSupportedApi = !isOpenGlEsPackage() || isSupportedGles(); 1561 1562 listener.testRunStarted(getId(), mRemainingTests.size()); 1563 1564 try { 1565 if (isSupportedApi) { 1566 // Make sure there is no pre-existing package form earlier interrupted test run. 1567 uninstallTestApk(); 1568 installTestApk(); 1569 1570 mInstanceListerner.setSink(listener); 1571 runTests(); 1572 1573 uninstallTestApk(); 1574 } else { 1575 // Pass all tests if OpenGL ES version is not supported 1576 fakePassTests(listener); 1577 } 1578 } catch (CapabilityQueryFailureException ex) { 1579 // Platform is not behaving correctly, for example crashing when trying to create 1580 // a window. Instead of silenty failing, signal failure by leaving the rest of the 1581 // test cases in "NotExecuted" state 1582 uninstallTestApk(); 1583 } 1584 1585 listener.testRunEnded(0, emptyMap); 1586 } 1587} 1588