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    }