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