summaryrefslogtreecommitdiffstats
path: root/junit4/src/main/java/org/junit/experimental/max/MaxCore.java
blob: a2a34a97ef92914582316d64830871f7123b0501 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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 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 <code>request</code>.
	 * @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 <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 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<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);
	}
}