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