blob: 9025bdd3822a13c6a5d1e7f306b8c4b50da107e7 [file] [log] [blame]
/*
* Copyright (C) 2010 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.ddmlib.testrunner;
import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.google.common.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* Holds results from a single test run.
* <p>
* Maintains an accurate count of tests, and tracks incomplete tests.
* <p>
* Not thread safe! The test* callbacks must be called in order
*/
public class TestRunResult implements ITestRunListener {
private static final String LOG_TAG = TestRunResult.class.getSimpleName();
private String mTestRunName;
// Uses a LinkedHashMap to have predictable iteration order
private Map<TestIdentifier, TestResult> mTestResults =
new LinkedHashMap<TestIdentifier, TestResult>();
private Map<String, String> mRunMetrics = new HashMap<String, String>();
private boolean mIsRunComplete = false;
private long mElapsedTime = 0;
/** represents sums of tests in each TestStatus state. Indexed by TestStatus.ordinal() */
private int[] mStatusCounts = new int[TestStatus.values().length];
/** tracks if mStatusCounts is accurate, or if it needs to be recalculated */
private boolean mIsCountDirty = true;
private String mRunFailureError = null;
private boolean mAggregateMetrics = false;
/**
* Create an empty{@link TestRunResult}.
*/
public TestRunResult() {
mTestRunName = "not started";
}
public void setAggregateMetrics(boolean metricAggregation) {
mAggregateMetrics = metricAggregation;
}
/**
* @return the test run name
*/
public String getName() {
return mTestRunName;
}
/** Returns a map of the test results. */
public Map<TestIdentifier, TestResult> getTestResults() {
return mTestResults;
}
/**
* @return a {@link Map} of the test test run metrics.
*/
public Map<String, String> getRunMetrics() {
return mRunMetrics;
}
/**
* Gets the set of completed tests.
*/
public Set<TestIdentifier> getCompletedTests() {
Set<TestIdentifier> completedTests = new LinkedHashSet<TestIdentifier>();
for (Map.Entry<TestIdentifier, TestResult> testEntry : getTestResults().entrySet()) {
if (!testEntry.getValue().getStatus().equals(TestStatus.INCOMPLETE)) {
completedTests.add(testEntry.getKey());
}
}
return completedTests;
}
/**
* @return <code>true</code> if test run failed.
*/
public boolean isRunFailure() {
return mRunFailureError != null;
}
/**
* @return <code>true</code> if test run finished.
*/
public boolean isRunComplete() {
return mIsRunComplete;
}
public void setRunComplete(boolean runComplete) {
mIsRunComplete = runComplete;
}
/**
* Gets the number of tests in given state for this run.
*/
public int getNumTestsInState(TestStatus status) {
if (mIsCountDirty) {
// clear counts
for (int i=0; i < mStatusCounts.length; i++) {
mStatusCounts[i] = 0;
}
// now recalculate
for (TestResult r : mTestResults.values()) {
mStatusCounts[r.getStatus().ordinal()]++;
}
mIsCountDirty = false;
}
return mStatusCounts[status.ordinal()];
}
/**
* Gets the number of tests in this run.
*/
public int getNumTests() {
return mTestResults.size();
}
/**
* Gets the number of complete tests in this run ie with status != incomplete.
*/
public int getNumCompleteTests() {
return getNumTests() - getNumTestsInState(TestStatus.INCOMPLETE);
}
/**
* @return <code>true</code> if test run had any failed or error tests.
*/
public boolean hasFailedTests() {
return getNumAllFailedTests() > 0;
}
/**
* Return total number of tests in a failure state (failed, assumption failure)
*/
public int getNumAllFailedTests() {
return getNumTestsInState(TestStatus.FAILURE);
}
/** Returns the current run elapsed time. */
public long getElapsedTime() {
return mElapsedTime;
}
/**
* Return the run failure error message, <code>null</code> if run did not fail.
*/
public String getRunFailureMessage() {
return mRunFailureError;
}
@Override
public void testRunStarted(String runName, int testCount) {
mTestRunName = runName;
mIsRunComplete = false;
mRunFailureError = null;
}
@Override
public void testStarted(TestIdentifier test) {
testStarted(test, System.currentTimeMillis());
}
@VisibleForTesting
void testStarted(TestIdentifier test, long startTime) {
TestResult res = new TestResult();
res.setStartTime(startTime);
addTestResult(test, res);
}
private void addTestResult(TestIdentifier test, TestResult testResult) {
mIsCountDirty = true;
mTestResults.put(test, testResult);
}
private void updateTestResult(TestIdentifier test, TestStatus status, String trace) {
TestResult r = mTestResults.get(test);
if (r == null) {
Log.d(LOG_TAG, String.format("received test event without test start for %s", test));
r = new TestResult();
}
r.setStatus(status);
r.setStackTrace(trace);
addTestResult(test, r);
}
@Override
public void testFailed(TestIdentifier test, String trace) {
updateTestResult(test, TestStatus.FAILURE, trace);
}
@Override
public void testAssumptionFailure(TestIdentifier test, String trace) {
updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, trace);
}
@Override
public void testIgnored(TestIdentifier test) {
updateTestResult(test, TestStatus.IGNORED, null);
}
@Override
public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
testEnded(test, System.currentTimeMillis(), testMetrics);
}
@VisibleForTesting
void testEnded(TestIdentifier test, long endTime, Map<String, String> testMetrics) {
TestResult result = mTestResults.get(test);
if (result == null) {
result = new TestResult();
}
if (result.getStatus().equals(TestStatus.INCOMPLETE)) {
result.setStatus(TestStatus.PASSED);
}
result.setEndTime(endTime);
result.setMetrics(testMetrics);
addTestResult(test, result);
}
@Override
public void testRunFailed(String errorMessage) {
mRunFailureError = errorMessage;
}
@Override
public void testRunStopped(long elapsedTime) {
mElapsedTime+= elapsedTime;
mIsRunComplete = true;
}
@Override
public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
if (mAggregateMetrics) {
for (Map.Entry<String, String> entry : runMetrics.entrySet()) {
String existingValue = mRunMetrics.get(entry.getKey());
String combinedValue = combineValues(existingValue, entry.getValue());
mRunMetrics.put(entry.getKey(), combinedValue);
}
} else {
mRunMetrics.putAll(runMetrics);
}
mElapsedTime+= elapsedTime;
mIsRunComplete = true;
}
/**
* Combine old and new metrics value
*
* @param existingValue
* @param newValue
* @return the combination of the two string as Long or Double value.
*/
private String combineValues(String existingValue, String newValue) {
if (existingValue != null) {
try {
Long existingLong = Long.parseLong(existingValue);
Long newLong = Long.parseLong(newValue);
return Long.toString(existingLong + newLong);
} catch (NumberFormatException e) {
// not a long, skip to next
}
try {
Double existingDouble = Double.parseDouble(existingValue);
Double newDouble = Double.parseDouble(newValue);
return Double.toString(existingDouble + newDouble);
} catch (NumberFormatException e) {
// not a double either, fall through
}
}
// default to overriding existingValue
return newValue;
}
/** Returns a user friendly string describing results. */
public String getTextSummary() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Total tests %d, ", getNumTests()));
for (TestStatus status : TestStatus.values()) {
int count = getNumTestsInState(status);
// only add descriptive state for states that have non zero values, to avoid cluttering
// the response
if (count > 0) {
builder.append(String.format("%s %d, ", status.toString().toLowerCase(), count));
}
}
return builder.toString();
}
}