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