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    }