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