• 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.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