1 /*
2  * Copyright (C) 2011 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.invoker;
17 
18 import com.android.ddmlib.Log.LogLevel;
19 import com.android.tradefed.log.LogUtil.CLog;
20 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
21 import com.android.tradefed.result.CollectingTestListener;
22 import com.android.tradefed.result.FailureDescription;
23 import com.android.tradefed.result.ILogSaverListener;
24 import com.android.tradefed.result.ITestInvocationListener;
25 import com.android.tradefed.result.InputStreamSource;
26 import com.android.tradefed.result.LogDataType;
27 import com.android.tradefed.result.LogFile;
28 import com.android.tradefed.result.TestDescription;
29 import com.android.tradefed.result.TestResult;
30 import com.android.tradefed.result.TestRunResult;
31 import com.android.tradefed.result.TestStatus;
32 import com.android.tradefed.result.retry.ISupportGranularResults;
33 import com.android.tradefed.result.skipped.SkipReason;
34 import com.android.tradefed.util.MultiMap;
35 import com.android.tradefed.util.TimeUtil;
36 
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Map.Entry;
43 
44 /**
45  * A {@link ITestInvocationListener} that collects results from a invocation shard (aka an
46  * invocation split to run on multiple resources in parallel), and forwards them to another
47  * listener.
48  */
49 public class ShardListener extends CollectingTestListener implements ISupportGranularResults {
50 
51     private ITestInvocationListener mMainListener;
52     private ShardMainResultForwarder mShardMainForwarder;
53     private IInvocationContext mModuleContext = null;
54     private int mAttemptInProgress = 0;
55     private boolean mEnableGranularResults = false;
56     private IInvocationContext mContext;
57 
58     /**
59      * Create a {@link ShardListener}.
60      *
61      * @param main the {@link ITestInvocationListener} the results should be forwarded. To prevent
62      *     collisions with other {@link ShardListener}s, this object will synchronize on
63      *     <var>main</var> when forwarding results. And results will only be sent once the
64      *     invocation shard completes.
65      */
ShardListener(ITestInvocationListener main)66     public ShardListener(ITestInvocationListener main) {
67         mMainListener = main;
68         if (main instanceof ShardMainResultForwarder) {
69             mShardMainForwarder = (ShardMainResultForwarder) main;
70         }
71     }
72 
getUnderlyingResultReporter()73     public List<ITestInvocationListener> getUnderlyingResultReporter() {
74         return mShardMainForwarder.getListeners();
75     }
76 
77     /** {@inheritDoc} */
78     @Override
supportGranularResults()79     public boolean supportGranularResults() {
80         return mEnableGranularResults;
81     }
82 
setSupportGranularResults(boolean enableGranularResults)83     public void setSupportGranularResults(boolean enableGranularResults) {
84         mEnableGranularResults = enableGranularResults;
85     }
86 
87     /**
88      * {@inheritDoc}
89      */
90     @Override
invocationStarted(IInvocationContext context)91     public void invocationStarted(IInvocationContext context) {
92         mContext = context;
93         super.invocationStarted(context);
94         synchronized (mMainListener) {
95             mMainListener.invocationStarted(context);
96         }
97     }
98 
99     /**
100      * {@inheritDoc}
101      */
102     @Override
invocationFailed(Throwable cause)103     public void invocationFailed(Throwable cause) {
104         super.invocationFailed(cause);
105         synchronized (mMainListener) {
106             mMainListener.invocationFailed(cause);
107         }
108     }
109 
110     /** {@inheritDoc} */
111     @Override
invocationFailed(FailureDescription failure)112     public void invocationFailed(FailureDescription failure) {
113         super.invocationFailed(failure);
114         synchronized (mMainListener) {
115             mMainListener.invocationFailed(failure);
116         }
117     }
118 
119     @Override
invocationSkipped(SkipReason reason)120     public void invocationSkipped(SkipReason reason) {
121         super.invocationSkipped(reason);
122         synchronized (mMainListener) {
123             mMainListener.invocationSkipped(reason);
124         }
125     }
126 
127     /**
128      * {@inheritDoc}
129      */
130     @Override
testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)131     public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
132         // forward testLog results immediately, since result reporters might take action on it.
133         synchronized (mMainListener) {
134             if (mMainListener instanceof ShardMainResultForwarder) {
135                 // If the listener is a log saver, we should simply forward the testLog not save
136                 // again.
137                 ((ShardMainResultForwarder) mMainListener)
138                         .testLogForward(dataName, dataType, dataStream);
139             } else {
140                 mMainListener.testLog(dataName, dataType, dataStream);
141             }
142         }
143     }
144 
145     /** {@inheritDoc} */
146     @Override
testLogSaved( String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)147     public void testLogSaved(
148             String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) {
149         super.testLogSaved(dataName, dataType, dataStream, logFile);
150         // Forward the testLogSaved callback.
151         synchronized (mMainListener) {
152             if (mMainListener instanceof ILogSaverListener) {
153                 ((ILogSaverListener) mMainListener)
154                         .testLogSaved(dataName, dataType, dataStream, logFile);
155             }
156         }
157     }
158 
159     /** {@inheritDoc} */
160     @Override
testModuleStarted(IInvocationContext moduleContext)161     public void testModuleStarted(IInvocationContext moduleContext) {
162         super.testModuleStarted(moduleContext);
163         mModuleContext = moduleContext;
164     }
165 
166     /** {@inheritDoc} */
167     @Override
testRunStarted(String name, int numTests, int attemptNumber, long startTime)168     public void testRunStarted(String name, int numTests, int attemptNumber, long startTime) {
169         super.testRunStarted(name, numTests, attemptNumber, startTime);
170         mAttemptInProgress = attemptNumber;
171     }
172 
173     /**
174      * {@inheritDoc}
175      */
176     @Override
testRunFailed(String failureMessage)177     public void testRunFailed(String failureMessage) {
178         super.testRunFailed(failureMessage);
179         CLog.logAndDisplay(LogLevel.ERROR, "FAILED: %s failed with message: %s",
180                 getCurrentRunResults().getName(), failureMessage);
181     }
182 
183     /** {@inheritDoc} */
184     @Override
testRunFailed(FailureDescription failure)185     public void testRunFailed(FailureDescription failure) {
186         super.testRunFailed(failure);
187         CLog.logAndDisplay(
188                 LogLevel.ERROR,
189                 "FAILED: %s failed with message: %s",
190                 getCurrentRunResults().getName(),
191                 failure.toString());
192     }
193 
194     /** {@inheritDoc} */
195     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)196     public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
197         super.testRunEnded(elapsedTime, runMetrics);
198         CLog.logAndDisplay(
199                 LogLevel.INFO, "Sharded test completed: %s", getCurrentRunResults().getName());
200         if (mModuleContext == null) {
201             // testRunEnded only forwards if it's not part of a module. If it's a module
202             // testModuleEnded is in charge of forwarding all run results.
203             synchronized (mMainListener) {
204                 forwardRunResults(getCurrentRunResults(), mAttemptInProgress);
205             }
206             mAttemptInProgress = 0;
207         }
208 
209     }
210 
211     /** {@inheritDoc} */
212     @Override
testModuleEnded()213     public void testModuleEnded() {
214         super.testModuleEnded();
215 
216         synchronized (mMainListener) {
217             mMainListener.testModuleStarted(mModuleContext);
218             List<String> resultNames = new ArrayList<String>();
219             if (mEnableGranularResults) {
220                 for (int i = 0; i < mAttemptInProgress + 1; i++) {
221                     List<TestRunResult> runResults = getTestRunForAttempts(i);
222                     for (TestRunResult runResult : runResults) {
223                         forwardRunResults(runResult, i);
224                         resultNames.add(runResult.getName());
225                     }
226                 }
227             } else {
228                 for (TestRunResult runResult : getMergedTestRunResults()) {
229                     // Forward the run level results
230                     forwardRunResults(runResult, 0);
231                     // Release memory right away
232                     clearResultsForName(runResult.getName());
233                 }
234             }
235 
236             // Ensure we don't carry results from one module to another.
237             for (String name : resultNames) {
238                 clearResultsForName(name);
239             }
240             forwardLogAssociation(getModuleLogFiles(), mMainListener);
241             // Clean the file we just logged to avoid overlap
242             clearModuleLogFiles();
243             mMainListener.testModuleEnded();
244         }
245         mModuleContext = null;
246     }
247 
248     /** {@inheritDoc} */
249     @Override
invocationEnded(long elapsedTime)250     public void invocationEnded(long elapsedTime) {
251         super.invocationEnded(elapsedTime);
252         synchronized (mMainListener) {
253             logShardContent(getMergedTestRunResults());
254             // Report all logs not associated with test runs
255             forwardLogAssociation(getNonAssociatedLogFiles(), mMainListener);
256             if (mShardMainForwarder != null) {
257                 mShardMainForwarder.invocationEnded(elapsedTime, mContext);
258             } else {
259                 mMainListener.invocationEnded(elapsedTime);
260             }
261         }
262     }
263 
264     @Override
logAssociation(String dataName, LogFile logFile)265     public void logAssociation(String dataName, LogFile logFile) {
266         if (dataName.equals("invocation-trace")) {
267             CLog.d("Received a trace for shard");
268             mShardMainForwarder.logAssociation(dataName, logFile);
269         } else {
270             super.logAssociation(dataName, logFile);
271         }
272     }
273 
forwardRunResults(TestRunResult runResult, int attempt)274     private void forwardRunResults(TestRunResult runResult, int attempt) {
275         mMainListener.testRunStarted(
276                 runResult.getName(),
277                 runResult.getExpectedTestCount(),
278                 attempt,
279                 runResult.getStartTime());
280         forwardTestResults(runResult.getTestResults());
281         if (runResult.isRunFailure()) {
282             mMainListener.testRunFailed(runResult.getRunFailureDescription());
283         }
284 
285         // Provide a strong association of the run to its logs.
286         forwardLogAssociation(runResult.getRunLoggedFiles(), mMainListener);
287 
288         mMainListener.testRunEnded(runResult.getElapsedTime(), runResult.getRunProtoMetrics());
289     }
290 
forwardTestResults(Map<TestDescription, TestResult> testResults)291     private void forwardTestResults(Map<TestDescription, TestResult> testResults) {
292         for (Map.Entry<TestDescription, TestResult> testEntry : testResults.entrySet()) {
293             mMainListener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
294             switch (testEntry.getValue().getStatus()) {
295                 case FAILURE:
296                     mMainListener.testFailed(testEntry.getKey(), testEntry.getValue().getFailure());
297                     break;
298                 case ASSUMPTION_FAILURE:
299                     mMainListener.testAssumptionFailure(
300                             testEntry.getKey(), testEntry.getValue().getFailure());
301                     break;
302                 case IGNORED:
303                     mMainListener.testIgnored(testEntry.getKey());
304                     break;
305                 default:
306                     break;
307             }
308             // Provide a strong association of the test to its logs.
309             forwardLogAssociation(testEntry.getValue().getLoggedFiles(), mMainListener);
310 
311             if (!testEntry.getValue().getResultStatus().equals(TestStatus.INCOMPLETE)) {
312                 mMainListener.testEnded(
313                         testEntry.getKey(),
314                         testEntry.getValue().getEndTime(),
315                         testEntry.getValue().getProtoMetrics());
316             }
317         }
318     }
319 
320     /** Forward to the listener the logAssociated callback on the files. */
forwardLogAssociation( MultiMap<String, LogFile> loggedFiles, ITestInvocationListener listener)321     private void forwardLogAssociation(
322             MultiMap<String, LogFile> loggedFiles, ITestInvocationListener listener) {
323         for (String key : loggedFiles.keySet()) {
324             for (LogFile logFile : loggedFiles.get(key)) {
325                 if (listener instanceof ILogSaverListener) {
326                     ((ILogSaverListener) listener).logAssociation(key, logFile);
327                 }
328             }
329         }
330     }
331 
332     /** Forward test cases logged files. */
forwardLogAssociation( Map<String, LogFile> loggedFiles, ITestInvocationListener listener)333     private void forwardLogAssociation(
334             Map<String, LogFile> loggedFiles, ITestInvocationListener listener) {
335         for (Entry<String, LogFile> logFile : loggedFiles.entrySet()) {
336             if (listener instanceof ILogSaverListener) {
337                 ((ILogSaverListener) listener).logAssociation(logFile.getKey(), logFile.getValue());
338             }
339         }
340     }
341 
342     /** Log the content of the shard for easier debugging. */
logShardContent(Collection<TestRunResult> listResults)343     private void logShardContent(Collection<TestRunResult> listResults) {
344         StringBuilder sb = new StringBuilder();
345         sb.append("=================================================\n");
346         sb.append(
347                 String.format(
348                         "========== Shard Primary Device %s ==========\n",
349                         getInvocationContext().getDevices().get(0).getSerialNumber()));
350         for (TestRunResult runRes : listResults) {
351             sb.append(
352                     String.format(
353                             "\tRan '%s' in %s\n",
354                             runRes.getName(), TimeUtil.formatElapsedTime(runRes.getElapsedTime())));
355         }
356         sb.append("=================================================\n");
357         CLog.logAndDisplay(LogLevel.DEBUG, sb.toString());
358     }
359 }
360