blob: 20afec146599268eef575b57b17f96f3abcb099a [file] [log] [blame]
/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.tests.buildbot.runner;
import com.google.dart.tools.core.test.IgnoreLoggedErrors;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.junit.Ignore;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An abstract class to execute JUnit tests. Subclasses must implement the five abstract methods.
* The call order is:
* <p>
* testsStarted()
* <p>
* testStarted()
* <p>
* testPassed() or testFailed()
* <p>
* testsFinished()
* <p>
*/
public abstract class AbstractTestRunner {
/**
* A TestCase and elapsed time tuple. This is used to store the elapsed time information for slow
* tests.
*/
public static class TestTime implements Comparable<TestTime> {
private TestCase test;
private long elapsedTime;
public TestTime(TestCase test, long elapsedTime) {
this.test = test;
this.elapsedTime = elapsedTime;
}
@Override
public int compareTo(TestTime other) {
if (other.elapsedTime == elapsedTime) {
return 0;
} else if (other.elapsedTime > elapsedTime) {
return 1;
} else {
return -1;
}
}
@Override
public String toString() {
return formatDouble(elapsedTime / 1000.0) + " sec, " + getTestId(test);
}
}
public static String getTestId(TestCase test) {
return test.getClass().getName() + "." + test.getName();
}
private static String formatDouble(double d) {
NumberFormat nf = new DecimalFormat();
nf.setMaximumFractionDigits(2);
nf.setMinimumFractionDigits(2);
return nf.format(d);
}
private Test mainTest;
private static final long ONE_SEC_MILLIS = 1000;
protected static <T extends Annotation> boolean hasAnnotation(TestCase test,
Class<? extends T> annotationClass) {
try {
Method m = test.getClass().getMethod(test.getName());
T a = m.getAnnotation(annotationClass);
return a != null;
} catch (Throwable e) {
}
return false;
}
private static boolean hasAnnotationIgnored(TestCase test) {
return hasAnnotation(test, Ignore.class);
}
private boolean currentTestLoggedError;
public AbstractTestRunner(Test test) {
this.mainTest = test;
}
/**
* Run the tests. Return true if they succeed; false if they fail.
*
* @return true if the tests succeed and false if they fail
*/
public final boolean runTests() {
List<TestCase> tests = filterTests(flattenTests(mainTest));
testsStarted(tests);
long totalStartTime = System.currentTimeMillis();
List<TestResult> failures = new ArrayList<TestResult>();
List<TestTime> slowTests = new ArrayList<TestTime>();
for (TestCase test : tests) {
currentTestLoggedError = false;
testStarted(test);
long startTime = System.nanoTime();
TestResult result = test.run();
long elapsedTimeMS = (System.nanoTime() - startTime) / (1000 * 1000);
if (currentTestLoggedError && result.wasSuccessful()
&& !hasAnnotation(test, IgnoreLoggedErrors.class)) {
result.addFailure(test, new AssertionFailedError("IStatus.ERROR written to eclipse log"));
}
if (isTestFailureExpected(test) && result.wasSuccessful()) {
result.addFailure(test, new AssertionFailedError("test passed but was expected to fail"));
} else if (isTestFailureExpected(test) && !result.wasSuccessful()) {
// This test failed and was expected to fail. We clear out the test results.
result = new TestResult();
}
if (result.wasSuccessful()) {
testPassed(test);
if (elapsedTimeMS >= ONE_SEC_MILLIS) {
slowTests.add(new TestTime(test, elapsedTimeMS));
}
} else {
testFailed(test, result);
failures.add(result);
}
}
long totalTestTime = System.currentTimeMillis() - totalStartTime;
testsFinished(tests, failures, slowTests, totalTestTime);
return failures.size() == 0;
}
public final void setStatusFile(String filePath) {
// TODO(devoncarew): read and use the status file
}
protected void markCurrentTestLoggedError() {
this.currentTestLoggedError = true;
}
protected boolean filterTest(TestCase test) {
if (hasAnnotationIgnored(test)) {
return true;
}
return false;
}
protected boolean isTestFailureExpected(TestCase test) {
String testId = getTestId(test);
return testId.indexOf(".fail_") != -1;
}
protected abstract void testFailed(TestCase test, TestResult result);
protected abstract void testPassed(TestCase test);
protected abstract void testsFinished(List<TestCase> allTests, List<TestResult> failures,
List<TestTime> slowTests, long totalTime);
protected abstract void testsStarted(List<TestCase> tests);
protected abstract void testStarted(TestCase test);
private List<TestCase> filterTests(List<TestCase> tests) {
List<TestCase> copy = new ArrayList<TestCase>();
for (TestCase test : tests) {
if (!filterTest(test)) {
copy.add(test);
}
}
return copy;
}
private void flatten(Test test, List<TestCase> tests) {
if (test instanceof TestCase) {
tests.add((TestCase) test);
} else if (test instanceof TestSuite) {
TestSuite suite = (TestSuite) test;
for (Test child : Collections.list(suite.tests())) {
flatten(child, tests);
}
} else {
System.out.println("Test instance not recognized: " + test);
}
}
private List<TestCase> flattenTests(Test mainTest) {
List<TestCase> tests = new ArrayList<TestCase>();
flatten(mainTest, tests);
return tests;
}
}