aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/experimental/max/MaxHistory.java
blob: ab7443f38535d0acd8c5f4c6eb8cd41dabd4263d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package org.junit.experimental.max;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

/**
 * Stores a subset of the history of each test:
 * <ul>
 * <li>Last failure timestamp
 * <li>Duration of last execution
 * </ul>
 */
public class MaxHistory implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
     * will be saved to {@code file}.
     */
    public static MaxHistory forFolder(File file) {
        if (file.exists()) {
            try {
                return readHistory(file);
            } catch (CouldNotReadCoreException e) {
                e.printStackTrace();
                file.delete();
            }
        }
        return new MaxHistory(file);
    }

    private static MaxHistory readHistory(File storedResults)
            throws CouldNotReadCoreException {
        try {
            FileInputStream file = new FileInputStream(storedResults);
            try {
                ObjectInputStream stream = new ObjectInputStream(file);
                try {
                    return (MaxHistory) stream.readObject();
                } finally {
                    stream.close();
                }
            } finally {
                file.close();
            }
        } catch (Exception e) {
            throw new CouldNotReadCoreException(e);
        }
    }

    /*
     * We have to use the f prefix until the next major release to ensure
     * serialization compatibility. 
     * See https://github.com/junit-team/junit4/issues/976
     */
    private final Map<String, Long> fDurations = new HashMap<String, Long>();
    private final Map<String, Long> fFailureTimestamps = new HashMap<String, Long>();
    private final File fHistoryStore;

    private MaxHistory(File storedResults) {
        fHistoryStore = storedResults;
    }

    private void save() throws IOException {
        ObjectOutputStream stream = null;
        try {
            stream = new ObjectOutputStream(new FileOutputStream(fHistoryStore));
            stream.writeObject(this);
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    Long getFailureTimestamp(Description key) {
        return fFailureTimestamps.get(key.toString());
    }

    void putTestFailureTimestamp(Description key, long end) {
        fFailureTimestamps.put(key.toString(), end);
    }

    boolean isNewTest(Description key) {
        return !fDurations.containsKey(key.toString());
    }

    Long getTestDuration(Description key) {
        return fDurations.get(key.toString());
    }

    void putTestDuration(Description description, long duration) {
        fDurations.put(description.toString(), duration);
    }

    private final class RememberingListener extends RunListener {
        private long overallStart = System.currentTimeMillis();

        private Map<Description, Long> starts = new HashMap<Description, Long>();

        @Override
        public void testStarted(Description description) throws Exception {
            starts.put(description, System.nanoTime()); // Get most accurate
            // possible time
        }

        @Override
        public void testFinished(Description description) throws Exception {
            long end = System.nanoTime();
            long start = starts.get(description);
            putTestDuration(description, end - start);
        }

        @Override
        public void testFailure(Failure failure) throws Exception {
            putTestFailureTimestamp(failure.getDescription(), overallStart);
        }

        @Override
        public void testRunFinished(Result result) throws Exception {
            save();
        }
    }

    private class TestComparator implements Comparator<Description> {
        public int compare(Description o1, Description o2) {
            // Always prefer new tests
            if (isNewTest(o1)) {
                return -1;
            }
            if (isNewTest(o2)) {
                return 1;
            }
            // Then most recently failed first
            int result = getFailure(o2).compareTo(getFailure(o1));
            return result != 0 ? result
                    // Then shorter tests first
                    : getTestDuration(o1).compareTo(getTestDuration(o2));
        }

        private Long getFailure(Description key) {
            Long result = getFailureTimestamp(key);
            if (result == null) {
                return 0L; // 0 = "never failed (that I know about)"
            }
            return result;
        }
    }

    /**
     * @return a listener that will update this history based on the test
     *         results reported.
     */
    public RunListener listener() {
        return new RememberingListener();
    }

    /**
     * @return a comparator that ranks tests based on the JUnit Max sorting
     *         rules, as described in the {@link MaxCore} class comment.
     */
    public Comparator<Description> testComparator() {
        return new TestComparator();
    }
}