1package org.junit.experimental.max;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileOutputStream;
6import java.io.IOException;
7import java.io.ObjectInputStream;
8import java.io.ObjectOutputStream;
9import java.io.Serializable;
10import java.util.Comparator;
11import java.util.HashMap;
12import java.util.Map;
13
14import org.junit.runner.Description;
15import org.junit.runner.Result;
16import org.junit.runner.notification.Failure;
17import org.junit.runner.notification.RunListener;
18
19/**
20 * Stores a subset of the history of each test:
21 * <ul>
22 * <li>Last failure timestamp
23 * <li>Duration of last execution
24 * </ul>
25 */
26public class MaxHistory implements Serializable {
27	private static final long serialVersionUID= 1L;
28
29	/**
30	 * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
31	 * will be saved to {@code file}.
32	 */
33	public static MaxHistory forFolder(File file) {
34		if (file.exists())
35			try {
36				return readHistory(file);
37			} catch (CouldNotReadCoreException e) {
38				e.printStackTrace();
39				file.delete();
40			}
41		return new MaxHistory(file);
42	}
43
44	private static MaxHistory readHistory(File storedResults)
45			throws CouldNotReadCoreException {
46		try {
47			FileInputStream file= new FileInputStream(storedResults);
48			try {
49				ObjectInputStream stream= new ObjectInputStream(file);
50				try {
51					return (MaxHistory) stream.readObject();
52				} finally {
53					stream.close();
54				}
55			} finally {
56				file.close();
57			}
58		} catch (Exception e) {
59			throw new CouldNotReadCoreException(e);
60		}
61	}
62
63	private final Map<String, Long> fDurations= new HashMap<String, Long>();
64
65	private final Map<String, Long> fFailureTimestamps= new HashMap<String, Long>();
66
67	private final File fHistoryStore;
68
69	private MaxHistory(File storedResults) {
70		fHistoryStore= storedResults;
71	}
72
73	private void save() throws IOException {
74		ObjectOutputStream stream= new ObjectOutputStream(new FileOutputStream(
75				fHistoryStore));
76		stream.writeObject(this);
77		stream.close();
78	}
79
80	Long getFailureTimestamp(Description key) {
81		return fFailureTimestamps.get(key.toString());
82	}
83
84	void putTestFailureTimestamp(Description key, long end) {
85		fFailureTimestamps.put(key.toString(), end);
86	}
87
88	boolean isNewTest(Description key) {
89		return !fDurations.containsKey(key.toString());
90	}
91
92	Long getTestDuration(Description key) {
93		return fDurations.get(key.toString());
94	}
95
96	void putTestDuration(Description description, long duration) {
97		fDurations.put(description.toString(), duration);
98	}
99
100	private final class RememberingListener extends RunListener {
101		private long overallStart= System.currentTimeMillis();
102
103		private Map<Description, Long> starts= new HashMap<Description, Long>();
104
105		@Override
106		public void testStarted(Description description) throws Exception {
107			starts.put(description, System.nanoTime()); // Get most accurate
108			// possible time
109		}
110
111		@Override
112		public void testFinished(Description description) throws Exception {
113			long end= System.nanoTime();
114			long start= starts.get(description);
115			putTestDuration(description, end - start);
116		}
117
118		@Override
119		public void testFailure(Failure failure) throws Exception {
120			putTestFailureTimestamp(failure.getDescription(), overallStart);
121		}
122
123		@Override
124		public void testRunFinished(Result result) throws Exception {
125			save();
126		}
127	}
128
129	private class TestComparator implements Comparator<Description> {
130		public int compare(Description o1, Description o2) {
131			// Always prefer new tests
132			if (isNewTest(o1))
133				return -1;
134			if (isNewTest(o2))
135				return 1;
136			// Then most recently failed first
137			int result= getFailure(o2).compareTo(getFailure(o1));
138			return result != 0 ? result
139			// Then shorter tests first
140					: getTestDuration(o1).compareTo(getTestDuration(o2));
141		}
142
143		private Long getFailure(Description key) {
144			Long result= getFailureTimestamp(key);
145			if (result == null)
146				return 0L; // 0 = "never failed (that I know about)"
147			return result;
148		}
149	}
150
151	/**
152	 * @return a listener that will update this history based on the test
153	 *         results reported.
154	 */
155	public RunListener listener() {
156		return new RememberingListener();
157	}
158
159	/**
160	 * @return a comparator that ranks tests based on the JUnit Max sorting
161	 *         rules, as described in the {@link MaxCore} class comment.
162	 */
163	public Comparator<Description> testComparator() {
164		return new TestComparator();
165	}
166}
167