1package org.junit.experimental.max; 2 3import java.io.File; 4import java.util.ArrayList; 5import java.util.Collections; 6import java.util.List; 7 8import junit.framework.TestSuite; 9 10import org.junit.internal.requests.SortingRequest; 11import org.junit.internal.runners.ErrorReportingRunner; 12import org.junit.internal.runners.JUnit38ClassRunner; 13import org.junit.runner.Description; 14import org.junit.runner.JUnitCore; 15import org.junit.runner.Request; 16import org.junit.runner.Result; 17import org.junit.runner.Runner; 18import org.junit.runners.Suite; 19import 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 */ 33public 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