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