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    }