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/junit4/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 = null;
079 try {
080 stream = new ObjectOutputStream(new FileOutputStream(fHistoryStore));
081 stream.writeObject(this);
082 } finally {
083 if (stream != null) {
084 stream.close();
085 }
086 }
087 }
088
089 Long getFailureTimestamp(Description key) {
090 return fFailureTimestamps.get(key.toString());
091 }
092
093 void putTestFailureTimestamp(Description key, long end) {
094 fFailureTimestamps.put(key.toString(), end);
095 }
096
097 boolean isNewTest(Description key) {
098 return !fDurations.containsKey(key.toString());
099 }
100
101 Long getTestDuration(Description key) {
102 return fDurations.get(key.toString());
103 }
104
105 void putTestDuration(Description description, long duration) {
106 fDurations.put(description.toString(), duration);
107 }
108
109 private final class RememberingListener extends RunListener {
110 private long overallStart = System.currentTimeMillis();
111
112 private Map<Description, Long> starts = new HashMap<Description, Long>();
113
114 @Override
115 public void testStarted(Description description) throws Exception {
116 starts.put(description, System.nanoTime()); // Get most accurate
117 // possible time
118 }
119
120 @Override
121 public void testFinished(Description description) throws Exception {
122 long end = System.nanoTime();
123 long start = starts.get(description);
124 putTestDuration(description, end - start);
125 }
126
127 @Override
128 public void testFailure(Failure failure) throws Exception {
129 putTestFailureTimestamp(failure.getDescription(), overallStart);
130 }
131
132 @Override
133 public void testRunFinished(Result result) throws Exception {
134 save();
135 }
136 }
137
138 private class TestComparator implements Comparator<Description> {
139 public int compare(Description o1, Description o2) {
140 // Always prefer new tests
141 if (isNewTest(o1)) {
142 return -1;
143 }
144 if (isNewTest(o2)) {
145 return 1;
146 }
147 // Then most recently failed first
148 int result = getFailure(o2).compareTo(getFailure(o1));
149 return result != 0 ? result
150 // Then shorter tests first
151 : getTestDuration(o1).compareTo(getTestDuration(o2));
152 }
153
154 private Long getFailure(Description key) {
155 Long result = getFailureTimestamp(key);
156 if (result == null) {
157 return 0L; // 0 = "never failed (that I know about)"
158 }
159 return result;
160 }
161 }
162
163 /**
164 * @return a listener that will update this history based on the test
165 * results reported.
166 */
167 public RunListener listener() {
168 return new RememberingListener();
169 }
170
171 /**
172 * @return a comparator that ranks tests based on the JUnit Max sorting
173 * rules, as described in the {@link MaxCore} class comment.
174 */
175 public Comparator<Description> testComparator() {
176 return new TestComparator();
177 }
178 }