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 = 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 }