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