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 }