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.compatibility.common.tradefed.targetprep.IncrementalDeqpPreparer; 20 import com.android.compatibility.common.util.PropertyUtil; 21 import com.android.ddmlib.AdbCommandRejectedException; 22 import com.android.ddmlib.IShellOutputReceiver; 23 import com.android.ddmlib.MultiLineReceiver; 24 import com.android.ddmlib.ShellCommandUnresponsiveException; 25 import com.android.ddmlib.TimeoutException; 26 import com.android.tradefed.build.IBuildInfo; 27 import com.android.tradefed.config.Option; 28 import com.android.tradefed.config.OptionClass; 29 import com.android.tradefed.device.DeviceNotAvailableException; 30 import com.android.tradefed.device.IManagedTestDevice; 31 import com.android.tradefed.device.ITestDevice; 32 import com.android.tradefed.error.HarnessRuntimeException; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 35 import com.android.tradefed.result.ByteArrayInputStreamSource; 36 import com.android.tradefed.result.ITestInvocationListener; 37 import com.android.tradefed.result.LogDataType; 38 import com.android.tradefed.result.TestDescription; 39 import com.android.tradefed.result.error.DeviceErrorIdentifier; 40 import com.android.tradefed.result.error.TestErrorIdentifier; 41 import com.android.tradefed.testtype.IAbi; 42 import com.android.tradefed.testtype.IAbiReceiver; 43 import com.android.tradefed.testtype.IBuildReceiver; 44 import com.android.tradefed.testtype.IDeviceTest; 45 import com.android.tradefed.testtype.IRemoteTest; 46 import com.android.tradefed.testtype.IRuntimeHintProvider; 47 import com.android.tradefed.testtype.IShardableTest; 48 import com.android.tradefed.testtype.ITestCollector; 49 import com.android.tradefed.testtype.ITestFilterReceiver; 50 import com.android.tradefed.util.AbiUtils; 51 import com.android.tradefed.util.FileUtil; 52 import com.android.tradefed.util.IRunUtil; 53 import com.android.tradefed.util.RunInterruptedException; 54 import com.android.tradefed.util.RunUtil; 55 import java.io.BufferedReader; 56 import java.io.File; 57 import java.io.FileNotFoundException; 58 import java.io.FileReader; 59 import java.io.IOException; 60 import java.io.Reader; 61 import java.nio.file.Paths; 62 import java.util.ArrayList; 63 import java.util.Collection; 64 import java.util.HashMap; 65 import java.util.HashSet; 66 import java.util.Iterator; 67 import java.util.LinkedHashMap; 68 import java.util.LinkedHashSet; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Optional; 72 import java.util.Set; 73 import java.util.concurrent.TimeUnit; 74 import java.util.regex.Matcher; 75 import java.util.regex.Pattern; 76 77 /** 78 * Test runner for dEQP tests 79 * 80 * Supports running drawElements Quality Program tests found under 81 * external/deqp. 82 */ 83 @OptionClass(alias = "deqp-test-runner") 84 public class DeqpTestRunner 85 implements IBuildReceiver, IDeviceTest, ITestFilterReceiver, IAbiReceiver, 86 IShardableTest, ITestCollector, IRuntimeHintProvider { 87 private static final String DEQP_ONDEVICE_APK = "com.drawelements.deqp.apk"; 88 private static final String DEQP_ONDEVICE_PKG = "com.drawelements.deqp"; 89 protected static final String INCOMPLETE_LOG_MESSAGE = 90 "Crash: Incomplete test log"; 91 protected static final String TIMEOUT_LOG_MESSAGE = "Timeout: Test timeout"; 92 private static final String SKIPPED_INSTANCE_LOG_MESSAGE = 93 "Configuration skipped"; 94 public static final String ASSUMPTION_FAILURE_DEQP_LEVEL_LOG_MESSAGE = 95 "Features to be tested are not supported by device"; 96 protected static final String NOT_EXECUTABLE_LOG_MESSAGE = 97 "Abort: Test cannot be executed"; 98 protected static final String APP_DIR = "/sdcard/"; 99 protected static final String CASE_LIST_FILE_NAME = "dEQP-TestCaseList.txt"; 100 protected static final String LOG_FILE_NAME = "TestLog.qpa"; 101 public static final String FEATURE_LANDSCAPE = 102 "android.hardware.screen.landscape"; 103 public static final String FEATURE_PORTRAIT = 104 "android.hardware.screen.portrait"; 105 public static final String FEATURE_VULKAN_LEVEL = 106 "android.hardware.vulkan.level"; 107 public static final String FEATURE_VULKAN_DEQP_LEVEL = 108 "android.software.vulkan.deqp.level"; 109 public static final String FEATURE_OPENGLES_DEQP_LEVEL = 110 "android.software.opengles.deqp.level"; 111 112 private static final int TESTCASE_BATCH_LIMIT = 1000; 113 private static final int UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT = 114 10 * 60 * 1000; // 10min 115 private static final int R_API_LEVEL = 30; 116 private static final int DEQP_LEVEL_R_2020 = 132383489; 117 118 protected static final String ANGLE_NONE = "none"; 119 protected static final String ANGLE_VULKAN = "vulkan"; 120 protected static final String ANGLE_OPENGLES = "opengles"; 121 122 // !NOTE: There's a static method copyOptions() for copying options during 123 // split. If you add state update copyOptions() as appropriate! 124 125 @Option(name = "timeout", 126 description = 127 "Timeout for unresponsive tests in milliseconds. Default: " + 128 UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT, 129 importance = Option.Importance.NEVER) 130 private long mUnresponsiveCmdTimeoutMs = 131 UNRESPONSIVE_CMD_TIMEOUT_MS_DEFAULT; 132 @Option(name = "deqp-package", 133 description = 134 "Name of the deqp module used. Determines GLES version.", 135 importance = Option.Importance.ALWAYS) 136 private String mDeqpPackage; 137 @Option(name = "deqp-gl-config-name", 138 description = 139 "GL render target config. See deqp documentation for syntax. ", 140 importance = Option.Importance.NEVER) 141 private String mConfigName = ""; 142 @Option(name = "deqp-caselist-file", 143 description = "File listing the names of the cases to be run.", 144 importance = Option.Importance.ALWAYS) 145 private String mCaselistFile; 146 @Option(name = "deqp-screen-rotation", 147 description = "Screen orientation. Defaults to 'unspecified'", 148 importance = Option.Importance.NEVER) 149 private String mScreenRotation = "unspecified"; 150 @Option( 151 name = "deqp-surface-type", 152 description = 153 "Surface type ('window', 'pbuffer', 'fbo'). Defaults to 'window'", 154 importance = Option.Importance.NEVER) 155 private String mSurfaceType = "window"; 156 @Option( 157 name = "deqp-config-required", 158 description = 159 "Is current config required if API is supported? Defaults to false.", 160 importance = Option.Importance.NEVER) 161 private boolean mConfigRequired = false; 162 @Option( 163 name = "include-filter", 164 description = 165 "Test include filter. '*' is zero or more letters. '.' has no special meaning.") 166 protected List<String> mIncludeFilters = new ArrayList<>(); 167 @Option(name = "include-filter-file", 168 description = "Load list of includes from the files given.") 169 private List<String> mIncludeFilterFiles = new ArrayList<>(); 170 @Option( 171 name = "exclude-filter", 172 description = 173 "Test exclude filter. '*' is zero or more letters. '.' has no special meaning.") 174 protected List<String> mExcludeFilters = new ArrayList<>(); 175 @Option(name = "exclude-filter-file", 176 description = "Load list of excludes from the files given.") 177 private List<String> mExcludeFilterFiles = new ArrayList<>(); 178 @Option( 179 name = "incremental-deqp-include-file", 180 description = 181 "Load list of includes from the files given for incremental dEQP.") 182 private List<String> mIncrementalDeqpIncludeFiles = new ArrayList<>(); 183 @Option( 184 name = "collect-tests-only", 185 description = 186 "Only invoke the instrumentation to collect list of applicable test " 187 + 188 "cases. All test run callbacks will be triggered, but test execution will " 189 + "not be actually carried out.") 190 private boolean mCollectTestsOnly = false; 191 @Option(name = "runtime-hint", isTimeVal = true, 192 description = 193 "The estimated config runtime. Defaults to 200ms x num tests.") 194 private long mRuntimeHint = -1; 195 196 @Option(name = "collect-raw-logs", 197 description = "whether to collect raw deqp test log data") 198 protected boolean mLogData = false; 199 200 @Option( 201 name = "deqp-use-angle", 202 description = 203 "ANGLE backend ('none', 'vulkan', 'opengles'). Defaults to 'none' (don't use ANGLE)", 204 importance = Option.Importance.NEVER) 205 protected String mAngle = "none"; 206 207 @Option(name = "disable-watchdog", 208 description = "Disable the native testrunner's per-test watchdog.") 209 private boolean mDisableWatchdog = false; 210 211 @Option( 212 name = "force-deqp-level", 213 description = 214 "Force dEQP level to a specific level instead of device dEQP level. " 215 + "'all' enforces all dEQP tests to run") 216 private String mForceDeqpLevel = ""; 217 218 protected Set<TestDescription> mRemainingTests = null; 219 private Map<TestDescription, Set<BatchRunConfiguration>> mTestInstances = 220 null; 221 private final TestInstanceResultListener mInstanceListerner = 222 new TestInstanceResultListener(); 223 private final Map<TestDescription, Integer> mTestInstabilityRatings = 224 new HashMap<>(); 225 protected IAbi mAbi; 226 protected CompatibilityBuildHelper mBuildHelper; 227 protected ITestDevice mDevice; 228 private Map<String, Optional<Integer>> mDeviceFeatures; 229 private Map<String, Boolean> mConfigQuerySupportCache = new HashMap<>(); 230 protected IRunUtil mRunUtil = RunUtil.getDefault(); 231 private Set<String> mIncrementalDeqpIncludeTests = new HashSet<>(); 232 protected long mTimeOfLastRun = 0; 233 234 protected IRecovery mDeviceRecovery = new Recovery(); mDeviceRecovery.setSleepProvider(new SleepProvider())235 { mDeviceRecovery.setSleepProvider(new SleepProvider()); } 236 DeqpTestRunner()237 public DeqpTestRunner() {} 238 DeqpTestRunner( DeqpTestRunner optionTemplate, Map<TestDescription, Set<BatchRunConfiguration>> tests)239 private DeqpTestRunner( 240 DeqpTestRunner optionTemplate, 241 Map<TestDescription, Set<BatchRunConfiguration>> tests) { 242 copyOptions(this, optionTemplate); 243 mTestInstances = tests; 244 } 245 246 /** 247 * @param abi the ABI to run the test on 248 */ 249 @Override setAbi(IAbi abi)250 public void setAbi(IAbi abi) { 251 mAbi = abi; 252 } 253 254 @Override getAbi()255 public IAbi getAbi() { 256 return mAbi; 257 } 258 259 /** 260 * {@inheritDoc} 261 */ 262 @Override setBuild(IBuildInfo buildInfo)263 public void setBuild(IBuildInfo buildInfo) { 264 setBuildHelper(new CompatibilityBuildHelper(buildInfo)); 265 } 266 267 /** 268 * Exposed for better mockability during testing. In real use, always flows 269 * from setBuild() called by the framework 270 */ setBuildHelper(CompatibilityBuildHelper helper)271 public void setBuildHelper(CompatibilityBuildHelper helper) { 272 mBuildHelper = helper; 273 } 274 275 /** 276 * Get the deqp-package option contents. 277 */ getPackageName()278 public String getPackageName() { return mDeqpPackage; } 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override setDevice(ITestDevice device)284 public void setDevice(ITestDevice device) { 285 mDevice = device; 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override getDevice()292 public ITestDevice getDevice() { 293 return mDevice; 294 } 295 296 /** 297 * Set recovery handler. 298 * 299 * Exposed for unit testing. 300 */ setRecovery(IRecovery deviceRecovery)301 public void setRecovery(IRecovery deviceRecovery) { 302 mDeviceRecovery = deviceRecovery; 303 } 304 305 /** 306 * Set IRunUtil. 307 * 308 * Exposed for unit testing. 309 */ setRunUtil(IRunUtil runUtil)310 public void setRunUtil(IRunUtil runUtil) { mRunUtil = runUtil; } 311 312 private static final class CapabilityQueryFailureException 313 extends Exception {} 314 getInstanceListener()315 protected TestInstanceResultListener getInstanceListener() {return mInstanceListerner;} 316 317 /** 318 * dEQP test instance listerer and invocation result forwarded 319 */ 320 protected class TestInstanceResultListener { 321 private BatchRunConfiguration mRunConfig; 322 protected ITestInvocationListener mSink; 323 protected TestDescription mCurrentTestId; 324 protected boolean mGotTestResult; 325 protected String mCurrentTestLog; 326 327 private class PendingResult { 328 boolean allInstancesPassed; 329 Map<BatchRunConfiguration, String> testLogs; 330 Map<BatchRunConfiguration, String> errorMessages; 331 Set<BatchRunConfiguration> remainingConfigs; 332 } 333 334 private final Map<TestDescription, PendingResult> mPendingResults = 335 new HashMap<>(); 336 setSink(ITestInvocationListener sink)337 public void setSink(ITestInvocationListener sink) { mSink = sink; } 338 setCurrentConfig(BatchRunConfiguration runConfig)339 public void setCurrentConfig(BatchRunConfiguration runConfig) { 340 mRunConfig = runConfig; 341 } 342 343 /** 344 * Get currently processed test id, or null if not currently processing 345 * a test case 346 */ getCurrentTestId()347 public TestDescription getCurrentTestId() { return mCurrentTestId; } 348 349 /** 350 * Forward result to sink 351 */ forwardFinalizedPendingResult(TestDescription testId)352 protected void forwardFinalizedPendingResult(TestDescription testId) { 353 if (mRemainingTests.contains(testId)) { 354 final PendingResult result = mPendingResults.get(testId); 355 356 mPendingResults.remove(testId); 357 mRemainingTests.remove(testId); 358 359 // Forward results to the sink 360 mSink.testStarted(testId); 361 362 // Test Log 363 if (mLogData) { 364 for (Map.Entry<BatchRunConfiguration, String> entry : 365 result.testLogs.entrySet()) { 366 final ByteArrayInputStreamSource source = 367 new ByteArrayInputStreamSource( 368 entry.getValue().getBytes()); 369 370 mSink.testLog(testId.getClassName() + "." + 371 testId.getTestName() + "@" + 372 entry.getKey().getId(), 373 LogDataType.XML, source); 374 375 source.close(); 376 } 377 } 378 379 // Error message 380 if (!result.allInstancesPassed) { 381 final StringBuilder errorLog = new StringBuilder(); 382 383 for (Map.Entry<BatchRunConfiguration, String> entry : 384 result.errorMessages.entrySet()) { 385 if (errorLog.length() > 0) { 386 errorLog.append('\n'); 387 } 388 errorLog.append( 389 String.format("=== with config %s ===\n", 390 entry.getKey().getId())); 391 errorLog.append(entry.getValue()); 392 } 393 394 mSink.testFailed(testId, errorLog.toString()); 395 } 396 397 final HashMap<String, Metric> emptyMap = new HashMap<>(); 398 mSink.testEnded(testId, emptyMap); 399 } 400 } 401 402 /** 403 * Declare existence of a test and instances 404 */ setTestInstances(TestDescription testId, Set<BatchRunConfiguration> configs)405 public void setTestInstances(TestDescription testId, 406 Set<BatchRunConfiguration> configs) { 407 // Test instances cannot change at runtime, ignore if we have 408 // already set this 409 if (!mPendingResults.containsKey(testId)) { 410 final PendingResult pendingResult = new PendingResult(); 411 pendingResult.allInstancesPassed = true; 412 pendingResult.testLogs = new LinkedHashMap<>(); 413 pendingResult.errorMessages = new LinkedHashMap<>(); 414 pendingResult.remainingConfigs = 415 new HashSet<>(configs); // avoid mutating argument 416 mPendingResults.put(testId, pendingResult); 417 } 418 } 419 420 /** 421 * Query if test instance has not yet been executed 422 */ isPendingTestInstance(TestDescription testId, BatchRunConfiguration config)423 public boolean isPendingTestInstance(TestDescription testId, 424 BatchRunConfiguration config) { 425 final PendingResult result = mPendingResults.get(testId); 426 if (result == null) { 427 // test is not in the current working batch of the runner, i.e. 428 // it cannot be "partially" completed. 429 if (!mRemainingTests.contains(testId)) { 430 // The test has been fully executed. Not pending. 431 return false; 432 } else { 433 // Test has not yet been executed. Check if such instance 434 // exists 435 return mTestInstances.get(testId).contains(config); 436 } 437 } else { 438 // could be partially completed, check this particular config 439 return result.remainingConfigs.contains(config); 440 } 441 } 442 443 /** 444 * Fake execution of an instance with current config 445 */ skipTest(TestDescription testId)446 public void skipTest(TestDescription testId) { 447 final PendingResult result = mPendingResults.get(testId); 448 449 result.errorMessages.put(mRunConfig, SKIPPED_INSTANCE_LOG_MESSAGE); 450 result.remainingConfigs.remove(mRunConfig); 451 452 // Pending result finished, report result 453 if (result.remainingConfigs.isEmpty()) { 454 forwardFinalizedPendingResult(testId); 455 } 456 } 457 458 /** 459 * Fake failure of an instance with current config 460 */ abortTest(TestDescription testId, String errorMessage)461 public void abortTest(TestDescription testId, String errorMessage) { 462 final PendingResult result = mPendingResults.get(testId); 463 464 // Mark as executed 465 result.allInstancesPassed = false; 466 result.errorMessages.put(mRunConfig, errorMessage); 467 result.remainingConfigs.remove(mRunConfig); 468 469 // Pending result finished, report result 470 if (result.remainingConfigs.isEmpty()) { 471 forwardFinalizedPendingResult(testId); 472 } 473 474 if (testId.equals(mCurrentTestId)) { 475 mCurrentTestId = null; 476 } 477 } 478 479 /** 480 * Handles beginning of dEQP session. 481 */ handleBeginSession(Map<String, String> values)482 protected void handleBeginSession(Map<String, String> values) { 483 // ignore 484 } 485 486 /** 487 * Handles end of dEQP session. 488 */ handleEndSession(Map<String, String> values)489 protected void handleEndSession(Map<String, String> values) { 490 // ignore 491 } 492 493 /** 494 * Handles beginning of dEQP testcase. 495 */ handleBeginTestCase(Map<String, String> values)496 protected void handleBeginTestCase(Map<String, String> values) { 497 mCurrentTestId = 498 pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath")); 499 mCurrentTestLog = ""; 500 mGotTestResult = false; 501 502 // mark instance as started 503 if (mPendingResults.get(mCurrentTestId) != null) { 504 mPendingResults.get(mCurrentTestId) 505 .remainingConfigs.remove(mRunConfig); 506 } else { 507 CLog.w("Got unexpected start of %s", mCurrentTestId); 508 } 509 } 510 511 /** 512 * Handles end of dEQP testcase. 513 */ handleEndTestCase(Map<String, String> values)514 protected void handleEndTestCase(Map<String, String> values) { 515 final PendingResult result = mPendingResults.get(mCurrentTestId); 516 517 if (result != null) { 518 if (!mGotTestResult) { 519 result.allInstancesPassed = false; 520 result.errorMessages.put(mRunConfig, 521 INCOMPLETE_LOG_MESSAGE); 522 } 523 524 if (mLogData && mCurrentTestLog != null && 525 mCurrentTestLog.length() > 0) { 526 result.testLogs.put(mRunConfig, mCurrentTestLog); 527 } 528 529 // Pending result finished, report result 530 if (result.remainingConfigs.isEmpty()) { 531 forwardFinalizedPendingResult(mCurrentTestId); 532 } 533 } else { 534 CLog.w("Got unexpected end of %s", mCurrentTestId); 535 } 536 mCurrentTestId = null; 537 } 538 539 /** 540 * Handles dEQP testcase result. 541 */ handleTestCaseResult(Map<String, String> values)542 protected void handleTestCaseResult(Map<String, String> values) { 543 String code = values.get("dEQP-TestCaseResult-Code"); 544 String details = values.get("dEQP-TestCaseResult-Details"); 545 546 if (mPendingResults.get(mCurrentTestId) == null) { 547 CLog.w("Got unexpected result for %s", mCurrentTestId); 548 mGotTestResult = true; 549 return; 550 } 551 552 if (code.compareTo("Pass") == 0) { 553 mGotTestResult = true; 554 } else if (code.compareTo("NotSupported") == 0) { 555 mGotTestResult = true; 556 } else if (code.compareTo("QualityWarning") == 0) { 557 mGotTestResult = true; 558 } else if (code.compareTo("CompatibilityWarning") == 0) { 559 mGotTestResult = true; 560 } else if (code.compareTo("Fail") == 0 || 561 code.compareTo("ResourceError") == 0 || 562 code.compareTo("InternalError") == 0 || 563 code.compareTo("Crash") == 0 || 564 code.compareTo("Timeout") == 0) { 565 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 566 mPendingResults.get(mCurrentTestId) 567 .errorMessages.put(mRunConfig, code + ": " + details); 568 mGotTestResult = true; 569 } else { 570 String codeError = "Unknown result code: " + code; 571 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 572 mPendingResults.get(mCurrentTestId) 573 .errorMessages.put(mRunConfig, codeError + ": " + details); 574 mGotTestResult = true; 575 } 576 } 577 578 /** 579 * Handles terminated dEQP testcase. 580 */ handleTestCaseTerminate(Map<String, String> values)581 protected void handleTestCaseTerminate(Map<String, String> values) { 582 final PendingResult result = mPendingResults.get(mCurrentTestId); 583 584 if (result != null) { 585 String reason = values.get("dEQP-TerminateTestCase-Reason"); 586 mPendingResults.get(mCurrentTestId).allInstancesPassed = false; 587 mPendingResults.get(mCurrentTestId) 588 .errorMessages.put(mRunConfig, "Terminated: " + reason); 589 590 // Pending result finished, report result 591 if (result.remainingConfigs.isEmpty()) { 592 forwardFinalizedPendingResult(mCurrentTestId); 593 } 594 } else { 595 CLog.w("Got unexpected termination of %s", mCurrentTestId); 596 } 597 598 mCurrentTestId = null; 599 mGotTestResult = true; 600 } 601 602 /** 603 * Handles dEQP testlog data. 604 */ handleTestLogData(Map<String, String> values)605 protected void handleTestLogData(Map<String, String> values) { 606 mCurrentTestLog = 607 mCurrentTestLog + values.get("dEQP-TestLogData-Log"); 608 } 609 610 /** 611 * Handles new instrumentation status message. 612 */ handleStatus(Map<String, String> values)613 public void handleStatus(Map<String, String> values) { 614 String eventType = values.get("dEQP-EventType"); 615 616 if (eventType == null) { 617 return; 618 } 619 620 if (eventType.compareTo("BeginSession") == 0) { 621 handleBeginSession(values); 622 } else if (eventType.compareTo("EndSession") == 0) { 623 handleEndSession(values); 624 } else if (eventType.compareTo("BeginTestCase") == 0) { 625 handleBeginTestCase(values); 626 } else if (eventType.compareTo("EndTestCase") == 0) { 627 handleEndTestCase(values); 628 } else if (eventType.compareTo("TestCaseResult") == 0) { 629 handleTestCaseResult(values); 630 } else if (eventType.compareTo("TerminateTestCase") == 0) { 631 handleTestCaseTerminate(values); 632 } else if (eventType.compareTo("TestLogData") == 0) { 633 handleTestLogData(values); 634 } 635 } 636 637 /** 638 * Signal listener that batch ended and forget incomplete results. 639 */ endBatch()640 public void endBatch() { 641 // end open test if when stream ends 642 if (mCurrentTestId != null) { 643 // Current instance was removed from remainingConfigs when case 644 // started. Mark current instance as pending. 645 if (mPendingResults.get(mCurrentTestId) != null) { 646 mPendingResults.get(mCurrentTestId) 647 .remainingConfigs.add(mRunConfig); 648 } else { 649 CLog.w("Got unexpected internal state of %s", 650 mCurrentTestId); 651 } 652 } 653 mCurrentTestId = null; 654 } 655 } 656 657 /** 658 * dEQP instrumentation parser 659 */ 660 protected static class InstrumentationParser extends MultiLineReceiver { 661 protected TestInstanceResultListener mListener; 662 663 protected Map<String, String> mValues; 664 protected String mCurrentName; 665 protected String mCurrentValue; 666 protected int mResultCode; 667 protected boolean mGotExitValue = false; 668 InstrumentationParser()669 protected InstrumentationParser(){} 670 InstrumentationParser(TestInstanceResultListener listener)671 public InstrumentationParser(TestInstanceResultListener listener) { 672 mListener = listener; 673 } 674 675 /** 676 * {@inheritDoc} 677 */ 678 @Override processNewLines(String[] lines)679 public void processNewLines(String[] lines) { 680 for (String line : lines) { 681 if (mValues == null) 682 mValues = new HashMap<String, String>(); 683 684 if (line.startsWith("INSTRUMENTATION_STATUS_CODE: ")) { 685 if (mCurrentName != null) { 686 mValues.put(mCurrentName, mCurrentValue); 687 688 mCurrentName = null; 689 mCurrentValue = null; 690 } 691 692 mListener.handleStatus(mValues); 693 mValues = null; 694 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) { 695 if (mCurrentName != null) { 696 mValues.put(mCurrentName, mCurrentValue); 697 698 mCurrentValue = null; 699 mCurrentName = null; 700 } 701 702 String prefix = "INSTRUMENTATION_STATUS: "; 703 int nameBegin = prefix.length(); 704 int nameEnd = line.indexOf('='); 705 int valueBegin = nameEnd + 1; 706 707 mCurrentName = line.substring(nameBegin, nameEnd); 708 mCurrentValue = line.substring(valueBegin); 709 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) { 710 try { 711 mResultCode = Integer.parseInt(line.substring(22)); 712 mGotExitValue = true; 713 } catch (NumberFormatException ex) { 714 CLog.w("Instrumentation code format unexpected"); 715 } 716 } else if (mCurrentValue != null) { 717 mCurrentValue = mCurrentValue + line; 718 } 719 } 720 } 721 722 /** 723 * {@inheritDoc} 724 */ 725 @Override done()726 public void done() { 727 if (mCurrentName != null) { 728 mValues.put(mCurrentName, mCurrentValue); 729 730 mCurrentName = null; 731 mCurrentValue = null; 732 } 733 734 if (mValues != null) { 735 mListener.handleStatus(mValues); 736 mValues = null; 737 } 738 } 739 740 /** 741 * {@inheritDoc} 742 */ 743 @Override isCancelled()744 public boolean isCancelled() { 745 return false; 746 } 747 748 /** 749 * Returns whether target instrumentation exited normally. 750 */ wasSuccessful()751 public boolean wasSuccessful() { return mGotExitValue; } 752 753 /** 754 * Returns Instrumentation return code 755 */ getResultCode()756 public int getResultCode() { return mResultCode; } 757 } 758 759 /** 760 * dEQP platfom query instrumentation parser 761 */ 762 private static class PlatformQueryInstrumentationParser 763 extends MultiLineReceiver { 764 private Map<String, String> mResultMap = new LinkedHashMap<>(); 765 private int mResultCode; 766 private boolean mGotExitValue = false; 767 768 /** 769 * {@inheritDoc} 770 */ 771 @Override processNewLines(String[] lines)772 public void processNewLines(String[] lines) { 773 for (String line : lines) { 774 if (line.startsWith("INSTRUMENTATION_RESULT: ")) { 775 final String parts[] = line.substring(24).split("=", 2); 776 if (parts.length == 2) { 777 mResultMap.put(parts[0], parts[1]); 778 } else { 779 CLog.w("Instrumentation status format unexpected"); 780 } 781 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) { 782 try { 783 mResultCode = Integer.parseInt(line.substring(22)); 784 mGotExitValue = true; 785 } catch (NumberFormatException ex) { 786 CLog.w("Instrumentation code format unexpected"); 787 } 788 } 789 } 790 } 791 792 /** 793 * {@inheritDoc} 794 */ 795 @Override isCancelled()796 public boolean isCancelled() { 797 return false; 798 } 799 800 /** 801 * Returns whether target instrumentation exited normally. 802 */ wasSuccessful()803 public boolean wasSuccessful() { return mGotExitValue; } 804 805 /** 806 * Returns Instrumentation return code 807 */ getResultCode()808 public int getResultCode() { return mResultCode; } 809 getResultMap()810 public Map<String, String> getResultMap() { return mResultMap; } 811 } 812 813 /** 814 * Interface for sleeping. 815 * 816 * Exposed for unit testing 817 */ 818 public static interface ISleepProvider { sleep(int milliseconds)819 public void sleep(int milliseconds); 820 } 821 822 private static class SleepProvider implements ISleepProvider { 823 @Override sleep(int milliseconds)824 public void sleep(int milliseconds) { 825 RunUtil.getDefault().sleep(milliseconds); 826 } 827 } 828 829 /** 830 * Interface for failure recovery. 831 * 832 * Exposed for unit testing 833 */ 834 public static interface IRecovery { 835 /** 836 * Sets the sleep provider IRecovery works on 837 */ setSleepProvider(ISleepProvider sleepProvider)838 public void setSleepProvider(ISleepProvider sleepProvider); 839 840 /** 841 * Sets the device IRecovery works on 842 */ setDevice(ITestDevice device)843 public void setDevice(ITestDevice device); 844 845 /** 846 * Informs Recovery that test execution has progressed since the last 847 * recovery 848 */ onExecutionProgressed()849 public void onExecutionProgressed(); 850 851 /** 852 * Tries to recover device after failed refused connection. 853 * 854 * @throws DeviceNotAvailableException if recovery did not succeed 855 */ recoverConnectionRefused()856 public void recoverConnectionRefused() 857 throws DeviceNotAvailableException; 858 859 /** 860 * Tries to recover device after abnormal execution termination or link 861 * failure. 862 * 863 * @throws DeviceNotAvailableException if recovery did not succeed 864 */ recoverComLinkKilled()865 public void recoverComLinkKilled() throws DeviceNotAvailableException; 866 } 867 868 /** 869 * State machine for execution failure recovery. 870 * 871 * Exposed for unit testing 872 */ 873 public static class Recovery implements IRecovery { 874 private int RETRY_COOLDOWN_MS = 6000; // 6 seconds 875 private int PROCESS_KILL_WAIT_MS = 1000; // 1 second 876 877 private static enum MachineState { 878 WAIT, // recover by waiting 879 RECOVER, // recover by calling recover() 880 REBOOT, // recover by rebooting 881 FAIL, // cannot recover 882 } 883 884 private MachineState mState = MachineState.WAIT; 885 private ITestDevice mDevice; 886 private ISleepProvider mSleepProvider; 887 888 private static class ProcessKillFailureException extends Exception {} 889 890 /** 891 * {@inheritDoc} 892 */ 893 @Override setSleepProvider(ISleepProvider sleepProvider)894 public void setSleepProvider(ISleepProvider sleepProvider) { 895 mSleepProvider = sleepProvider; 896 } 897 898 /** 899 * {@inheritDoc} 900 */ 901 @Override setDevice(ITestDevice device)902 public void setDevice(ITestDevice device) { 903 mDevice = device; 904 } 905 906 /** 907 * {@inheritDoc} 908 */ 909 @Override onExecutionProgressed()910 public void onExecutionProgressed() { 911 mState = MachineState.WAIT; 912 } 913 914 /** 915 * {@inheritDoc} 916 */ 917 @Override recoverConnectionRefused()918 public void recoverConnectionRefused() 919 throws DeviceNotAvailableException { 920 switch (mState) { 921 case WAIT: // not a valid stratedy for connection refusal, 922 // fallthrough 923 case RECOVER: 924 // First failure, just try to recover 925 CLog.w("ADB connection failed, trying to recover"); 926 mState = MachineState.REBOOT; // the next step is to reboot 927 928 try { 929 recoverDevice(); 930 } catch (DeviceNotAvailableException ex) { 931 // chain forward 932 recoverConnectionRefused(); 933 } 934 break; 935 936 case REBOOT: 937 // Second failure in a row, try to reboot 938 CLog.w( 939 "ADB connection failed after recovery, rebooting device"); 940 mState = MachineState.FAIL; // the next step is to fail 941 942 try { 943 rebootDevice(); 944 } catch (DeviceNotAvailableException ex) { 945 // chain forward 946 recoverConnectionRefused(); 947 } 948 break; 949 950 case FAIL: 951 // Third failure in a row, just fail 952 CLog.w("Cannot recover ADB connection"); 953 throw new DeviceNotAvailableException( 954 "failed to connect after reboot", 955 mDevice.getSerialNumber()); 956 } 957 } 958 959 /** 960 * {@inheritDoc} 961 */ 962 @Override recoverComLinkKilled()963 public void recoverComLinkKilled() throws DeviceNotAvailableException { 964 switch (mState) { 965 case WAIT: 966 // First failure, just try to wait and try again 967 CLog.w("ADB link failed, retrying after a cooldown period"); 968 mState = MachineState 969 .RECOVER; // the next step is to recover the device 970 971 waitCooldown(); 972 973 // even if the link to deqp on-device process was killed, the 974 // process might still be alive. Locate and terminate such 975 // unwanted processes. 976 try { 977 killDeqpProcess(); 978 } catch (DeviceNotAvailableException ex) { 979 // chain forward 980 recoverComLinkKilled(); 981 } catch (ProcessKillFailureException ex) { 982 // chain forward 983 recoverComLinkKilled(); 984 } 985 break; 986 987 case RECOVER: 988 // Second failure, just try to recover 989 CLog.w("ADB link failed, trying to recover"); 990 mState = MachineState.REBOOT; // the next step is to reboot 991 992 try { 993 recoverDevice(); 994 killDeqpProcess(); 995 } catch (DeviceNotAvailableException ex) { 996 // chain forward 997 recoverComLinkKilled(); 998 } catch (ProcessKillFailureException ex) { 999 // chain forward 1000 recoverComLinkKilled(); 1001 } 1002 break; 1003 1004 case REBOOT: 1005 // Third failure in a row, try to reboot 1006 CLog.w("ADB link failed after recovery, rebooting device"); 1007 mState = MachineState.FAIL; // the next step is to fail 1008 1009 try { 1010 rebootDevice(); 1011 } catch (DeviceNotAvailableException ex) { 1012 // chain forward 1013 recoverComLinkKilled(); 1014 } 1015 break; 1016 1017 case FAIL: 1018 // Fourth failure in a row, just fail 1019 CLog.w("Cannot recover ADB connection"); 1020 throw new DeviceNotAvailableException( 1021 "link killed after reboot", mDevice.getSerialNumber(), 1022 DeviceErrorIdentifier.DEVICE_UNAVAILABLE); 1023 } 1024 } 1025 waitCooldown()1026 private void waitCooldown() { mSleepProvider.sleep(RETRY_COOLDOWN_MS); } 1027 getDeqpProcessPids()1028 private Iterable<Integer> getDeqpProcessPids() 1029 throws DeviceNotAvailableException { 1030 final List<Integer> pids = new ArrayList<Integer>(2); 1031 final String processes = 1032 mDevice.executeShellCommand("ps | grep com.drawelements"); 1033 final String[] lines = processes.split("(\\r|\\n)+"); 1034 for (String line : lines) { 1035 final String[] fields = line.split("\\s+"); 1036 if (fields.length < 2) { 1037 continue; 1038 } 1039 1040 try { 1041 final int processId = Integer.parseInt(fields[1], 10); 1042 pids.add(processId); 1043 } catch (NumberFormatException ex) { 1044 continue; 1045 } 1046 } 1047 return pids; 1048 } 1049 killDeqpProcess()1050 private void killDeqpProcess() 1051 throws DeviceNotAvailableException, ProcessKillFailureException { 1052 for (Integer processId : getDeqpProcessPids()) { 1053 mDevice.executeShellCommand( 1054 String.format("kill -9 %d", processId)); 1055 } 1056 1057 mSleepProvider.sleep(PROCESS_KILL_WAIT_MS); 1058 1059 // check that processes actually died 1060 if (getDeqpProcessPids().iterator().hasNext()) { 1061 // a process is still alive, killing failed 1062 throw new ProcessKillFailureException(); 1063 } 1064 } 1065 recoverDevice()1066 public void recoverDevice() throws DeviceNotAvailableException { 1067 ((IManagedTestDevice)mDevice).recoverDevice(); 1068 } 1069 rebootDevice()1070 private void rebootDevice() throws DeviceNotAvailableException { 1071 mDevice.reboot(); 1072 } 1073 } 1074 addTestsToInstancesMap( File testlist, String configName, String screenRotation, String surfaceType, boolean required, Map<TestDescription, Set<BatchRunConfiguration>> instances)1075 private static void addTestsToInstancesMap( 1076 File testlist, String configName, String screenRotation, 1077 String surfaceType, boolean required, 1078 Map<TestDescription, Set<BatchRunConfiguration>> instances) { 1079 1080 try (final FileReader testlistInnerReader = new FileReader(testlist); 1081 final BufferedReader testlistReader = 1082 new BufferedReader(testlistInnerReader)) { 1083 1084 String testName; 1085 while ((testName = testlistReader.readLine()) != null) { 1086 testName = testName.trim(); 1087 1088 // Skip empty lines. 1089 if (testName.isEmpty()) { 1090 continue; 1091 } 1092 1093 // Lines starting with "#" are comments. 1094 if (testName.startsWith("#")) { 1095 continue; 1096 } 1097 1098 // If the "testName" ends with .txt, then it is a path to 1099 // another test list (relative to the current test list, path 1100 // separator is "/") that we need to read. 1101 if (testName.endsWith(".txt")) { 1102 addTestsToInstancesMap( 1103 Paths.get(testlist.getParent(), testName.split("/")) 1104 .toFile(), 1105 configName, screenRotation, surfaceType, required, 1106 instances); 1107 continue; 1108 } 1109 1110 // Test name -> testId -> only one config -> done. 1111 final Set<BatchRunConfiguration> testInstanceSet = 1112 new LinkedHashSet<>(); 1113 BatchRunConfiguration config = new BatchRunConfiguration( 1114 configName, screenRotation, surfaceType, required); 1115 testInstanceSet.add(config); 1116 TestDescription test = pathToIdentifier(testName); 1117 instances.put(test, testInstanceSet); 1118 } 1119 } catch (IOException e) { 1120 throw new RuntimeException( 1121 "Failure while reading the test case list for deqp: " + 1122 e.getMessage()); 1123 } 1124 } 1125 1126 private Set<BatchRunConfiguration> getTestRunConfigs(TestDescription testId)1127 getTestRunConfigs(TestDescription testId) { 1128 return mTestInstances.get(testId); 1129 } 1130 1131 /** 1132 * Get the test instance of the runner. Exposed for testing. 1133 */ getTestInstance()1134 Map<TestDescription, Set<BatchRunConfiguration>> getTestInstance() { 1135 return mTestInstances; 1136 } 1137 1138 /** 1139 * Converts dEQP testcase path to TestDescription. 1140 */ pathToIdentifier(String testPath)1141 protected static TestDescription pathToIdentifier(String testPath) { 1142 int indexOfLastDot = testPath.lastIndexOf('.'); 1143 String className = testPath.substring(0, indexOfLastDot); 1144 String testName = testPath.substring(indexOfLastDot + 1); 1145 1146 return new TestDescription(className, testName); 1147 } 1148 1149 // \todo [2015-10-16 kalle] How unique should this be? getId()1150 protected String getId() { 1151 return AbiUtils.createId(mAbi.getName(), mDeqpPackage); 1152 } 1153 1154 /** 1155 * Generates tescase trie from dEQP testcase paths. Used to define which 1156 * testcases to execute. 1157 */ 1158 protected static String generateTestCaseTrieFromPaths(Collection<String> tests)1159 generateTestCaseTrieFromPaths(Collection<String> tests) { 1160 String result = "{"; 1161 boolean first = true; 1162 1163 // Add testcases to results 1164 for (Iterator<String> iter = tests.iterator(); iter.hasNext();) { 1165 String test = iter.next(); 1166 String[] components = test.split("\\."); 1167 1168 if (components.length == 1) { 1169 if (!first) { 1170 result = result + ","; 1171 } 1172 first = false; 1173 1174 result += components[0]; 1175 iter.remove(); 1176 } 1177 } 1178 1179 if (!tests.isEmpty()) { 1180 HashMap<String, ArrayList<String>> testGroups = new HashMap<>(); 1181 1182 // Collect all sub testgroups 1183 for (String test : tests) { 1184 String[] components = test.split("\\."); 1185 ArrayList<String> testGroup = testGroups.get(components[0]); 1186 1187 if (testGroup == null) { 1188 testGroup = new ArrayList<String>(); 1189 testGroups.put(components[0], testGroup); 1190 } 1191 1192 testGroup.add(test.substring(components[0].length() + 1)); 1193 } 1194 1195 for (String testGroup : testGroups.keySet()) { 1196 if (!first) { 1197 result = result + ","; 1198 } 1199 1200 first = false; 1201 result = 1202 result + testGroup + 1203 generateTestCaseTrieFromPaths(testGroups.get(testGroup)); 1204 } 1205 } 1206 1207 return result + "}"; 1208 } 1209 1210 /** 1211 * Generates testcase trie from TestDescriptions. 1212 */ 1213 protected static String generateTestCaseTrie(Collection<TestDescription> tests)1214 generateTestCaseTrie(Collection<TestDescription> tests) { 1215 ArrayList<String> testPaths = new ArrayList<String>(); 1216 1217 for (TestDescription test : tests) { 1218 testPaths.add(test.getClassName() + "." + test.getTestName()); 1219 } 1220 1221 return generateTestCaseTrieFromPaths(testPaths); 1222 } 1223 1224 protected static class TestBatch { 1225 private BatchRunConfiguration mConfig; 1226 protected List<TestDescription> mTests; 1227 getTestBatchConfig()1228 public BatchRunConfiguration getTestBatchConfig() {return mConfig;} getTestBatchTestDescriptionList()1229 public List<TestDescription> getTestBatchTestDescriptionList() {return mTests;} setTestBatchConfig(BatchRunConfiguration config)1230 public void setTestBatchConfig(BatchRunConfiguration config) {mConfig = config;} setTestBatchTestDescriptionList(List<TestDescription> tests)1231 public void setTestBatchTestDescriptionList(List<TestDescription> tests) {mTests = tests;} 1232 } 1233 1234 /** 1235 * Creates a TestBatch from the given tests or null if not tests remaining. 1236 * 1237 * @param pool List of tests to select from 1238 * @param requiredConfig Select only instances with pending requiredConfig, 1239 * or null to select any run configuration. 1240 */ selectRunBatch(Collection<TestDescription> pool, BatchRunConfiguration requiredConfig)1241 protected TestBatch selectRunBatch(Collection<TestDescription> pool, 1242 BatchRunConfiguration requiredConfig) { 1243 // select one test (leading test) that is going to be executed and then 1244 // pack along as many other compatible instances as possible. 1245 TestDescription leadingTest = null; 1246 for (TestDescription test : pool) { 1247 if (!mRemainingTests.contains(test)) { 1248 continue; 1249 } 1250 if (requiredConfig != null && 1251 !getInstanceListener().isPendingTestInstance(test, 1252 requiredConfig)) { 1253 continue; 1254 } 1255 leadingTest = test; 1256 break; 1257 } 1258 1259 // no remaining tests? 1260 if (leadingTest == null) { 1261 return null; 1262 } 1263 1264 BatchRunConfiguration leadingTestConfig = null; 1265 if (requiredConfig != null) { 1266 leadingTestConfig = requiredConfig; 1267 } else { 1268 for (BatchRunConfiguration runConfig : 1269 getTestRunConfigs(leadingTest)) { 1270 if (getInstanceListener().isPendingTestInstance(leadingTest, 1271 runConfig)) { 1272 leadingTestConfig = runConfig; 1273 break; 1274 } 1275 } 1276 } 1277 1278 // test pending <=> test has a pending config 1279 if (leadingTestConfig == null) { 1280 throw new AssertionError("search postcondition failed"); 1281 } 1282 1283 final int leadingInstability = getTestInstabilityRating(leadingTest); 1284 1285 final TestBatch runBatch = new TestBatch(); 1286 runBatch.setTestBatchConfig(leadingTestConfig); 1287 List<TestDescription> runBatchTests = new ArrayList<>(); 1288 runBatchTests.add(leadingTest); 1289 1290 for (TestDescription test : pool) { 1291 if (test == leadingTest) { 1292 // do not re-select the leading tests 1293 continue; 1294 } 1295 if (!getInstanceListener().isPendingTestInstance(test, 1296 leadingTestConfig)) { 1297 // select only compatible 1298 continue; 1299 } 1300 if (getTestInstabilityRating(test) != leadingInstability) { 1301 // pack along only cases in the same stability category. Packing 1302 // more dangerous tests along jeopardizes the stability of this 1303 // run. Packing more stable tests along jeopardizes their 1304 // stability rating. 1305 continue; 1306 } 1307 if (runBatchTests.size() >= 1308 getBatchSizeLimitForInstability(leadingInstability)) { 1309 // batch size is limited. 1310 break; 1311 } 1312 runBatchTests.add(test); 1313 } 1314 runBatch.setTestBatchTestDescriptionList(runBatchTests); 1315 1316 return runBatch; 1317 } 1318 getBatchSizeLimit()1319 private int getBatchSizeLimit() { 1320 return TESTCASE_BATCH_LIMIT; 1321 } 1322 getBatchNumPendingCases(TestBatch batch)1323 protected int getBatchNumPendingCases(TestBatch batch) { 1324 int numPending = 0; 1325 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1326 if (getInstanceListener().isPendingTestInstance(test, batch.getTestBatchConfig())) { 1327 ++numPending; 1328 } 1329 } 1330 return numPending; 1331 } 1332 getBatchSizeLimitForInstability(int batchInstabilityRating)1333 protected int getBatchSizeLimitForInstability(int batchInstabilityRating) { 1334 // reduce group size exponentially down to one 1335 return Math.max(1, getBatchSizeLimit() / (1 << batchInstabilityRating)); 1336 } 1337 getTestInstabilityRating(TestDescription testId)1338 protected int getTestInstabilityRating(TestDescription testId) { 1339 if (mTestInstabilityRatings.containsKey(testId)) { 1340 return mTestInstabilityRatings.get(testId); 1341 } else { 1342 return 0; 1343 } 1344 } 1345 recordTestInstability(TestDescription testId)1346 protected void recordTestInstability(TestDescription testId) { 1347 mTestInstabilityRatings.put(testId, 1348 getTestInstabilityRating(testId) + 1); 1349 } 1350 clearTestInstability(TestDescription testId)1351 protected void clearTestInstability(TestDescription testId) { 1352 mTestInstabilityRatings.put(testId, 0); 1353 } 1354 1355 /** 1356 * Executes all tests on the device. 1357 */ runTests()1358 private void runTests() 1359 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1360 for (;;) { 1361 TestBatch batch = selectRunBatch(mTestInstances.keySet(), null); 1362 1363 if (batch == null) { 1364 break; 1365 } 1366 1367 runTestRunBatch(batch); 1368 } 1369 } 1370 1371 /** 1372 * Runs a TestBatch by either faking it or executing it on a device. 1373 */ runTestRunBatch(TestBatch batch)1374 protected void runTestRunBatch(TestBatch batch) 1375 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1376 // prepare instance listener 1377 getInstanceListener().setCurrentConfig(batch.getTestBatchConfig()); 1378 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1379 getInstanceListener().setTestInstances(test, getTestRunConfigs(test)); 1380 } 1381 1382 // execute only if config is executable, else fake results 1383 if (isSupportedRunConfiguration(batch.getTestBatchConfig())) { 1384 executeTestRunBatch(batch); 1385 } else { 1386 if (batch.getTestBatchConfig().isRequired()) { 1387 fakeFailTestRunBatch(batch); 1388 } else { 1389 fakePassTestRunBatch(batch); 1390 } 1391 } 1392 } 1393 1394 /** 1395 * Checks if the runner should ignore dEQP tests completely and report nothing. 1396 */ shouldBypassTestExecutionAndReporting()1397 private boolean shouldBypassTestExecutionAndReporting() { 1398 // When the incremental dEQP attribute is set, the run is just for dEQP dependencies 1399 // collection and should be done by the dEQP binary. There is no need to run dEQP tests by 1400 // the runner. 1401 IBuildInfo buildInfo = mBuildHelper.getBuildInfo(); 1402 return buildInfo.getBuildAttributes().containsKey( 1403 IncrementalDeqpPreparer.INCREMENTAL_DEQP_ATTRIBUTE_NAME); 1404 } 1405 isSupportedRunConfiguration(BatchRunConfiguration runConfig)1406 private boolean isSupportedRunConfiguration(BatchRunConfiguration runConfig) 1407 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1408 // orientation support 1409 if (!BatchRunConfiguration.ROTATION_UNSPECIFIED.equals( 1410 runConfig.getRotation())) { 1411 final Map<String, Optional<Integer>> features = 1412 getDeviceFeatures(mDevice); 1413 1414 if (isPortraitClassRotation(runConfig.getRotation()) && 1415 !features.containsKey(FEATURE_PORTRAIT)) { 1416 return false; 1417 } 1418 if (isLandscapeClassRotation(runConfig.getRotation()) && 1419 !features.containsKey(FEATURE_LANDSCAPE)) { 1420 return false; 1421 } 1422 } 1423 1424 if (isOpenGlEsPackage()) { 1425 // renderability support for OpenGL ES tests 1426 return isSupportedGlesRenderConfig(runConfig); 1427 } else { 1428 return true; 1429 } 1430 } 1431 1432 protected static final class AdbComLinkOpenError extends Exception { AdbComLinkOpenError(String description, Throwable inner)1433 public AdbComLinkOpenError(String description, Throwable inner) { 1434 super(description, inner); 1435 } 1436 } 1437 1438 protected static final class AdbComLinkKilledError extends Exception { AdbComLinkKilledError(String description, Throwable inner)1439 public AdbComLinkKilledError(String description, Throwable inner) { 1440 super(description, inner); 1441 } 1442 } 1443 1444 protected static final class AdbComLinkUnresponsiveError extends Exception { AdbComLinkUnresponsiveError(String description, Throwable inner)1445 public AdbComLinkUnresponsiveError(String description, 1446 Throwable inner) { 1447 super(description, inner); 1448 } 1449 } 1450 1451 /** 1452 * Executes a given command in adb shell 1453 * 1454 * @throws AdbComLinkOpenError if connection cannot be established. 1455 * @throws AdbComLinkKilledError if established connection is killed 1456 * prematurely. 1457 */ 1458 protected void executeShellCommandAndReadOutput(final String command, final IShellOutputReceiver receiver)1459 executeShellCommandAndReadOutput(final String command, 1460 final IShellOutputReceiver receiver) 1461 throws AdbComLinkOpenError, AdbComLinkKilledError, 1462 AdbComLinkUnresponsiveError { 1463 try { 1464 mDevice.getIDevice().executeShellCommand(command, receiver, 1465 mUnresponsiveCmdTimeoutMs, 1466 TimeUnit.MILLISECONDS); 1467 } catch (TimeoutException ex) { 1468 // Opening connection timed out 1469 throw new AdbComLinkOpenError("opening connection timed out", ex); 1470 } catch (AdbCommandRejectedException ex) { 1471 // Command rejected 1472 throw new AdbComLinkOpenError("command rejected", ex); 1473 } catch (IOException ex) { 1474 // shell command channel killed 1475 throw new AdbComLinkKilledError("command link killed", ex); 1476 } catch (ShellCommandUnresponsiveException ex) { 1477 // shell command halted 1478 throw new AdbComLinkUnresponsiveError( 1479 "command link was unresponsive for longer than requested timeout", 1480 ex); 1481 } 1482 } 1483 1484 /** 1485 * Executes given test batch on a device 1486 */ executeTestRunBatch(TestBatch batch)1487 protected void executeTestRunBatch(TestBatch batch) 1488 throws DeviceNotAvailableException { 1489 final String instrumentationName = 1490 "com.drawelements.deqp/com.drawelements.deqp.testercore.DeqpInstrumentation"; 1491 1492 final StringBuilder deqpCmdLine = new StringBuilder(); 1493 deqpCmdLine.append("--deqp-caselist-file="); 1494 deqpCmdLine.append(APP_DIR + CASE_LIST_FILE_NAME); 1495 deqpCmdLine.append(" "); 1496 deqpCmdLine.append(getRunConfigDisplayCmdLine(batch.getTestBatchConfig())); 1497 1498 // If we are not logging data, do not bother outputting the images from 1499 // the test exe. 1500 if (!mLogData) { 1501 deqpCmdLine.append(" --deqp-log-images=disable"); 1502 } 1503 1504 if (!mDisableWatchdog) { 1505 deqpCmdLine.append(" --deqp-watchdog=enable"); 1506 } 1507 1508 final String command = String.format( 1509 "am instrument %s -w -e deqpLogFilename \"%s\" -e deqpCmdLine \"%s\"" 1510 + " -e deqpLogData \"%s\" %s", 1511 AbiUtils.createAbiFlag(mAbi.getName()), APP_DIR + LOG_FILE_NAME, 1512 deqpCmdLine.toString(), mLogData, instrumentationName); 1513 1514 final InstrumentationParser parser = 1515 new InstrumentationParser(getInstanceListener()); 1516 // attempt full run once 1517 executeTestRunBatchRun(batch, instrumentationName, command, parser); 1518 1519 // split remaining tests to two sub batches and execute both. This will 1520 // terminate since executeTestRunBatchRun will always progress for a 1521 // batch of size 1. 1522 final ArrayList<TestDescription> pendingTests = new ArrayList<>(); 1523 1524 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1525 if (getInstanceListener().isPendingTestInstance(test, batch.getTestBatchConfig())) { 1526 pendingTests.add(test); 1527 } 1528 } 1529 1530 final int divisorNdx = pendingTests.size() / 2; 1531 final List<TestDescription> headList = 1532 pendingTests.subList(0, divisorNdx); 1533 final List<TestDescription> tailList = 1534 pendingTests.subList(divisorNdx, pendingTests.size()); 1535 1536 // head 1537 for (;;) { 1538 TestBatch subBatch = selectRunBatch(headList, batch.getTestBatchConfig()); 1539 1540 if (subBatch == null) { 1541 break; 1542 } 1543 1544 executeTestRunBatch(subBatch); 1545 } 1546 1547 // tail 1548 for (;;) { 1549 TestBatch subBatch = selectRunBatch(tailList, batch.getTestBatchConfig()); 1550 1551 if (subBatch == null) { 1552 break; 1553 } 1554 1555 executeTestRunBatch(subBatch); 1556 } 1557 1558 if (getBatchNumPendingCases(batch) != 0) { 1559 throw new AssertionError( 1560 "executeTestRunBatch postcondition failed"); 1561 } 1562 } 1563 1564 /** 1565 * Runs one execution pass over the given batch. 1566 * 1567 * Tries to run the batch. Always makes progress (executes instances or 1568 * modifies stability scores). 1569 */ executeTestRunBatchRun(TestBatch batch, final String instrumentationName, final String runCommand, final InstrumentationParser parser)1570 protected void executeTestRunBatchRun(TestBatch batch, final String instrumentationName, final String runCommand, final InstrumentationParser parser) 1571 throws DeviceNotAvailableException { 1572 if (getBatchNumPendingCases(batch) != batch.getTestBatchTestDescriptionList().size()) { 1573 throw new AssertionError( 1574 "executeTestRunBatchRun precondition failed"); 1575 } 1576 1577 checkInterrupted(); // throws if interrupted 1578 1579 final String testCases = generateTestCaseTrie(batch.getTestBatchTestDescriptionList()); 1580 final String testCaseFilename = APP_DIR + CASE_LIST_FILE_NAME; 1581 mDevice.executeShellCommand("rm " + testCaseFilename); 1582 mDevice.executeShellCommand("rm " + APP_DIR + LOG_FILE_NAME); 1583 if (!mDevice.pushString(testCases + "\n", testCaseFilename)) { 1584 throw new RuntimeException("Failed to write test cases to " + 1585 testCaseFilename); 1586 } 1587 1588 final int numRemainingInstancesBefore = getNumRemainingInstances(); 1589 Throwable interruptingError = null; 1590 1591 // Fix the requirement of sleep() between batches 1592 long duration = System.currentTimeMillis() - mTimeOfLastRun; 1593 if (duration < 5000) { 1594 CLog.i("Sleeping for %dms", 5000 - duration); 1595 mRunUtil.sleep(5000 - duration); 1596 } 1597 1598 try { 1599 executeShellCommandAndReadOutput(runCommand, parser); 1600 } catch (Throwable ex) { 1601 interruptingError = ex; 1602 } finally { 1603 mTimeOfLastRun = System.currentTimeMillis(); 1604 parser.flush(); 1605 } 1606 1607 final boolean progressedSinceLastCall = 1608 getInstanceListener().getCurrentTestId() != null || 1609 getNumRemainingInstances() < numRemainingInstancesBefore; 1610 1611 if (progressedSinceLastCall) { 1612 mDeviceRecovery.onExecutionProgressed(); 1613 } 1614 1615 // interrupted either because of ADB or test timeout 1616 if (interruptingError != null) { 1617 1618 // AdbComLinkUnresponsiveError means the test has timeout during 1619 // execution. Device is likely fine, so we won't attempt to recover 1620 // the device. 1621 if (interruptingError instanceof AdbComLinkUnresponsiveError) { 1622 getInstanceListener().abortTest( 1623 getInstanceListener().getCurrentTestId(), TIMEOUT_LOG_MESSAGE); 1624 } else if (interruptingError instanceof AdbComLinkOpenError) { 1625 mDeviceRecovery.recoverConnectionRefused(); 1626 } else if (interruptingError instanceof AdbComLinkKilledError) { 1627 mDeviceRecovery.recoverComLinkKilled(); 1628 } else if (interruptingError instanceof RunInterruptedException) { 1629 // external run interruption request. Terminate immediately. 1630 throw (RunInterruptedException)interruptingError; 1631 } else { 1632 CLog.e(interruptingError); 1633 throw new RuntimeException(interruptingError); 1634 } 1635 1636 // recoverXXX did not throw => recovery succeeded 1637 } else if (!parser.wasSuccessful()) { 1638 mDeviceRecovery.recoverComLinkKilled(); 1639 // recoverXXX did not throw => recovery succeeded 1640 } 1641 1642 // Progress guarantees. 1643 if (batch.getTestBatchTestDescriptionList().size() == 1) { 1644 final TestDescription onlyTest = batch.getTestBatchTestDescriptionList().iterator().next(); 1645 final boolean wasTestExecuted = 1646 !getInstanceListener().isPendingTestInstance(onlyTest, 1647 batch.getTestBatchConfig()) && 1648 getInstanceListener().getCurrentTestId() == null; 1649 final boolean wasLinkFailure = 1650 !parser.wasSuccessful() || interruptingError != null; 1651 1652 // Link failures can be caused by external events, require at least 1653 // two observations until bailing. 1654 if (!wasTestExecuted && 1655 (!wasLinkFailure || getTestInstabilityRating(onlyTest) > 0)) { 1656 recordTestInstability(onlyTest); 1657 // If we cannot finish the test, mark the case as a crash. 1658 // 1659 // If we couldn't even start the test, fail the test instance as 1660 // non-executable. This is required so that a consistently 1661 // crashing or non-existent tests will not cause futile 1662 // (non-terminating) re-execution attempts. 1663 if (getInstanceListener().getCurrentTestId() != null) { 1664 getInstanceListener().abortTest(onlyTest, 1665 INCOMPLETE_LOG_MESSAGE); 1666 } else { 1667 getInstanceListener().abortTest(onlyTest, 1668 NOT_EXECUTABLE_LOG_MESSAGE); 1669 } 1670 } else if (wasTestExecuted) { 1671 clearTestInstability(onlyTest); 1672 } 1673 } else { 1674 // Analyze results to update test stability ratings. If there is no 1675 // interrupting test logged, increase instability rating of all 1676 // remaining tests. If there is a interrupting test logged, increase 1677 // only its instability rating. 1678 // 1679 // A successful run of tests clears instability rating. 1680 if (getInstanceListener().getCurrentTestId() == null) { 1681 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1682 if (getInstanceListener().isPendingTestInstance( 1683 test, batch.getTestBatchConfig())) { 1684 recordTestInstability(test); 1685 } else { 1686 clearTestInstability(test); 1687 } 1688 } 1689 } else { 1690 recordTestInstability(getInstanceListener().getCurrentTestId()); 1691 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1692 // \note: isPendingTestInstance is false for 1693 // getCurrentTestId. Current ID is considered 'running' and 1694 // will be restored to 'pending' in endBatch(). 1695 if (!test.equals(getInstanceListener().getCurrentTestId()) && 1696 !getInstanceListener().isPendingTestInstance( 1697 test, batch.getTestBatchConfig())) { 1698 clearTestInstability(test); 1699 } 1700 } 1701 } 1702 } 1703 1704 getInstanceListener().endBatch(); 1705 } 1706 1707 protected String getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig)1708 getRunConfigDisplayCmdLine(BatchRunConfiguration runConfig) { 1709 final StringBuilder deqpCmdLine = new StringBuilder(); 1710 if (!runConfig.getGlConfig().isEmpty()) { 1711 deqpCmdLine.append("--deqp-gl-config-name="); 1712 deqpCmdLine.append(runConfig.getGlConfig()); 1713 } 1714 if (!runConfig.getRotation().isEmpty()) { 1715 if (deqpCmdLine.length() != 0) { 1716 deqpCmdLine.append(" "); 1717 } 1718 deqpCmdLine.append("--deqp-screen-rotation="); 1719 deqpCmdLine.append(runConfig.getRotation()); 1720 } 1721 if (!runConfig.getSurfaceType().isEmpty()) { 1722 if (deqpCmdLine.length() != 0) { 1723 deqpCmdLine.append(" "); 1724 } 1725 deqpCmdLine.append("--deqp-surface-type="); 1726 deqpCmdLine.append(runConfig.getSurfaceType()); 1727 } 1728 return deqpCmdLine.toString(); 1729 } 1730 getNumRemainingInstances()1731 protected int getNumRemainingInstances() { 1732 int retVal = 0; 1733 for (TestDescription testId : mRemainingTests) { 1734 // If case is in current working set, sum only not yet executed 1735 // instances. If case is not in current working set, sum all 1736 // instances (since they are not yet executed). 1737 if (getInstanceListener().mPendingResults.containsKey(testId)) { 1738 retVal += getInstanceListener().mPendingResults.get(testId) 1739 .remainingConfigs.size(); 1740 } else { 1741 retVal += mTestInstances.get(testId).size(); 1742 } 1743 } 1744 return retVal; 1745 } 1746 1747 /** 1748 * Checks if this execution has been marked as interrupted and throws if it 1749 * has. 1750 */ checkInterrupted()1751 protected void checkInterrupted() throws RunInterruptedException { 1752 // Work around the API. RunUtil::checkInterrupted is private but we can 1753 // call it indirectly by sleeping a value <= 0. 1754 mRunUtil.sleep(0); 1755 } 1756 1757 /** 1758 * Pass given batch tests without running it 1759 */ fakePassTestRunBatch(TestBatch batch)1760 private void fakePassTestRunBatch(TestBatch batch) { 1761 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1762 CLog.d( 1763 "Marking '%s' invocation in config '%s' as passed without running", 1764 test.toString(), batch.getTestBatchConfig().getId()); 1765 getInstanceListener().skipTest(test); 1766 } 1767 } 1768 1769 /** 1770 * Fail given batch tests without running it 1771 */ fakeFailTestRunBatch(TestBatch batch)1772 private void fakeFailTestRunBatch(TestBatch batch) { 1773 for (TestDescription test : batch.getTestBatchTestDescriptionList()) { 1774 CLog.d( 1775 "Marking '%s' invocation in config '%s' as failed without running", 1776 test.toString(), batch.getTestBatchConfig().getId()); 1777 getInstanceListener().abortTest(test, "Required config not supported"); 1778 } 1779 } 1780 1781 /** 1782 * Pass tests without running them 1783 */ fakePassTests(ITestInvocationListener listener)1784 private void fakePassTests(ITestInvocationListener listener) { 1785 HashMap<String, Metric> emptyMap = new HashMap<>(); 1786 for (TestDescription test : mTestInstances.keySet()) { 1787 listener.testStarted(test); 1788 listener.testEnded(test, emptyMap); 1789 } 1790 1791 // Log only once all the skipped tests 1792 CLog.d( 1793 "Marking tests '%s', as pass because tests are simply being collected", 1794 mTestInstances.keySet()); 1795 mRemainingTests.removeAll(mTestInstances.keySet()); 1796 } 1797 1798 /** 1799 * Ignoring tests without running them 1800 */ 1801 private void markTestsAsAssumptionFailure(ITestInvocationListener listener)1802 markTestsAsAssumptionFailure(ITestInvocationListener listener) { 1803 HashMap<String, Metric> emptyMap = new HashMap<>(); 1804 for (TestDescription test : mTestInstances.keySet()) { 1805 listener.testStarted(test); 1806 listener.testAssumptionFailure( 1807 test, ASSUMPTION_FAILURE_DEQP_LEVEL_LOG_MESSAGE); 1808 listener.testEnded(test, emptyMap); 1809 } 1810 1811 // Log only once after all the tests marked as assumption Failure 1812 CLog.d( 1813 "Assumption failed for tests '%s' because features are not supported by device", 1814 mRemainingTests); 1815 mRemainingTests.removeAll(mTestInstances.keySet()); 1816 } 1817 1818 /** 1819 * Ignoring tests without running them 1820 */ ignoreTests(ITestInvocationListener listener)1821 private void ignoreTests(ITestInvocationListener listener) { 1822 HashMap<String, Metric> emptyMap = new HashMap<>(); 1823 for (TestDescription test : mTestInstances.keySet()) { 1824 listener.testStarted(test); 1825 listener.testIgnored(test); 1826 listener.testEnded(test, emptyMap); 1827 } 1828 1829 // Log only once after all the tests ignored 1830 CLog.d( 1831 "Tests '%s', ignored because they are not required by the deqp level", 1832 mRemainingTests); 1833 mRemainingTests.removeAll(mTestInstances.keySet()); 1834 } 1835 1836 /** 1837 * Check if device supports Vulkan. 1838 */ isSupportedVulkan()1839 private boolean isSupportedVulkan() 1840 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1841 final Map<String, Optional<Integer>> features = 1842 getDeviceFeatures(mDevice); 1843 1844 for (String feature : features.keySet()) { 1845 if (feature.startsWith(FEATURE_VULKAN_LEVEL)) { 1846 return true; 1847 } 1848 } 1849 1850 return false; 1851 } 1852 1853 /** 1854 * Check whether the device's claimed dEQP level is high enough that it 1855 * should pass the tests in the caselist. 1856 */ claimedDeqpLevelIsRecentEnough()1857 private boolean claimedDeqpLevelIsRecentEnough() 1858 throws CapabilityQueryFailureException, DeviceNotAvailableException { 1859 if (mForceDeqpLevel.equals("all")) { 1860 CLog.d("All deqp levels have been forced for this run"); 1861 return true; 1862 } 1863 1864 // Determine whether we need to check the dEQP feature flag for Vulkan 1865 // or OpenGL ES. 1866 final String featureName; 1867 if (isVulkanPackage()) { 1868 featureName = FEATURE_VULKAN_DEQP_LEVEL; 1869 } else if (isOpenGlEsPackage() || isEglPackage()) { 1870 // The OpenGL ES feature flag is used for EGL as well. 1871 featureName = FEATURE_OPENGLES_DEQP_LEVEL; 1872 } else { 1873 throw new AssertionError( 1874 "Claims about dEQP support should only be checked for Vulkan, OpenGL ES, or EGL " 1875 + "packages"); 1876 } 1877 1878 CLog.d("For caselist \"%s\", the dEQP level feature flag is \"%s\".", 1879 mCaselistFile, featureName); 1880 1881 // A Vulkan/OpenGL ES caselist filename has the form: 1882 // {gles2,gles3,gles31,vk,egl}-main-YYYY-MM-DD.txt 1883 final Pattern caseListFilenamePattern = 1884 Pattern.compile("-main-(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)\\.txt$"); 1885 final Matcher matcher = caseListFilenamePattern.matcher(mCaselistFile); 1886 if (!matcher.find()) { 1887 CLog.d( 1888 "No dEQP level date found in caselist. Running unconditionally."); 1889 return true; 1890 } 1891 1892 final int year = Integer.parseInt(matcher.group(1)); 1893 final int month = Integer.parseInt(matcher.group(2)); 1894 final int day = Integer.parseInt(matcher.group(3)); 1895 CLog.d("Caselist date is %04d-%02d-%02d", year, month, day); 1896 1897 // As per the documentation for FEATURE_VULKAN_DEQP_LEVEL and 1898 // FEATURE_OPENGLES_DEQP_LEVEL in android.content.pm.PackageManager, a 1899 // year is encoded as an integer by devoting bits 31-16 to year, 15-8 to 1900 // month and 7-0 to day. 1901 final int minimumLevel = (year << 16) + (month << 8) + day; 1902 1903 CLog.d("For reference, date -> level mappings are:"); 1904 CLog.d(" 2019-03-01 -> 132317953"); 1905 CLog.d(" 2020-03-01 -> 132383489"); 1906 CLog.d(" 2021-03-01 -> 132449025"); 1907 CLog.d(" 2022-03-01 -> 132514561"); 1908 CLog.d(" 2023-03-01 -> 132580097"); 1909 CLog.d(" 2024-03-01 -> 132645633"); 1910 CLog.d(" 2025-03-01 -> 132711169"); 1911 1912 CLog.d("Minimum level required to run this caselist is %d", 1913 minimumLevel); 1914 1915 if (!mForceDeqpLevel.isEmpty()) { 1916 int forcedDepqLevel; 1917 try { 1918 forcedDepqLevel = Integer.parseInt(mForceDeqpLevel); 1919 CLog.d("%s forced as deqp level"); 1920 } catch (NumberFormatException e) { 1921 throw new AssertionError( 1922 "Deqp Level is not an acceptable numeric value"); 1923 } 1924 1925 final boolean shouldRunCaselist = forcedDepqLevel >= minimumLevel; 1926 CLog.d("Running caselist? %b", shouldRunCaselist); 1927 return shouldRunCaselist; 1928 } 1929 1930 // Now look for the feature flag. 1931 final Map<String, Optional<Integer>> features = 1932 getDeviceFeatures(mDevice); 1933 1934 for (String feature : features.keySet()) { 1935 if (feature.startsWith(featureName)) { 1936 final Optional<Integer> claimedDeqpLevel = 1937 features.get(feature); 1938 if (!claimedDeqpLevel.isPresent()) { 1939 throw new IllegalStateException( 1940 "Feature " + featureName + 1941 " has no associated version"); 1942 } 1943 CLog.d("Device level is %d", claimedDeqpLevel.get()); 1944 1945 final boolean shouldRunCaselist = 1946 claimedDeqpLevel.get() >= minimumLevel; 1947 CLog.d("Running caselist? %b", shouldRunCaselist); 1948 return shouldRunCaselist; 1949 } 1950 } 1951 1952 CLog.d("Could not find dEQP level feature flag \"%s\".", featureName); 1953 1954 // A Vulkan dEQP level has been required since R. 1955 // A GLES/EGL dEQP level has only been required since S. 1956 // Thus, if the VSR level is <= R and there is no GLES dEQP level, then 1957 // we can assume a GLES dEQP level of R (2020). 1958 if (PropertyUtil.getVsrApiLevel(mDevice) <= R_API_LEVEL && 1959 FEATURE_OPENGLES_DEQP_LEVEL.equals(featureName)) { 1960 final int claimedDeqpLevel = DEQP_LEVEL_R_2020; 1961 CLog.d("Device level is %d due to VSR R", claimedDeqpLevel); 1962 final boolean shouldRunCaselist = claimedDeqpLevel >= minimumLevel; 1963 CLog.d("Running caselist? %b", shouldRunCaselist); 1964 return shouldRunCaselist; 1965 } 1966 1967 CLog.d("Running caselist unconditionally"); 1968 1969 return true; 1970 } 1971 1972 /** 1973 * Check if device supports OpenGL ES version. 1974 */ isSupportedGles(ITestDevice device, int requiredMajorVersion, int requiredMinorVersion)1975 private static boolean isSupportedGles(ITestDevice device, 1976 int requiredMajorVersion, 1977 int requiredMinorVersion) 1978 throws DeviceNotAvailableException { 1979 String roOpenglesVersion = device.getProperty("ro.opengles.version"); 1980 1981 if (roOpenglesVersion == null) 1982 return false; 1983 1984 int intValue = Integer.parseInt(roOpenglesVersion); 1985 1986 int majorVersion = ((intValue & 0xffff0000) >> 16); 1987 int minorVersion = (intValue & 0xffff); 1988 1989 return (majorVersion > requiredMajorVersion) || 1990 (majorVersion == requiredMajorVersion && 1991 minorVersion >= requiredMinorVersion); 1992 } 1993 1994 /** 1995 * Query if rendertarget is supported 1996 */ isSupportedGlesRenderConfig(BatchRunConfiguration runConfig)1997 private boolean isSupportedGlesRenderConfig(BatchRunConfiguration runConfig) 1998 throws DeviceNotAvailableException, CapabilityQueryFailureException { 1999 // query if configuration is supported 2000 final StringBuilder configCommandLine = 2001 new StringBuilder(getRunConfigDisplayCmdLine(runConfig)); 2002 if (configCommandLine.length() != 0) { 2003 configCommandLine.append(" "); 2004 } 2005 configCommandLine.append("--deqp-gl-major-version="); 2006 configCommandLine.append(getGlesMajorVersion()); 2007 configCommandLine.append(" --deqp-gl-minor-version="); 2008 configCommandLine.append(getGlesMinorVersion()); 2009 2010 final String commandLine = configCommandLine.toString(); 2011 2012 // check for cached result first 2013 if (mConfigQuerySupportCache.containsKey(commandLine)) { 2014 return mConfigQuerySupportCache.get(commandLine); 2015 } 2016 2017 final boolean supported = 2018 queryIsSupportedConfigCommandLine(commandLine); 2019 mConfigQuerySupportCache.put(commandLine, supported); 2020 return supported; 2021 } 2022 queryIsSupportedConfigCommandLine(String deqpCommandLine)2023 private boolean queryIsSupportedConfigCommandLine(String deqpCommandLine) 2024 throws DeviceNotAvailableException, CapabilityQueryFailureException { 2025 final String instrumentationName = 2026 "com.drawelements.deqp/com.drawelements.deqp.platformutil.DeqpPlatformCapabilityQueryInstrumentation"; 2027 final String command = String.format( 2028 "am instrument %s -w -e deqpQueryType renderConfigSupported -e deqpCmdLine \"%s\"" 2029 + " %s", 2030 AbiUtils.createAbiFlag(mAbi.getName()), deqpCommandLine, 2031 instrumentationName); 2032 2033 final PlatformQueryInstrumentationParser parser = 2034 new PlatformQueryInstrumentationParser(); 2035 mDevice.executeShellCommand(command, parser); 2036 parser.flush(); 2037 2038 if (parser.wasSuccessful() && parser.getResultCode() == 0 && 2039 parser.getResultMap().containsKey("Supported")) { 2040 if ("Yes".equals(parser.getResultMap().get("Supported"))) { 2041 return true; 2042 } else if ("No".equals(parser.getResultMap().get("Supported"))) { 2043 return false; 2044 } else { 2045 CLog.e("Capability query did not return a result"); 2046 throw new CapabilityQueryFailureException(); 2047 } 2048 } else if (parser.wasSuccessful()) { 2049 CLog.e("Failed to run capability query. Code: %d, Result: %s", 2050 parser.getResultCode(), parser.getResultMap().toString()); 2051 throw new CapabilityQueryFailureException(); 2052 } else { 2053 CLog.e("Failed to run capability query"); 2054 throw new CapabilityQueryFailureException(); 2055 } 2056 } 2057 2058 /** 2059 * Return feature set supported by the device, mapping integer-valued 2060 * features to their values 2061 */ getDeviceFeatures(ITestDevice device)2062 private Map<String, Optional<Integer>> getDeviceFeatures(ITestDevice device) 2063 throws DeviceNotAvailableException, CapabilityQueryFailureException { 2064 if (mDeviceFeatures == null) { 2065 mDeviceFeatures = queryDeviceFeatures(device); 2066 } 2067 return mDeviceFeatures; 2068 } 2069 2070 /** 2071 * Query feature set supported by the device 2072 */ 2073 private static Map<String, Optional<Integer>> queryDeviceFeatures(ITestDevice device)2074 queryDeviceFeatures(ITestDevice device) 2075 throws DeviceNotAvailableException, CapabilityQueryFailureException { 2076 // NOTE: Almost identical code in BaseDevicePolicyTest#hasDeviceFeatures 2077 // TODO: Move this logic to ITestDevice. 2078 String command = "pm list features"; 2079 String commandOutput = device.executeShellCommand(command); 2080 2081 // Extract the id of the new user. 2082 Map<String, Optional<Integer>> availableFeatures = new HashMap<>(); 2083 for (String feature : commandOutput.split("\\s+")) { 2084 // Each line in the output of the command has the format 2085 // "feature:{FEATURE_NAME}", optionally followed by 2086 // "={FEATURE_VERSION}". 2087 String[] tokens = feature.split(":|="); 2088 if (tokens.length < 2 || !"feature".equals(tokens[0])) { 2089 CLog.e("Failed parse features. Unexpect format on line \"%s\"", 2090 feature); 2091 throw new CapabilityQueryFailureException(); 2092 } 2093 final String featureName = tokens[1]; 2094 Optional<Integer> featureValue = Optional.empty(); 2095 if (tokens.length > 2) { 2096 try { 2097 // Integer.decode, rather than Integer.parseInt, is used 2098 // here since some feature versions may be presented in 2099 // decimal and others in hexadecimal. 2100 featureValue = Optional.of(Integer.decode(tokens[2])); 2101 } catch (NumberFormatException numberFormatException) { 2102 CLog.e( 2103 "Failed parse features. Feature value \"%s\" was not an integer on " 2104 + "line \"%s\"", 2105 tokens[2], feature); 2106 throw new CapabilityQueryFailureException(); 2107 } 2108 } 2109 availableFeatures.put(featureName, featureValue); 2110 } 2111 return availableFeatures; 2112 } 2113 isPortraitClassRotation(String rotation)2114 private boolean isPortraitClassRotation(String rotation) { 2115 return BatchRunConfiguration.ROTATION_PORTRAIT.equals(rotation) || 2116 BatchRunConfiguration.ROTATION_REVERSE_PORTRAIT.equals(rotation); 2117 } 2118 isLandscapeClassRotation(String rotation)2119 private boolean isLandscapeClassRotation(String rotation) { 2120 return BatchRunConfiguration.ROTATION_LANDSCAPE.equals(rotation) || 2121 BatchRunConfiguration.ROTATION_REVERSE_LANDSCAPE.equals(rotation); 2122 } 2123 checkRecognizedPackage()2124 private void checkRecognizedPackage() { 2125 if (!isRecognizedPackage()) { 2126 throw new IllegalStateException( 2127 "dEQP runner was created with illegal package name"); 2128 } 2129 } 2130 isRecognizedPackage()2131 private boolean isRecognizedPackage() { 2132 return "dEQP-EGL".equals(mDeqpPackage) || 2133 "dEQP-GLES2".equals(mDeqpPackage) || 2134 "dEQP-GLES3".equals(mDeqpPackage) || 2135 "dEQP-GLES31".equals(mDeqpPackage) || 2136 "dEQP-VK".equals(mDeqpPackage); 2137 } 2138 2139 /** 2140 * Parse EGL nature from package name 2141 */ isEglPackage()2142 private boolean isEglPackage() { 2143 checkRecognizedPackage(); 2144 return "dEQP-EGL".equals(mDeqpPackage); 2145 } 2146 2147 /** 2148 * Parse gl nature from package name 2149 */ isOpenGlEsPackage()2150 private boolean isOpenGlEsPackage() { 2151 checkRecognizedPackage(); 2152 return "dEQP-GLES2".equals(mDeqpPackage) || 2153 "dEQP-GLES3".equals(mDeqpPackage) || 2154 "dEQP-GLES31".equals(mDeqpPackage); 2155 } 2156 2157 /** 2158 * Parse vulkan nature from package name 2159 */ isVulkanPackage()2160 private boolean isVulkanPackage() { 2161 checkRecognizedPackage(); 2162 return "dEQP-VK".equals(mDeqpPackage); 2163 } 2164 2165 /** 2166 * Check GL support (based on package name) 2167 */ isSupportedGles()2168 private boolean isSupportedGles() throws DeviceNotAvailableException { 2169 return isSupportedGles(mDevice, getGlesMajorVersion(), 2170 getGlesMinorVersion()); 2171 } 2172 2173 /** 2174 * Get GL major version (based on package name) 2175 */ getGlesMajorVersion()2176 private int getGlesMajorVersion() { 2177 if ("dEQP-GLES2".equals(mDeqpPackage)) { 2178 return 2; 2179 } else if ("dEQP-GLES3".equals(mDeqpPackage)) { 2180 return 3; 2181 } else if ("dEQP-GLES31".equals(mDeqpPackage)) { 2182 return 3; 2183 } else { 2184 throw new IllegalStateException( 2185 "getGlesMajorVersion called for non gles pkg"); 2186 } 2187 } 2188 2189 /** 2190 * Get GL minor version (based on package name) 2191 */ getGlesMinorVersion()2192 private int getGlesMinorVersion() { 2193 if ("dEQP-GLES2".equals(mDeqpPackage)) { 2194 return 0; 2195 } else if ("dEQP-GLES3".equals(mDeqpPackage)) { 2196 return 0; 2197 } else if ("dEQP-GLES31".equals(mDeqpPackage)) { 2198 return 1; 2199 } else { 2200 throw new IllegalStateException( 2201 "getGlesMinorVersion called for non gles pkg"); 2202 } 2203 } 2204 getPatternFilters(List<String> filters)2205 protected static List<Pattern> getPatternFilters(List<String> filters) { 2206 List<Pattern> patterns = new ArrayList<Pattern>(); 2207 for (String filter : filters) { 2208 if (filter.contains("*")) { 2209 patterns.add(Pattern.compile( 2210 filter.replace(".", "\\.").replace("*", ".*"))); 2211 } 2212 } 2213 return patterns; 2214 } 2215 getNonPatternFilters(List<String> filters)2216 protected static Set<String> getNonPatternFilters(List<String> filters) { 2217 Set<String> nonPatternFilters = new HashSet<String>(); 2218 for (String filter : filters) { 2219 if (filter.startsWith("#") || filter.isEmpty()) { 2220 // Skip comments and empty lines 2221 continue; 2222 } 2223 if (!filter.contains("*")) { 2224 // Deqp usesly only dots for separating between parts of the 2225 // names Convert last dot to hash if needed. 2226 if (!filter.contains("#")) { 2227 int lastSeparator = filter.lastIndexOf('.'); 2228 String filterWithHash = 2229 filter.substring(0, lastSeparator) + "#" + 2230 filter.substring(lastSeparator + 1, filter.length()); 2231 nonPatternFilters.add(filterWithHash); 2232 } else { 2233 nonPatternFilters.add(filter); 2234 } 2235 } 2236 } 2237 return nonPatternFilters; 2238 } 2239 matchesAny(TestDescription test, List<Pattern> patterns)2240 protected static boolean matchesAny(TestDescription test, 2241 List<Pattern> patterns) { 2242 for (Pattern pattern : patterns) { 2243 if (pattern.matcher(test.toString()).matches()) { 2244 return true; 2245 } 2246 } 2247 return false; 2248 } 2249 2250 /** 2251 * Filter tests with the option of filtering by pattern. 2252 * 2253 * '*' is 0 or more characters. 2254 * '.' is interpreted verbatim. 2255 */ 2256 private static void filterTests(Map<TestDescription, Set<BatchRunConfiguration>> tests, List<String> includeFilters, List<String> excludeFilters)2257 filterTests(Map<TestDescription, Set<BatchRunConfiguration>> tests, 2258 List<String> includeFilters, List<String> excludeFilters) { 2259 // We could filter faster by building the test case tree. 2260 // Let's see if this is fast enough. 2261 Set<String> includeStrings = getNonPatternFilters(includeFilters); 2262 Set<String> excludeStrings = getNonPatternFilters(excludeFilters); 2263 List<Pattern> includePatterns = getPatternFilters(includeFilters); 2264 List<Pattern> excludePatterns = getPatternFilters(excludeFilters); 2265 2266 List<TestDescription> testList = new ArrayList<>(tests.keySet()); 2267 for (TestDescription test : testList) { 2268 if (excludeStrings.contains(test.toString())) { 2269 tests.remove(test); // remove test if explicitly excluded 2270 continue; 2271 } 2272 boolean includesExist = 2273 !includeStrings.isEmpty() || !includePatterns.isEmpty(); 2274 boolean testIsIncluded = includeStrings.contains(test.toString()) || 2275 matchesAny(test, includePatterns); 2276 if ((includesExist && !testIsIncluded) || 2277 matchesAny(test, excludePatterns)) { 2278 // if this test isn't included and other tests are, 2279 // or if test matches exclude pattern, exclude test 2280 tests.remove(test); 2281 } 2282 } 2283 } 2284 2285 /** 2286 * Read each line from a file. 2287 */ readFile(Collection<String> lines, File file)2288 static private void readFile(Collection<String> lines, File file) 2289 throws FileNotFoundException { 2290 if (!file.canRead()) { 2291 CLog.e("Failed to read file '%s'", file.getPath()); 2292 throw new FileNotFoundException(); 2293 } 2294 try (Reader plainReader = new FileReader(file); 2295 BufferedReader reader = new BufferedReader(plainReader)) { 2296 String line = ""; 2297 while ((line = reader.readLine()) != null) { 2298 // TOOD: Quick check filter 2299 lines.add(line); 2300 } 2301 // Rely on try block to autoclose 2302 } catch (IOException e) { 2303 throw new RuntimeException("Failed to read file '" + 2304 file.getPath() + "': " + e.getMessage()); 2305 } 2306 } 2307 2308 /** 2309 * Prints filters into debug log stream, limiting to 20 entries. 2310 */ printFilters(List<String> filters)2311 static private void printFilters(List<String> filters) { 2312 int numPrinted = 0; 2313 for (String filter : filters) { 2314 CLog.d(" %s", filter); 2315 if (++numPrinted == 20) { 2316 CLog.d(" ... AND %d others", filters.size() - numPrinted); 2317 break; 2318 } 2319 } 2320 } 2321 2322 /** 2323 * Loads tests into mTestInstances based on the options. Assumes 2324 * that no tests have been loaded for this instance before. 2325 */ loadTests()2326 private void loadTests() { 2327 if (mTestInstances != null) 2328 throw new AssertionError("Re-load of tests not supported"); 2329 2330 // Note: This is specifically a LinkedHashMap to guarantee that tests 2331 // are iterated in the insertion order. 2332 mTestInstances = new LinkedHashMap<>(); 2333 2334 if (shouldBypassTestExecutionAndReporting()) { 2335 return; 2336 } 2337 2338 try { 2339 File testlist = new File(mBuildHelper.getTestsDir(), mCaselistFile); 2340 if (!testlist.isFile()) { 2341 // Finding file in sub directory if no matching file in the 2342 // first layer of testdir. 2343 testlist = FileUtil.findFile(mBuildHelper.getTestsDir(), 2344 mCaselistFile); 2345 if (testlist == null || !testlist.isFile()) { 2346 throw new FileNotFoundException( 2347 "Cannot find deqp test list file: " + mCaselistFile); 2348 } 2349 } 2350 addTestsToInstancesMap(testlist, mConfigName, mScreenRotation, 2351 mSurfaceType, mConfigRequired, 2352 mTestInstances); 2353 } catch (FileNotFoundException e) { 2354 throw new RuntimeException("Cannot read deqp test list file: " + 2355 mCaselistFile); 2356 } 2357 2358 try { 2359 for (String filterFile : mIncludeFilterFiles) { 2360 CLog.d("Read include filter file '%s'", filterFile); 2361 File file = new File(mBuildHelper.getTestsDir(), filterFile); 2362 if (!file.isFile()) { 2363 // Find file in sub directory if no matching file in the 2364 // first layer of testdir. 2365 file = FileUtil.findFile(mBuildHelper.getTestsDir(), 2366 filterFile); 2367 if (file == null || !file.isFile()) { 2368 throw new FileNotFoundException( 2369 "Cannot find include-filter-file file: " + 2370 filterFile); 2371 } 2372 } 2373 readFile(mIncludeFilters, file); 2374 } 2375 for (String filterFile : mExcludeFilterFiles) { 2376 CLog.d("Read exclude filter file '%s'", filterFile); 2377 File file = new File(mBuildHelper.getTestsDir(), filterFile); 2378 if (!file.isFile()) { 2379 // Find file in sub directory if no matching file in the 2380 // first layer of testdir. 2381 file = FileUtil.findFile(mBuildHelper.getTestsDir(), 2382 filterFile); 2383 if (file == null || !file.isFile()) { 2384 throw new FileNotFoundException( 2385 "Cannot find exclude-filter-file file: " + 2386 filterFile); 2387 } 2388 } 2389 readFile(mExcludeFilters, file); 2390 } 2391 } catch (FileNotFoundException e) { 2392 throw new HarnessRuntimeException( 2393 "Cannot read deqp filter list file." + e, 2394 TestErrorIdentifier.TEST_ABORTED); 2395 } 2396 2397 CLog.d("Include filters:"); 2398 printFilters(mIncludeFilters); 2399 CLog.d("Exclude filters:"); 2400 printFilters(mExcludeFilters); 2401 2402 long originalTestCount = mTestInstances.size(); 2403 CLog.i("Num tests before filtering: %d", originalTestCount); 2404 if ((!mIncludeFilters.isEmpty() || !mExcludeFilters.isEmpty()) && 2405 originalTestCount > 0) { 2406 filterTests(mTestInstances, mIncludeFilters, mExcludeFilters); 2407 2408 // Update runtime estimation hint. 2409 if (mRuntimeHint != -1) { 2410 mRuntimeHint = 2411 (mRuntimeHint * mTestInstances.size()) / originalTestCount; 2412 } 2413 } 2414 CLog.i("Num tests after filtering: %d", mTestInstances.size()); 2415 } 2416 2417 /** 2418 * Set up the test environment. 2419 */ setupTestEnvironment(String testPackageName)2420 protected void setupTestEnvironment(String testPackageName) throws DeviceNotAvailableException { 2421 try { 2422 // Get the system into a known state. 2423 // Clear ANGLE Global.Settings values 2424 mDevice.executeShellCommand( 2425 "settings delete global angle_gl_driver_selection_pkgs"); 2426 mDevice.executeShellCommand( 2427 "settings delete global angle_gl_driver_selection_values"); 2428 2429 // ANGLE 2430 if (mAngle.equals(ANGLE_VULKAN)) { 2431 CLog.i("Configuring ANGLE to use: " + mAngle); 2432 // Force dEQP to use ANGLE 2433 mDevice.executeShellCommand( 2434 "settings put global angle_gl_driver_selection_pkgs " + 2435 testPackageName); 2436 mDevice.executeShellCommand( 2437 "settings put global angle_gl_driver_selection_values angle"); 2438 // Configure ANGLE to use Vulkan 2439 mDevice.executeShellCommand("setprop debug.angle.backend 2"); 2440 } else if (mAngle.equals(ANGLE_OPENGLES)) { 2441 CLog.i("Configuring ANGLE to use: " + mAngle); 2442 // Force dEQP to use ANGLE 2443 mDevice.executeShellCommand( 2444 "settings put global angle_gl_driver_selection_pkgs " + 2445 testPackageName); 2446 mDevice.executeShellCommand( 2447 "settings put global angle_gl_driver_selection_values angle"); 2448 // Configure ANGLE to use Vulkan 2449 mDevice.executeShellCommand("setprop debug.angle.backend 0"); 2450 } 2451 } catch (DeviceNotAvailableException ex) { 2452 // chain forward 2453 CLog.e("Failed to set up ANGLE correctly."); 2454 throw new DeviceNotAvailableException("Device not available", ex, 2455 mDevice.getSerialNumber()); 2456 } 2457 } 2458 2459 /** 2460 * Clean up the test environment. 2461 */ teardownTestEnvironment()2462 protected void teardownTestEnvironment() throws DeviceNotAvailableException { 2463 // ANGLE 2464 try { 2465 CLog.i("Cleaning up ANGLE"); 2466 // Stop forcing dEQP to use ANGLE 2467 mDevice.executeShellCommand( 2468 "settings delete global angle_gl_driver_selection_pkgs"); 2469 mDevice.executeShellCommand( 2470 "settings delete global angle_gl_driver_selection_values"); 2471 } catch (DeviceNotAvailableException ex) { 2472 // chain forward 2473 CLog.e("Failed to clean up ANGLE correctly."); 2474 throw new DeviceNotAvailableException("Device not available", ex, 2475 mDevice.getSerialNumber()); 2476 } 2477 } 2478 2479 /** 2480 * {@inheritDoc} 2481 */ 2482 @Override run(ITestInvocationListener listener)2483 public void run(ITestInvocationListener listener) 2484 throws DeviceNotAvailableException { 2485 final HashMap<String, Metric> emptyMap = new HashMap<>(); 2486 // If sharded, split() will load the tests. 2487 if (mTestInstances == null) { 2488 loadTests(); 2489 } 2490 2491 mRemainingTests = new HashSet<>(); 2492 if (!shouldBypassTestExecutionAndReporting()) { 2493 mRemainingTests.addAll(mTestInstances.keySet()); 2494 } 2495 long startTime = System.currentTimeMillis(); 2496 listener.testRunStarted(getId(), mRemainingTests.size()); 2497 2498 try { 2499 if (mRemainingTests.isEmpty()) { 2500 CLog.d("No tests to run."); 2501 return; 2502 } 2503 final boolean isSupportedApi = 2504 (isOpenGlEsPackage() && isSupportedGles()) || 2505 (isVulkanPackage() && isSupportedVulkan()) || 2506 (!isOpenGlEsPackage() && !isVulkanPackage()); 2507 final boolean deqpLevelIsRecent = claimedDeqpLevelIsRecentEnough(); 2508 2509 if (mCollectTestsOnly) { 2510 // Pass all tests trivially if: 2511 // - we are only collecting the names of the tests 2512 fakePassTests(listener); 2513 } else if (!isSupportedApi) { 2514 // Skip tests with "Assumption Failure" when 2515 // - the relevant API/feature is not supported or 2516 markTestsAsAssumptionFailure(listener); 2517 } else if (!deqpLevelIsRecent) { 2518 // Skip tests with "Ignore" when 2519 // - the device's deqp level do not claim to pass the tests 2520 ignoreTests(listener); 2521 } else if (!mRemainingTests.isEmpty()) { 2522 getInstanceListener().setSink(listener); 2523 mDeviceRecovery.setDevice(mDevice); 2524 setupTestEnvironment(DEQP_ONDEVICE_PKG); 2525 runTests(); 2526 teardownTestEnvironment(); 2527 } 2528 } catch (CapabilityQueryFailureException ex) { 2529 // Platform is not behaving correctly, for example crashing when 2530 // trying to create a window. Instead of silently failing, signal 2531 // failure by leaving the rest of the test cases in "NotExecuted" 2532 // state 2533 CLog.e("Capability query failed - leaving tests unexecuted."); 2534 } finally { 2535 listener.testRunEnded(System.currentTimeMillis() - startTime, 2536 emptyMap); 2537 } 2538 } 2539 2540 /** 2541 * {@inheritDoc} 2542 */ 2543 @Override addIncludeFilter(String filter)2544 public void addIncludeFilter(String filter) { 2545 mIncludeFilters.add(filter); 2546 } 2547 2548 /** 2549 * {@inheritDoc} 2550 */ 2551 @Override addAllIncludeFilters(Set<String> filters)2552 public void addAllIncludeFilters(Set<String> filters) { 2553 mIncludeFilters.addAll(filters); 2554 } 2555 2556 /** 2557 * {@inheritDoc} 2558 */ 2559 @Override getIncludeFilters()2560 public Set<String> getIncludeFilters() { 2561 return new HashSet<>(mIncludeFilters); 2562 } 2563 2564 /** 2565 * {@inheritDoc} 2566 */ 2567 @Override clearIncludeFilters()2568 public void clearIncludeFilters() { 2569 mIncludeFilters.clear(); 2570 } 2571 2572 /** 2573 * {@inheritDoc} 2574 */ 2575 @Override addExcludeFilter(String filter)2576 public void addExcludeFilter(String filter) { 2577 mExcludeFilters.add(filter); 2578 } 2579 2580 /** 2581 * {@inheritDoc} 2582 */ 2583 @Override addAllExcludeFilters(Set<String> filters)2584 public void addAllExcludeFilters(Set<String> filters) { 2585 mExcludeFilters.addAll(filters); 2586 } 2587 2588 /** 2589 * {@inheritDoc} 2590 */ 2591 @Override getExcludeFilters()2592 public Set<String> getExcludeFilters() { 2593 return new HashSet<>(mExcludeFilters); 2594 } 2595 2596 /** 2597 * {@inheritDoc} 2598 */ 2599 @Override clearExcludeFilters()2600 public void clearExcludeFilters() { 2601 mExcludeFilters.clear(); 2602 } 2603 2604 /** 2605 * {@inheritDoc} 2606 */ 2607 @Override setCollectTestsOnly(boolean collectTests)2608 public void setCollectTestsOnly(boolean collectTests) { 2609 mCollectTestsOnly = collectTests; 2610 } 2611 2612 /** 2613 * These methods are for testing. 2614 */ addIncrementalDeqpIncludeTests(Collection<String> tests)2615 public void addIncrementalDeqpIncludeTests(Collection<String> tests) { 2616 mIncrementalDeqpIncludeTests.addAll(tests); 2617 } 2618 copyOptions(DeqpTestRunner destination, DeqpTestRunner source)2619 private static void copyOptions(DeqpTestRunner destination, 2620 DeqpTestRunner source) { 2621 destination.mUnresponsiveCmdTimeoutMs = 2622 source.mUnresponsiveCmdTimeoutMs; 2623 destination.mDeqpPackage = source.mDeqpPackage; 2624 destination.mConfigName = source.mConfigName; 2625 destination.mCaselistFile = source.mCaselistFile; 2626 destination.mScreenRotation = source.mScreenRotation; 2627 destination.mSurfaceType = source.mSurfaceType; 2628 destination.mConfigRequired = source.mConfigRequired; 2629 destination.mIncludeFilters = new ArrayList<>(source.mIncludeFilters); 2630 destination.mIncludeFilterFiles = 2631 new ArrayList<>(source.mIncludeFilterFiles); 2632 destination.mExcludeFilters = new ArrayList<>(source.mExcludeFilters); 2633 destination.mExcludeFilterFiles = 2634 new ArrayList<>(source.mExcludeFilterFiles); 2635 destination.mAbi = source.mAbi; 2636 destination.mLogData = source.mLogData; 2637 destination.mCollectTestsOnly = source.mCollectTestsOnly; 2638 destination.mAngle = source.mAngle; 2639 destination.mDisableWatchdog = source.mDisableWatchdog; 2640 destination.mIncrementalDeqpIncludeFiles = 2641 new ArrayList<>(source.mIncrementalDeqpIncludeFiles); 2642 destination.mForceDeqpLevel = source.mForceDeqpLevel; 2643 } 2644 2645 /** 2646 * Helper to update the RuntimeHint of the tests after being sharded. 2647 */ updateRuntimeHint(long originalSize, Collection<IRemoteTest> runners)2648 private void updateRuntimeHint(long originalSize, 2649 Collection<IRemoteTest> runners) { 2650 if (originalSize > 0) { 2651 long fullRuntimeMs = getRuntimeHint(); 2652 for (IRemoteTest remote : runners) { 2653 DeqpTestRunner runner = (DeqpTestRunner)remote; 2654 long shardRuntime = 2655 (fullRuntimeMs * runner.mTestInstances.size()) / 2656 originalSize; 2657 runner.mRuntimeHint = shardRuntime; 2658 } 2659 } 2660 } 2661 2662 /** 2663 * {@inheritDoc} 2664 */ 2665 @Override split()2666 public Collection<IRemoteTest> split() { 2667 if (mTestInstances != null) { 2668 throw new AssertionError( 2669 "Re-splitting or splitting running instance?"); 2670 } 2671 // \todo [2015-11-23 kalle] If we split to batches at shard level, we 2672 // could basically get rid of batching. Except that sharding is 2673 // optional? 2674 2675 // Assume that tests have not been yet loaded. 2676 loadTests(); 2677 2678 Collection<IRemoteTest> runners = new ArrayList<>(); 2679 // NOTE: Use linked hash map to keep the insertion order in iteration 2680 Map<TestDescription, Set<BatchRunConfiguration>> currentSet = 2681 new LinkedHashMap<>(); 2682 Map<TestDescription, Set<BatchRunConfiguration>> iterationSet = 2683 this.mTestInstances; 2684 2685 if (iterationSet.keySet().isEmpty()) { 2686 CLog.i("Cannot split deqp tests, no tests to run"); 2687 return null; 2688 } 2689 2690 // Go through tests, split 2691 for (TestDescription test : iterationSet.keySet()) { 2692 currentSet.put(test, iterationSet.get(test)); 2693 if (currentSet.size() >= getBatchSizeLimit()) { 2694 runners.add(new DeqpTestRunner(this, currentSet)); 2695 // NOTE: Use linked hash map to keep the insertion order in 2696 // iteration 2697 currentSet = new LinkedHashMap<>(); 2698 } 2699 } 2700 runners.add(new DeqpTestRunner(this, currentSet)); 2701 2702 // Compute new runtime hints 2703 updateRuntimeHint(iterationSet.size(), runners); 2704 CLog.i("Split deqp tests into %d shards", runners.size()); 2705 return runners; 2706 } 2707 2708 /** 2709 * {@inheritDoc} 2710 */ 2711 @Override getRuntimeHint()2712 public long getRuntimeHint() { 2713 if (mRuntimeHint != -1) { 2714 return mRuntimeHint; 2715 } 2716 if (mTestInstances == null) { 2717 loadTests(); 2718 } 2719 // Tests normally take something like ~100ms. Some take a 2720 // second. Let's guess 200ms per test. 2721 return 200 * mTestInstances.size(); 2722 } 2723 } 2724