blob: 1b0590c8784d5834bbc3d2e3395102b319b4bf96 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.result;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.invoker.IInvocationContext;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Result reporter to print the test results to the console.
*
* <p>Prints each test run, each test case, and test metrics, test logs, and test file locations.
*
* <p>
*/
@OptionClass(alias = "console-result-reporter")
public class ConsoleResultReporter extends TestResultListener
implements ILogSaverListener, ITestInvocationListener {
private static final SimpleDateFormat sTimeStampFormat = new SimpleDateFormat("HH:mm:ss");
@Option(
name = "suppress-passed-tests",
description =
"For functional tests, ommit summary for "
+ "passing tests, only print failed and ignored ones")
private boolean mSuppressPassedTest = false;
@Option(
name = "display-failure-summary",
description = "Display all the failures at the very end for easier visualization.")
private boolean mDisplayFailureSummary = true;
@Option(
name = "display-invocation-attributes",
description =
"Display all the invocation attributes at the very end for easier"
+ " visualization.")
private boolean mDisplayInvocationAttributes = false;
private final PrintStream mStream;
private Set<LogFile> mLoggedFiles = new LinkedHashSet<>();
private Map<TestDescription, TestResult> mFailures = new LinkedHashMap<>();
private String mTestTag;
private String mRunInProgress;
private CountingTestResultListener mResultCountListener = new CountingTestResultListener();
private IInvocationContext mContext;
public ConsoleResultReporter() {
this(System.out);
}
ConsoleResultReporter(PrintStream outputStream) {
mStream = outputStream;
}
@Override
public void invocationStarted(IInvocationContext context) {
mTestTag = context.getTestTag();
mContext = context;
}
@Override
public void testResult(TestDescription test, TestResult result) {
mResultCountListener.testResult(test, result);
if (mSuppressPassedTest && TestStatus.PASSED.equals(result.getStatus())) {
return;
}
if (mDisplayFailureSummary && TestStatus.FAILURE.equals(result.getStatus())) {
mFailures.put(test, result);
}
print(getTestSummary(mTestTag, test, result));
}
@Override
public void testRunStarted(String runName, int testCount) {
super.testRunStarted(runName, testCount);
mRunInProgress = runName;
}
@Override
public void testRunFailed(String errorMessage) {
print(String.format("%s: run failed: %s\n", mRunInProgress, errorMessage));
}
@Override
public void testRunFailed(FailureDescription failure) {
print(String.format("%s: run failed: %s\n", mRunInProgress, failure));
}
@Override
public void testRunEnded(long elapsedTimeMillis, Map<String, String> metrics) {
super.testRunEnded(elapsedTimeMillis, metrics);
if (metrics != null && !metrics.isEmpty()) {
String tag = mTestTag != null ? mTestTag : "unknown";
String runName = mRunInProgress != null ? mRunInProgress : "unknown";
StringBuilder sb = new StringBuilder(tag);
sb.append(": ");
sb.append(runName);
sb.append(": ");
List<String> metricKeys = new ArrayList<String>(metrics.keySet());
Collections.sort(metricKeys);
for (String metricKey : metricKeys) {
sb.append(String.format("%s=%s\n", metricKey, metrics.get(metricKey)));
}
print(sb.toString());
}
mRunInProgress = null;
}
/** {@inheritDoc} */
@Override
public void invocationEnded(long elapsedTime) {
int[] results = mResultCountListener.getResultCounts();
StringBuilder sb = new StringBuilder();
sb.append("========== Result Summary ==========");
sb.append(String.format("\nResults summary for test-tag '%s': ", mTestTag));
sb.append(mResultCountListener.getTotalTests());
sb.append(" Tests [");
sb.append(results[TestStatus.PASSED.ordinal()]);
sb.append(" Passed");
if (results[TestStatus.FAILURE.ordinal()] > 0) {
sb.append(" ");
sb.append(results[TestStatus.FAILURE.ordinal()]);
sb.append(" Failed");
}
if (results[TestStatus.IGNORED.ordinal()] > 0) {
sb.append(" ");
sb.append(results[TestStatus.IGNORED.ordinal()]);
sb.append(" Ignored");
}
if (results[TestStatus.ASSUMPTION_FAILURE.ordinal()] > 0) {
sb.append(" ");
sb.append(results[TestStatus.ASSUMPTION_FAILURE.ordinal()]);
sb.append(" Assumption failures");
}
if (results[TestStatus.INCOMPLETE.ordinal()] > 0) {
sb.append(" ");
sb.append(results[TestStatus.INCOMPLETE.ordinal()]);
sb.append(" Incomplete");
}
sb.append("] \r\n");
print(sb.toString());
if (mDisplayInvocationAttributes && !mContext.getAttributes().isEmpty()) {
StringBuilder metricPrint = new StringBuilder();
metricPrint.append(" Metrics:\n");
for (String key : mContext.getAttributes().keySet()) {
metricPrint.append(
" " + key + "=" + mContext.getAttributes().get(key).toString() + "\n");
}
print(metricPrint.toString());
}
if (mDisplayFailureSummary) {
for (Entry<TestDescription, TestResult> entry : mFailures.entrySet()) {
print(getTestSummary(mTestTag, entry.getKey(), entry.getValue()));
}
}
// Print the logs
for (LogFile logFile : mLoggedFiles) {
printLog(logFile);
}
}
/** {@inheritDoc} */
@Override
public void logAssociation(String dataName, LogFile logFile) {
mLoggedFiles.add(logFile);
}
/** {@inheritDoc} */
@Override
public void testLogSaved(
String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) {
mLoggedFiles.add(logFile);
}
private void printLog(LogFile logFile) {
if (mSuppressPassedTest && !mResultCountListener.hasFailedTests()) {
// all tests passed, skip logging
return;
}
String logDesc = logFile.getUrl() == null ? logFile.getPath() : logFile.getUrl();
print("Log: " + logDesc + "\r\n");
}
/** Get the test summary as string including test metrics. */
static String getTestSummary(String testTag, TestDescription testId, TestResult testResult) {
StringBuilder sb = new StringBuilder();
sb.append(
String.format(
"%s: %s: %s (%dms)\n",
testTag,
testId.toString(),
testResult.getStatus(),
testResult.getEndTime() - testResult.getStartTime()));
String stack = testResult.getStackTrace();
if (stack != null && !stack.isEmpty()) {
sb.append(" stack=\n");
String lines[] = stack.split("\\r?\\n");
for (String line : lines) {
sb.append(String.format(" %s\n", line));
}
}
Map<String, String> metrics = testResult.getMetrics();
if (metrics != null && !metrics.isEmpty()) {
List<String> metricKeys = new ArrayList<String>(metrics.keySet());
Collections.sort(metricKeys);
for (String metricKey : metricKeys) {
sb.append(String.format(" %s: %s\n", metricKey, metrics.get(metricKey)));
}
}
return sb.toString();
}
private void print(String msg) {
mStream.print(sTimeStampFormat.format(new Date()) + " " + msg);
}
}