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