1 /* 2 * Copyright (C) 2010 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.android.tradefed.result; 17 18 import com.android.ddmlib.testrunner.TestResult.TestStatus; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.invoker.IInvocationContext; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 24 import com.android.tradefed.retry.MergeStrategy; 25 26 import com.google.common.annotations.VisibleForTesting; 27 28 import java.util.ArrayList; 29 import java.util.Collection; 30 import java.util.Collections; 31 import java.util.HashMap; 32 import java.util.LinkedHashMap; 33 import java.util.LinkedList; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Map.Entry; 37 import java.util.concurrent.atomic.AtomicBoolean; 38 39 /** 40 * A {@link ITestInvocationListener} that will collect all test results. 41 * 42 * <p>Although the data structures used in this object are thread-safe, the {@link 43 * ITestInvocationListener} callbacks must be called in the correct order. 44 */ 45 public class CollectingTestListener implements ITestInvocationListener, ILogSaverListener { 46 47 @Option( 48 name = "aggregate-metrics", 49 description = "attempt to add test metrics values for test runs with the same name.") 50 private boolean mIsAggregateMetrics = false; 51 52 /** Toggle the 'aggregate metrics' option */ setIsAggregrateMetrics(boolean aggregate)53 protected void setIsAggregrateMetrics(boolean aggregate) { 54 mIsAggregateMetrics = aggregate; 55 } 56 57 private IInvocationContext mContext; 58 private IBuildInfo mBuildInfo; 59 private Map<String, IInvocationContext> mModuleContextMap = new HashMap<>(); 60 // Use LinkedHashMap to provide consistent iterations over the keys. 61 private Map<String, List<TestRunResult>> mTestRunResultMap = 62 Collections.synchronizedMap(new LinkedHashMap<>()); 63 64 private IInvocationContext mCurrentModuleContext = null; 65 private TestRunResult mCurrentTestRunResult = new TestRunResult(); 66 /** True if the default initialized mCurrentTestRunResult has its original value. */ 67 private boolean mDefaultRun = true; 68 /** Track whether or not a test run is currently in progress */ 69 private boolean mRunInProgress = false; 70 71 private Map<String, LogFile> mNonAssociatedLogFiles = new LinkedHashMap<>(); 72 73 // Tracks if mStatusCounts are accurate, or if they need to be recalculated 74 private AtomicBoolean mIsCountDirty = new AtomicBoolean(true); 75 // Tracks if the expected count is accurate, or if it needs to be recalculated. 76 private AtomicBoolean mIsExpectedCountDirty = new AtomicBoolean(true); 77 private int mExpectedCount = 0; 78 79 // Represents the merged test results. This should not be accessed directly since it's only 80 // calculated when needed. 81 private final List<TestRunResult> mMergedTestRunResults = new ArrayList<>(); 82 // Represents the number of tests in each TestStatus state of the merged test results. Indexed 83 // by TestStatus.ordinal() 84 private int[] mStatusCounts = new int[TestStatus.values().length]; 85 86 private MergeStrategy mStrategy = MergeStrategy.ONE_TESTCASE_PASS_IS_PASS; 87 88 /** Sets the {@link MergeStrategy} to use when merging results. */ setMergeStrategy(MergeStrategy strategy)89 public void setMergeStrategy(MergeStrategy strategy) { 90 mStrategy = strategy; 91 } 92 93 /** 94 * Return the primary build info that was reported via {@link 95 * #invocationStarted(IInvocationContext)}. Primary build is the build returned by the first 96 * build provider of the running configuration. Returns null if there is no context (no build to 97 * test case). 98 */ getPrimaryBuildInfo()99 public IBuildInfo getPrimaryBuildInfo() { 100 if (mContext == null) { 101 return null; 102 } else { 103 return mContext.getBuildInfos().get(0); 104 } 105 } 106 107 /** 108 * Return the invocation context that was reported via {@link 109 * #invocationStarted(IInvocationContext)} 110 */ getInvocationContext()111 public IInvocationContext getInvocationContext() { 112 return mContext; 113 } 114 115 /** 116 * Returns the build info. 117 * 118 * @deprecated rely on the {@link IBuildInfo} from {@link #getInvocationContext()}. 119 */ 120 @Deprecated getBuildInfo()121 public IBuildInfo getBuildInfo() { 122 return mBuildInfo; 123 } 124 125 /** 126 * Set the build info. Should only be used for testing. 127 * 128 * @deprecated Not necessary for testing anymore. 129 */ 130 @VisibleForTesting 131 @Deprecated setBuildInfo(IBuildInfo buildInfo)132 public void setBuildInfo(IBuildInfo buildInfo) { 133 mBuildInfo = buildInfo; 134 } 135 136 /** {@inheritDoc} */ 137 @Override invocationStarted(IInvocationContext context)138 public void invocationStarted(IInvocationContext context) { 139 mContext = context; 140 mBuildInfo = getPrimaryBuildInfo(); 141 } 142 143 /** {@inheritDoc} */ 144 @Override invocationEnded(long elapsedTime)145 public void invocationEnded(long elapsedTime) { 146 // ignore 147 } 148 149 /** {@inheritDoc} */ 150 @Override invocationFailed(Throwable cause)151 public void invocationFailed(Throwable cause) { 152 // ignore 153 } 154 155 @Override testModuleStarted(IInvocationContext moduleContext)156 public void testModuleStarted(IInvocationContext moduleContext) { 157 mCurrentModuleContext = moduleContext; 158 } 159 160 @Override testModuleEnded()161 public void testModuleEnded() { 162 mCurrentModuleContext = null; 163 } 164 165 /** {@inheritDoc} */ 166 @Override testRunStarted(String name, int numTests)167 public void testRunStarted(String name, int numTests) { 168 testRunStarted(name, numTests, 0); 169 } 170 getNewRunResult()171 private TestRunResult getNewRunResult() { 172 TestRunResult result = new TestRunResult(); 173 if (mDefaultRun) { 174 result = mCurrentTestRunResult; 175 mDefaultRun = false; 176 } 177 result.setAggregateMetrics(mIsAggregateMetrics); 178 return result; 179 } 180 181 /** {@inheritDoc} */ 182 @Override testRunStarted(String name, int numTests, int attemptNumber)183 public void testRunStarted(String name, int numTests, int attemptNumber) { 184 testRunStarted(name, numTests, attemptNumber, System.currentTimeMillis()); 185 } 186 187 /** {@inheritDoc} */ 188 @Override testRunStarted(String name, int numTests, int attemptNumber, long startTime)189 public void testRunStarted(String name, int numTests, int attemptNumber, long startTime) { 190 setCountDirty(); 191 // Only testRunStarted can affect the expected count. 192 mIsExpectedCountDirty.set(true); 193 194 // Associate the run name with the current module context 195 if (mCurrentModuleContext != null) { 196 mModuleContextMap.put(name, mCurrentModuleContext); 197 } 198 199 // Add the list of maps if the run doesn't exist 200 if (!mTestRunResultMap.containsKey(name)) { 201 mTestRunResultMap.put(name, new LinkedList<>()); 202 } 203 List<TestRunResult> results = mTestRunResultMap.get(name); 204 205 // Set the current test run result based on the attempt 206 if (attemptNumber < results.size()) { 207 if (results.get(attemptNumber) == null) { 208 throw new RuntimeException( 209 "Test run results should never be null in internal structure."); 210 } 211 } else if (attemptNumber == results.size()) { 212 // new run 213 TestRunResult result = getNewRunResult(); 214 results.add(result); 215 } else { 216 int size = results.size(); 217 for (int i = size; i < attemptNumber; i++) { 218 TestRunResult result = getNewRunResult(); 219 result.testRunStarted(name, numTests, startTime); 220 String errorMessage = 221 String.format( 222 "Run attempt %s of %s did not exists, but got attempt %s." 223 + " This is a placeholder for the missing attempt.", 224 i, name, attemptNumber); 225 result.testRunFailed(errorMessage); 226 result.testRunEnded(0L, new HashMap<String, Metric>()); 227 results.add(result); 228 } 229 // New current run 230 TestRunResult newResult = getNewRunResult(); 231 results.add(newResult); 232 } 233 mCurrentTestRunResult = results.get(attemptNumber); 234 235 mCurrentTestRunResult.testRunStarted(name, numTests, startTime); 236 mRunInProgress = true; 237 } 238 239 /** {@inheritDoc} */ 240 @Override testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)241 public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) { 242 setCountDirty(); 243 mCurrentTestRunResult.testRunEnded(elapsedTime, runMetrics); 244 mRunInProgress = false; 245 } 246 247 /** {@inheritDoc} */ 248 @Override testRunFailed(String errorMessage)249 public void testRunFailed(String errorMessage) { 250 setCountDirty(); 251 mCurrentTestRunResult.testRunFailed(errorMessage); 252 } 253 254 /** {@inheritDoc} */ 255 @Override testRunFailed(FailureDescription failure)256 public void testRunFailed(FailureDescription failure) { 257 setCountDirty(); 258 mCurrentTestRunResult.testRunFailed(failure); 259 } 260 261 /** {@inheritDoc} */ 262 @Override testRunStopped(long elapsedTime)263 public void testRunStopped(long elapsedTime) { 264 setCountDirty(); 265 mCurrentTestRunResult.testRunStopped(elapsedTime); 266 } 267 268 /** {@inheritDoc} */ 269 @Override testStarted(TestDescription test)270 public void testStarted(TestDescription test) { 271 testStarted(test, System.currentTimeMillis()); 272 } 273 274 /** {@inheritDoc} */ 275 @Override testStarted(TestDescription test, long startTime)276 public void testStarted(TestDescription test, long startTime) { 277 setCountDirty(); 278 mCurrentTestRunResult.testStarted(test, startTime); 279 } 280 281 /** {@inheritDoc} */ 282 @Override testEnded(TestDescription test, HashMap<String, Metric> testMetrics)283 public void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) { 284 testEnded(test, System.currentTimeMillis(), testMetrics); 285 } 286 287 /** {@inheritDoc} */ 288 @Override testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics)289 public void testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 290 setCountDirty(); 291 mCurrentTestRunResult.testEnded(test, endTime, testMetrics); 292 } 293 294 /** {@inheritDoc} */ 295 @Override testFailed(TestDescription test, String trace)296 public void testFailed(TestDescription test, String trace) { 297 setCountDirty(); 298 mCurrentTestRunResult.testFailed(test, trace); 299 } 300 301 @Override testFailed(TestDescription test, FailureDescription failure)302 public void testFailed(TestDescription test, FailureDescription failure) { 303 setCountDirty(); 304 mCurrentTestRunResult.testFailed(test, failure); 305 } 306 307 @Override testAssumptionFailure(TestDescription test, String trace)308 public void testAssumptionFailure(TestDescription test, String trace) { 309 setCountDirty(); 310 mCurrentTestRunResult.testAssumptionFailure(test, trace); 311 } 312 313 @Override testIgnored(TestDescription test)314 public void testIgnored(TestDescription test) { 315 setCountDirty(); 316 mCurrentTestRunResult.testIgnored(test); 317 } 318 319 /** {@inheritDoc} */ 320 @Override logAssociation(String dataName, LogFile logFile)321 public void logAssociation(String dataName, LogFile logFile) { 322 if (mRunInProgress) { 323 mCurrentTestRunResult.testLogSaved(dataName, logFile); 324 } else { 325 mNonAssociatedLogFiles.put(dataName, logFile); 326 } 327 } 328 329 /** 330 * Gets the results for the current test run. 331 * 332 * <p>Note the results may not be complete. It is recommended to test the value of {@link 333 * TestRunResult#isRunComplete()} and/or (@link TestRunResult#isRunFailure()} as appropriate 334 * before processing the results. 335 * 336 * @return the {@link TestRunResult} representing data collected during last test run 337 */ getCurrentRunResults()338 public TestRunResult getCurrentRunResults() { 339 return mCurrentTestRunResult; 340 } 341 342 /** Returns the total number of complete tests for all runs. */ getNumTotalTests()343 public int getNumTotalTests() { 344 computeMergedResults(); 345 int total = 0; 346 for (TestStatus s : TestStatus.values()) { 347 total += mStatusCounts[s.ordinal()]; 348 } 349 return total; 350 } 351 352 /** 353 * Returns the number of expected tests count. Could differ from {@link #getNumTotalTests()} if 354 * some tests did not run. 355 */ getExpectedTests()356 public synchronized int getExpectedTests() { 357 // If expected count is not dirty, no need to do anything 358 if (!mIsExpectedCountDirty.compareAndSet(true, false)) { 359 return mExpectedCount; 360 } 361 362 computeMergedResults(); 363 mExpectedCount = 0; 364 for (TestRunResult result : getMergedTestRunResults()) { 365 mExpectedCount += result.getExpectedTestCount(); 366 } 367 return mExpectedCount; 368 } 369 370 /** Returns the number of tests in given state for this run. */ getNumTestsInState(TestStatus status)371 public int getNumTestsInState(TestStatus status) { 372 computeMergedResults(); 373 return mStatusCounts[status.ordinal()]; 374 } 375 376 /** Returns if the invocation had any failed or assumption failed tests. */ hasFailedTests()377 public boolean hasFailedTests() { 378 return getNumAllFailedTests() > 0; 379 } 380 381 /** Returns the total number of test runs in a failure state */ getNumAllFailedTestRuns()382 public int getNumAllFailedTestRuns() { 383 int count = 0; 384 for (TestRunResult result : getMergedTestRunResults()) { 385 if (result.isRunFailure()) { 386 count++; 387 } 388 } 389 return count; 390 } 391 392 /** 393 * Returns the total number of tests in a failure state (only failed, assumption failures do not 394 * count toward it). 395 */ getNumAllFailedTests()396 public int getNumAllFailedTests() { 397 return getNumTestsInState(TestStatus.FAILURE); 398 } 399 400 /** 401 * Return the merged collection of results for all runs across different attempts. 402 * 403 * <p>If there are multiple results, each test run is merged, with the latest test result 404 * overwriting test results of previous runs. Test runs are ordered by attempt number. 405 * 406 * <p>Metrics for the same attempt will be merged based on the preference set by {@code 407 * aggregate-metrics}. The final metrics will be the metrics of the last attempt. 408 */ getMergedTestRunResults()409 public List<TestRunResult> getMergedTestRunResults() { 410 computeMergedResults(); 411 return new ArrayList<>(mMergedTestRunResults); 412 } 413 414 /** 415 * Returns the results for all test runs. 416 * 417 * @deprecated Use {@link #getMergedTestRunResults()} 418 */ 419 @Deprecated getRunResults()420 public Collection<TestRunResult> getRunResults() { 421 return getMergedTestRunResults(); 422 } 423 424 /** 425 * Computes and stores the merged results and the total status counts since both operations are 426 * expensive. 427 */ computeMergedResults()428 private synchronized void computeMergedResults() { 429 // If not dirty, nothing to be done 430 if (!mIsCountDirty.compareAndSet(true, false)) { 431 return; 432 } 433 434 mMergedTestRunResults.clear(); 435 // Merge results 436 if (mTestRunResultMap.isEmpty() && mCurrentTestRunResult.isRunFailure()) { 437 // In case of early failure that is a bit untracked, still add it to the list to 438 // not loose it. 439 CLog.e( 440 "Early failure resulting in no testRunStart. Results might be inconsistent:" 441 + "\n%s", 442 mCurrentTestRunResult.getRunFailureMessage()); 443 mMergedTestRunResults.add(mCurrentTestRunResult); 444 } else { 445 for (Entry<String, List<TestRunResult>> results : mTestRunResultMap.entrySet()) { 446 TestRunResult res = TestRunResult.merge(results.getValue(), mStrategy); 447 if (res == null) { 448 // Merge can return null in case of results being empty. 449 CLog.w("No results for %s", results.getKey()); 450 } else { 451 mMergedTestRunResults.add(res); 452 } 453 } 454 } 455 // Reset counts 456 for (TestStatus s : TestStatus.values()) { 457 mStatusCounts[s.ordinal()] = 0; 458 } 459 460 // Calculate results 461 for (TestRunResult result : mMergedTestRunResults) { 462 for (TestStatus s : TestStatus.values()) { 463 mStatusCounts[s.ordinal()] += result.getNumTestsInState(s); 464 } 465 } 466 } 467 468 /** 469 * Keep dirty count as AtomicBoolean to ensure when accessed from another thread the state is 470 * consistent. 471 */ setCountDirty()472 private void setCountDirty() { 473 mIsCountDirty.set(true); 474 } 475 476 /** 477 * Return all the names for all the test runs. 478 * 479 * <p>These test runs may have run multiple times with different attempts. 480 */ getTestRunNames()481 public Collection<String> getTestRunNames() { 482 return new ArrayList<String>(mTestRunResultMap.keySet()); 483 } 484 485 /** 486 * Gets all the attempts for a {@link TestRunResult} of a given test run. 487 * 488 * @param testRunName The name given by {{@link #testRunStarted(String, int)}. 489 * @return All {@link TestRunResult} for a given test run, ordered by attempts. 490 */ getTestRunAttempts(String testRunName)491 public List<TestRunResult> getTestRunAttempts(String testRunName) { 492 return mTestRunResultMap.get(testRunName); 493 } 494 495 /** 496 * Gets all the results for a given attempt. 497 * 498 * @param attempt The attempt we want results for. 499 * @return All {@link TestRunResult} for a given attempt. 500 */ getTestRunForAttempts(int attempt)501 public List<TestRunResult> getTestRunForAttempts(int attempt) { 502 List<TestRunResult> allResultForAttempts = new ArrayList<>(); 503 for (Entry<String, List<TestRunResult>> runInfo : mTestRunResultMap.entrySet()) { 504 if (attempt < runInfo.getValue().size()) { 505 TestRunResult attemptRes = runInfo.getValue().get(attempt); 506 allResultForAttempts.add(attemptRes); 507 } 508 } 509 return allResultForAttempts; 510 } 511 512 /** 513 * Returns whether a given test run name has any results. 514 * 515 * @param testRunName The name given by {{@link #testRunStarted(String, int)}. 516 */ hasTestRunResultsForName(String testRunName)517 public boolean hasTestRunResultsForName(String testRunName) { 518 return mTestRunResultMap.containsKey(testRunName); 519 } 520 521 /** 522 * Returns the number of attempts for a given test run name. 523 * 524 * @param testRunName The name given by {{@link #testRunStarted(String, int)}. 525 */ getTestRunAttemptCount(String testRunName)526 public int getTestRunAttemptCount(String testRunName) { 527 List<TestRunResult> results = mTestRunResultMap.get(testRunName); 528 if (results == null) { 529 return 0; 530 } 531 return results.size(); 532 } 533 534 /** 535 * Return the {@link TestRunResult} for a single attempt. 536 * 537 * @param testRunName The name given by {{@link #testRunStarted(String, int)}. 538 * @param attempt The attempt id. 539 * @return The {@link TestRunResult} for the given name and attempt id or {@code null} if it 540 * does not exist. 541 */ getTestRunAtAttempt(String testRunName, int attempt)542 public TestRunResult getTestRunAtAttempt(String testRunName, int attempt) { 543 List<TestRunResult> results = mTestRunResultMap.get(testRunName); 544 if (results == null || attempt < 0 || attempt >= results.size()) { 545 return null; 546 } 547 548 return results.get(attempt); 549 } 550 551 /** 552 * Returns the {@link IInvocationContext} of the module associated with the results. 553 * 554 * @param testRunName The name given by {{@link #testRunStarted(String, int)}. 555 * @return The {@link IInvocationContext} of the module for a given test run name {@code null} 556 * if there are no results for that name. 557 */ getModuleContextForRunResult(String testRunName)558 public IInvocationContext getModuleContextForRunResult(String testRunName) { 559 return mModuleContextMap.get(testRunName); 560 } 561 562 /** Returns a copy of the map containing all the logged file not associated with a test run. */ getNonAssociatedLogFiles()563 public Map<String, LogFile> getNonAssociatedLogFiles() { 564 return new LinkedHashMap<>(mNonAssociatedLogFiles); 565 } 566 567 /** 568 * Allows to clear the results for a given run name. Should only be used in some cases like the 569 * aggregator of results. 570 */ clearResultsForName(String testRunName)571 protected final synchronized void clearResultsForName(String testRunName) { 572 setCountDirty(); 573 mTestRunResultMap.remove(testRunName); 574 } 575 } 576