Home | History | Annotate | Download | only in max
      1 package org.junit.experimental.max;
      2 
      3 import java.io.File;
      4 import java.io.FileInputStream;
      5 import java.io.FileOutputStream;
      6 import java.io.IOException;
      7 import java.io.ObjectInputStream;
      8 import java.io.ObjectOutputStream;
      9 import java.io.Serializable;
     10 import java.util.Comparator;
     11 import java.util.HashMap;
     12 import java.util.Map;
     13 
     14 import org.junit.runner.Description;
     15 import org.junit.runner.Result;
     16 import org.junit.runner.notification.Failure;
     17 import org.junit.runner.notification.RunListener;
     18 
     19 /**
     20  * Stores a subset of the history of each test:
     21  * <ul>
     22  * <li>Last failure timestamp
     23  * <li>Duration of last execution
     24  * </ul>
     25  */
     26 public class MaxHistory implements Serializable {
     27     private static final long serialVersionUID = 1L;
     28 
     29     /**
     30      * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
     31      * will be saved to {@code file}.
     32      */
     33     public static MaxHistory forFolder(File file) {
     34         if (file.exists()) {
     35             try {
     36                 return readHistory(file);
     37             } catch (CouldNotReadCoreException e) {
     38                 e.printStackTrace();
     39                 file.delete();
     40             }
     41         }
     42         return new MaxHistory(file);
     43     }
     44 
     45     private static MaxHistory readHistory(File storedResults)
     46             throws CouldNotReadCoreException {
     47         try {
     48             FileInputStream file = new FileInputStream(storedResults);
     49             try {
     50                 ObjectInputStream stream = new ObjectInputStream(file);
     51                 try {
     52                     return (MaxHistory) stream.readObject();
     53                 } finally {
     54                     stream.close();
     55                 }
     56             } finally {
     57                 file.close();
     58             }
     59         } catch (Exception e) {
     60             throw new CouldNotReadCoreException(e);
     61         }
     62     }
     63 
     64     /*
     65      * We have to use the f prefix until the next major release to ensure
     66      * serialization compatibility.
     67      * See https://github.com/junit-team/junit/issues/976
     68      */
     69     private final Map<String, Long> fDurations = new HashMap<String, Long>();
     70     private final Map<String, Long> fFailureTimestamps = new HashMap<String, Long>();
     71     private final File fHistoryStore;
     72 
     73     private MaxHistory(File storedResults) {
     74         fHistoryStore = storedResults;
     75     }
     76 
     77     private void save() throws IOException {
     78         ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(
     79                 fHistoryStore));
     80         stream.writeObject(this);
     81         stream.close();
     82     }
     83 
     84     Long getFailureTimestamp(Description key) {
     85         return fFailureTimestamps.get(key.toString());
     86     }
     87 
     88     void putTestFailureTimestamp(Description key, long end) {
     89         fFailureTimestamps.put(key.toString(), end);
     90     }
     91 
     92     boolean isNewTest(Description key) {
     93         return !fDurations.containsKey(key.toString());
     94     }
     95 
     96     Long getTestDuration(Description key) {
     97         return fDurations.get(key.toString());
     98     }
     99 
    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 }
    174