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