Open Source Repository

Home /junit/junit-4.10 | Repository Home


org/junit/experimental/max/MaxCore.java
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:
 <ol>
 <li> Never-run tests first, in arbitrary order
 <li> Group remaining tests by the date at which they most recently failed.
 <li> Sort groups such that the most recent failure date is first, and never-failing tests are at the end.
 <li> Within a group, run the fastest tests first. 
 </ol>
 */
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 <code>class</code>.
   @return {@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 <code>request</code>.
   @param request the request describing tests
   @return {@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 <code>request</code>.
   
   * 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 {@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<Description> leaves= findLeaves(request);
    Collections.sort(leaves, fHistory.testComparator());
    return constructLeafRequest(leaves);
  }

  private Request constructLeafRequest(List<Description> leaves) {
    final List<Runner> runners = new ArrayList<Runner>();
    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<Description> sortedLeavesForTest(Request request) {
    return findLeaves(sortRequest(request));
  }
  
  private List<Description> findLeaves(Request request) {
    List<Description> results= new ArrayList<Description>();
    findLeaves(null, request.getRunner().getDescription(), results);
    return results;
  }
  
  private void findLeaves(Description parent, Description description, List<Description> 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);
  }
}