| /* |
| * 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(); |
| } |
| } |