package org.junit.experimental.max; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import junit.framework.TestSuite; import org.junit.internal.requests.SortingRequest; import org.junit.internal.runners.ErrorReportingRunner; import org.junit.internal.runners.JUnit38ClassRunner; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Request; import org.junit.runner.Result; import org.junit.runner.Runner; import org.junit.runners.Suite; import org.junit.runners.model.InitializationError; /** * A replacement for JUnitCore, which keeps track of runtime and failure history, and reorders tests * to maximize the chances that a failing test occurs early in the test run. * * The rules for sorting are: *
    *
  1. Never-run tests first, in arbitrary order *
  2. Group remaining tests by the date at which they most recently failed. *
  3. Sort groups such that the most recent failure date is first, and never-failing tests are at the end. *
  4. Within a group, run the fastest tests first. *
*/ public class MaxCore { private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX= "malformed JUnit 3 test class: "; /** * Create a new MaxCore from a serialized file stored at storedResults * @deprecated use storedLocally() */ @Deprecated public static MaxCore forFolder(String folderName) { return storedLocally(new File(folderName)); } /** * Create a new MaxCore from a serialized file stored at storedResults */ public static MaxCore storedLocally(File storedResults) { return new MaxCore(storedResults); } private final MaxHistory fHistory; private MaxCore(File storedResults) { fHistory = MaxHistory.forFolder(storedResults); } /** * Run all the tests in class. * @return a {@link Result} describing the details of the test run and the failed tests. */ public Result run(Class testClass) { return run(Request.aClass(testClass)); } /** * Run all the tests contained in request. * @param request the request describing tests * @return a {@link Result} describing the details of the test run and the failed tests. */ public Result run(Request request) { return run(request, new JUnitCore()); } /** * Run all the tests contained in request. * * This variant should be used if {@code core} has attached listeners that this * run should notify. * * @param request the request describing tests * @param core a JUnitCore to delegate to. * @return a {@link Result} describing the details of the test run and the failed tests. */ public Result run(Request request, JUnitCore core) { core.addListener(fHistory.listener()); return core.run(sortRequest(request).getRunner()); } /** * @param request * @return a new Request, which contains all of the same tests, but in a new order. */ public Request sortRequest(Request request) { if (request instanceof SortingRequest) // We'll pay big karma points for this return request; List leaves= findLeaves(request); Collections.sort(leaves, fHistory.testComparator()); return constructLeafRequest(leaves); } private Request constructLeafRequest(List leaves) { final List runners = new ArrayList(); for (Description each : leaves) runners.add(buildRunner(each)); return new Request() { @Override public Runner getRunner() { try { return new Suite((Class)null, runners) {}; } catch (InitializationError e) { return new ErrorReportingRunner(null, e); } } }; } private Runner buildRunner(Description each) { if (each.toString().equals("TestSuite with 0 tests")) return Suite.emptySuite(); if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX)) // This is cheating, because it runs the whole class // to get the warning for this method, but we can't do better, // because JUnit 3.8's // thrown away which method the warning is for. return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each))); Class type= each.getTestClass(); if (type == null) throw new RuntimeException("Can't build a runner from description [" + each + "]"); String methodName= each.getMethodName(); if (methodName == null) return Request.aClass(type).getRunner(); return Request.method(type, methodName).getRunner(); } private Class getMalformedTestClass(Description each) { try { return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, "")); } catch (ClassNotFoundException e) { return null; } } /** * @param request a request to run * @return a list of method-level tests to run, sorted in the order * specified in the class comment. */ public List sortedLeavesForTest(Request request) { return findLeaves(sortRequest(request)); } private List findLeaves(Request request) { List results= new ArrayList(); findLeaves(null, request.getRunner().getDescription(), results); return results; } private void findLeaves(Description parent, Description description, List results) { if (description.getChildren().isEmpty()) if (description.toString().equals("warning(junit.framework.TestSuite$1)")) results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent)); else results.add(description); else for (Description each : description.getChildren()) findLeaves(description, each, results); } }