    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    }