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