001 package org.junit.experimental.max;
002
003 import java.io.File;
004 import java.io.FileInputStream;
005 import java.io.FileOutputStream;
006 import java.io.IOException;
007 import java.io.ObjectInputStream;
008 import java.io.ObjectOutputStream;
009 import java.io.Serializable;
010 import java.util.Comparator;
011 import java.util.HashMap;
012 import java.util.Map;
013
014 import org.junit.runner.Description;
015 import org.junit.runner.Result;
016 import org.junit.runner.notification.Failure;
017 import org.junit.runner.notification.RunListener;
018
019 /**
020 * Stores a subset of the history of each test:
021 * <ul>
022 * <li>Last failure timestamp
023 * <li>Duration of last execution
024 * </ul>
025 */
026 public class MaxHistory implements Serializable {
027 private static final long serialVersionUID = 1L;
028
029 /**
030 * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
031 * will be saved to {@code file}.
032 */
033 public static MaxHistory forFolder(File file) {
034 if (file.exists()) {
035 try {
036 return readHistory(file);
037 } catch (CouldNotReadCoreException e) {
038 e.printStackTrace();
039 file.delete();
040 }
041 }
042 return new MaxHistory(file);
043 }
044
045 private static MaxHistory readHistory(File storedResults)
046 throws CouldNotReadCoreException {
047 try {
048 FileInputStream file = new FileInputStream(storedResults);
049 try {
050 ObjectInputStream stream = new ObjectInputStream(file);
051 try {
052 return (MaxHistory) stream.readObject();
053 } finally {
054 stream.close();
055 }
056 } finally {
057 file.close();
058 }
059 } catch (Exception e) {
060 throw new CouldNotReadCoreException(e);
061 }
062 }
063
064 /*
065 * We have to use the f prefix until the next major release to ensure
066 * serialization compatibility.
067 * See https://github.com/junit-team/junit/issues/976
068 */
069 private final Map<String, Long> fDurations = new HashMap<String, Long>();
070 private final Map<String, Long> fFailureTimestamps = new HashMap<String, Long>();
071 private final File fHistoryStore;
072
073 private MaxHistory(File storedResults) {
074 fHistoryStore = storedResults;
075 }
076
077 private void save() throws IOException {
078 ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(
079 fHistoryStore));
080 stream.writeObject(this);
081 stream.close();
082 }
083
084 Long getFailureTimestamp(Description key) {
085 return fFailureTimestamps.get(key.toString());
086 }
087
088 void putTestFailureTimestamp(Description key, long end) {
089 fFailureTimestamps.put(key.toString(), end);
090 }
091
092 boolean isNewTest(Description key) {
093 return !fDurations.containsKey(key.toString());
094 }
095
096 Long getTestDuration(Description key) {
097 return fDurations.get(key.toString());
098 }
099
100 void putTestDuration(Description description, long duration) {
101 fDurations.put(description.toString(), duration);
102 }
103
104 private final class RememberingListener extends RunListener {
105 private long overallStart = System.currentTimeMillis();
106
107 private Map<Description, Long> starts = new HashMap<Description, Long>();
108
109 @Override
110 public void testStarted(Description description) throws Exception {
111 starts.put(description, System.nanoTime()); // Get most accurate
112 // possible time
113 }
114
115 @Override
116 public void testFinished(Description description) throws Exception {
117 long end = System.nanoTime();
118 long start = starts.get(description);
119 putTestDuration(description, end - start);
120 }
121
122 @Override
123 public void testFailure(Failure failure) throws Exception {
124 putTestFailureTimestamp(failure.getDescription(), overallStart);
125 }
126
127 @Override
128 public void testRunFinished(Result result) throws Exception {
129 save();
130 }
131 }
132
133 private class TestComparator implements Comparator<Description> {
134 public int compare(Description o1, Description o2) {
135 // Always prefer new tests
136 if (isNewTest(o1)) {
137 return -1;
138 }
139 if (isNewTest(o2)) {
140 return 1;
141 }
142 // Then most recently failed first
143 int result = getFailure(o2).compareTo(getFailure(o1));
144 return result != 0 ? result
145 // Then shorter tests first
146 : getTestDuration(o1).compareTo(getTestDuration(o2));
147 }
148
149 private Long getFailure(Description key) {
150 Long result = getFailureTimestamp(key);
151 if (result == null) {
152 return 0L; // 0 = "never failed (that I know about)"
153 }
154 return result;
155 }
156 }
157
158 /**
159 * @return a listener that will update this history based on the test
160 * results reported.
161 */
162 public RunListener listener() {
163 return new RememberingListener();
164 }
165
166 /**
167 * @return a comparator that ranks tests based on the JUnit Max sorting
168 * rules, as described in the {@link MaxCore} class comment.
169 */
170 public Comparator<Description> testComparator() {
171 return new TestComparator();
172 }
173 }