Home | History | Annotate | Download | only in max
      1 package org.junit.experimental.max;
      2 
      3 import java.io.File;
      4 import java.util.ArrayList;
      5 import java.util.Collections;
      6 import java.util.List;
      7 
      8 import junit.framework.TestSuite;
      9 
     10 import org.junit.internal.requests.SortingRequest;
     11 import org.junit.internal.runners.ErrorReportingRunner;
     12 import org.junit.internal.runners.JUnit38ClassRunner;
     13 import org.junit.runner.Description;
     14 import org.junit.runner.JUnitCore;
     15 import org.junit.runner.Request;
     16 import org.junit.runner.Result;
     17 import org.junit.runner.Runner;
     18 import org.junit.runners.Suite;
     19 import org.junit.runners.model.InitializationError;
     20 
     21 /**
     22  * A replacement for JUnitCore, which keeps track of runtime and failure history, and reorders tests
     23  * to maximize the chances that a failing test occurs early in the test run.
     24  *
     25  * The rules for sorting are:
     26  * <ol>
     27  * <li> Never-run tests first, in arbitrary order
     28  * <li> Group remaining tests by the date at which they most recently failed.
     29  * <li> Sort groups such that the most recent failure date is first, and never-failing tests are at the end.
     30  * <li> Within a group, run the fastest tests first.
     31  * </ol>
     32  */
     33 public class MaxCore {
     34 	private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX= "malformed JUnit 3 test class: ";
     35 
     36 	/**
     37 	 * Create a new MaxCore from a serialized file stored at storedResults
     38 	 * @deprecated use storedLocally()
     39 	 */
     40 	@Deprecated
     41 	public static MaxCore forFolder(String folderName) {
     42 		return storedLocally(new File(folderName));
     43 	}
     44 
     45 	/**
     46 	 * Create a new MaxCore from a serialized file stored at storedResults
     47 	 */
     48 	public static MaxCore storedLocally(File storedResults) {
     49 		return new MaxCore(storedResults);
     50 	}
     51 
     52 	private final MaxHistory fHistory;
     53 
     54 	private MaxCore(File storedResults) {
     55 		fHistory = MaxHistory.forFolder(storedResults);
     56 	}
     57 
     58 	/**
     59 	 * Run all the tests in <code>class</code>.
     60 	 * @return a {@link Result} describing the details of the test run and the failed tests.
     61 	 */
     62 	public Result run(Class<?> testClass) {
     63 		return run(Request.aClass(testClass));
     64 	}
     65 
     66 	/**
     67 	 * Run all the tests contained in <code>request</code>.
     68 	 * @param request the request describing tests
     69 	 * @return a {@link Result} describing the details of the test run and the failed tests.
     70 	 */
     71 	public Result run(Request request) {
     72 		return run(request, new JUnitCore());
     73 	}
     74 
     75 	/**
     76 	 * Run all the tests contained in <code>request</code>.
     77 	 *
     78 	 * This variant should be used if {@code core} has attached listeners that this
     79 	 * run should notify.
     80 	 *
     81 	 * @param request the request describing tests
     82 	 * @param core a JUnitCore to delegate to.
     83 	 * @return a {@link Result} describing the details of the test run and the failed tests.
     84 	 */
     85 	public Result run(Request request, JUnitCore core) {
     86 		core.addListener(fHistory.listener());
     87 		return core.run(sortRequest(request).getRunner());
     88 	}
     89 
     90 	/**
     91 	 * @param request
     92 	 * @return a new Request, which contains all of the same tests, but in a new order.
     93 	 */
     94 	public Request sortRequest(Request request) {
     95 		if (request instanceof SortingRequest) // We'll pay big karma points for this
     96 			return request;
     97 		List<Description> leaves= findLeaves(request);
     98 		Collections.sort(leaves, fHistory.testComparator());
     99 		return constructLeafRequest(leaves);
    100 	}
    101 
    102 	private Request constructLeafRequest(List<Description> leaves) {
    103 		final List<Runner> runners = new ArrayList<Runner>();
    104 		for (Description each : leaves)
    105 			runners.add(buildRunner(each));
    106 		return new Request() {
    107 			@Override
    108 			public Runner getRunner() {
    109 				try {
    110 					return new Suite((Class<?>)null, runners) {};
    111 				} catch (InitializationError e) {
    112 					return new ErrorReportingRunner(null, e);
    113 				}
    114 			}
    115 		};
    116 	}
    117 
    118 	private Runner buildRunner(Description each) {
    119 		if (each.toString().equals("TestSuite with 0 tests"))
    120 			return Suite.emptySuite();
    121 		if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX))
    122 			// This is cheating, because it runs the whole class
    123 			// to get the warning for this method, but we can't do better,
    124 			// because JUnit 3.8's
    125 			// thrown away which method the warning is for.
    126 			return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each)));
    127 		Class<?> type= each.getTestClass();
    128 		if (type == null)
    129 			throw new RuntimeException("Can't build a runner from description [" + each + "]");
    130 		String methodName= each.getMethodName();
    131 		if (methodName == null)
    132 			return Request.aClass(type).getRunner();
    133 		return Request.method(type, methodName).getRunner();
    134 	}
    135 
    136 	private Class<?> getMalformedTestClass(Description each) {
    137 		try {
    138 			return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, ""));
    139 		} catch (ClassNotFoundException e) {
    140 			return null;
    141 		}
    142 	}
    143 
    144 	/**
    145 	 * @param request a request to run
    146 	 * @return a list of method-level tests to run, sorted in the order
    147 	 * specified in the class comment.
    148 	 */
    149 	public List<Description> sortedLeavesForTest(Request request) {
    150 		return findLeaves(sortRequest(request));
    151 	}
    152 
    153 	private List<Description> findLeaves(Request request) {
    154 		List<Description> results= new ArrayList<Description>();
    155 		findLeaves(null, request.getRunner().getDescription(), results);
    156 		return results;
    157 	}
    158 
    159 	private void findLeaves(Description parent, Description description, List<Description> results) {
    160 		if (description.getChildren().isEmpty())
    161 			if (description.toString().equals("warning(junit.framework.TestSuite$1)"))
    162 				results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent));
    163 			else
    164 				results.add(description);
    165 		else
    166 			for (Description each : description.getChildren())
    167 				findLeaves(description, each, results);
    168 	}
    169 }
    170 
    171