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