Ticket #12875: patch-bug-report-base-tests.patch

File patch-bug-report-base-tests.patch, 14.4 KB (added by michael2402, 10 years ago)

Test cases

  • new file src/org/openstreetmap/josm/tools/bugreport/ReportedException.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java b/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java
    new file mode 100644
    index 0000000..4d897af
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools.bugreport;
     3
     4import java.io.PrintWriter;
     5import java.util.ArrayList;
     6import java.util.Arrays;
     7import java.util.Collection;
     8import java.util.Collections;
     9import java.util.IdentityHashMap;
     10import java.util.LinkedList;
     11import java.util.Map;
     12import java.util.Map.Entry;
     13import java.util.Set;
     14
     15import org.openstreetmap.josm.Main;
     16
     17/**
     18 * This is a special exception that cannot be directly thrown.
     19 * <p>
     20 * It is used to capture more information about an exception that was already thrown.
     21 *
     22 * @author Michael Zangl
     23 * @see BugReport
     24 * @since xxx
     25 */
     26public class ReportedException extends RuntimeException {
     27    private static final int MAX_COLLECTION_ENTRIES = 30;
     28    /**
     29     *
     30     */
     31    private static final long serialVersionUID = 737333873766201033L;
     32    /**
     33     * We capture all stack traces on exception creation. This allows us to trace synchonization problems better. We cannot be really sure what
     34     * happened but we at least see which threads
     35     */
     36    private final transient Map<Thread, StackTraceElement[]> allStackTraces;
     37    private final LinkedList<Section> sections = new LinkedList<>();
     38    private final transient Thread caughtOnThread;
     39    private final Throwable exception;
     40    private String methodWarningFrom;
     41
     42    ReportedException(Throwable exception) {
     43        this(exception, Thread.currentThread());
     44    }
     45
     46    ReportedException(Throwable exception, Thread caughtOnThread) {
     47        super(exception);
     48        this.exception = exception;
     49
     50        allStackTraces = Thread.getAllStackTraces();
     51        this.caughtOnThread = caughtOnThread;
     52    }
     53
     54    /**
     55     * Displays a warning for this exception. The program can then continue normally. Does not block.
     56     */
     57    public void warn() {
     58        methodWarningFrom = BugReport.getCallingMethod(2);
     59        // TODO: Open the dialog.
     60    }
     61
     62    /**
     63     * Starts a new debug data section. This normally does not need to be called manually.
     64     *
     65     * @param sectionName
     66     *            The section name.
     67     */
     68    public void startSection(String sectionName) {
     69        sections.add(new Section(sectionName));
     70    }
     71
     72    /**
     73     * Prints the captured data of this report to a {@link PrintWriter}.
     74     *
     75     * @param out
     76     *            The writer to print to.
     77     */
     78    public void printReportDataTo(PrintWriter out) {
     79        out.println("=== REPORTED CRASH DATA ===");
     80        for (Section s : sections) {
     81            s.printSection(out);
     82            out.println();
     83        }
     84
     85        if (methodWarningFrom != null) {
     86            out.println("Warning issued by: " + methodWarningFrom);
     87            out.println();
     88        }
     89    }
     90
     91
     92    /**
     93     * Prints the stack trace of this report to a {@link PrintWriter}.
     94     *
     95     * @param out
     96     *            The writer to print to.
     97     */
     98    public void printReportStackTo(PrintWriter out) {
     99        out.println("=== STACK TRACE ===");
     100        out.println(niceThreadName(caughtOnThread));
     101        getCause().printStackTrace(out);
     102        out.println();
     103    }
     104
     105
     106    /**
     107     * Prints the stack traces for other threads of this report to a {@link PrintWriter}.
     108     *
     109     * @param out
     110     *            The writer to print to.
     111     */
     112    public void printReportThreadsTo(PrintWriter out) {
     113        out.println("=== RUNNING THREADS ===");
     114        for (Entry<Thread, StackTraceElement[]> thread : allStackTraces.entrySet()) {
     115            out.println(niceThreadName(thread.getKey()));
     116            if (caughtOnThread.equals(thread.getKey())) {
     117                out.println("Stacktrace see above.");
     118            } else {
     119                for (StackTraceElement e : thread.getValue()) {
     120                    out.println(e);
     121                }
     122            }
     123            out.println();
     124        }
     125    }
     126
     127    private static String niceThreadName(Thread thread) {
     128        String name = "Thread: " + thread.getName() + " (" + thread.getId() + ")";
     129        ThreadGroup threadGroup = thread.getThreadGroup();
     130        if (threadGroup != null) {
     131            name += " of " + threadGroup.getName();
     132        }
     133        return name;
     134    }
     135
     136    /**
     137     * Checks if this exception is considered the same as an other exception. This is the case if both have the same cause and message.
     138     *
     139     * @param e
     140     *            The exception to check against.
     141     * @return <code>true</code> if they are considered the same.
     142     */
     143    public boolean isSame(ReportedException e) {
     144        if (!getMessage().equals(e.getMessage())) {
     145            return false;
     146        }
     147
     148        Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
     149        return hasSameStackTrace(dejaVu, this.exception, e.exception);
     150    }
     151
     152    private static boolean hasSameStackTrace(Set<Throwable> dejaVu, Throwable e1, Throwable e2) {
     153        if (dejaVu.contains(e1)) {
     154            // cycle. If it was the same until here, we assume both have that cycle.
     155            return true;
     156        }
     157        dejaVu.add(e1);
     158
     159        StackTraceElement[] t1 = e1.getStackTrace();
     160        StackTraceElement[] t2 = e2.getStackTrace();
     161
     162        if (!Arrays.equals(t1, t2)) {
     163            return false;
     164        }
     165
     166        Throwable c1 = e1.getCause();
     167        Throwable c2 = e2.getCause();
     168        if ((c1 == null) != (c2 == null)) {
     169            return false;
     170        } else if (c1 != null) {
     171            return hasSameStackTrace(dejaVu, c1, c2);
     172        } else {
     173            return true;
     174        }
     175    }
     176
     177    /**
     178     * Adds some debug values to this exception.
     179     *
     180     * @param key
     181     *            The key to add this for. Does not need to be unique but it would be nice.
     182     * @param value
     183     *            The value.
     184     * @return This exception for easy chaining.
     185     */
     186    public ReportedException put(String key, Object value) {
     187        String string;
     188        try {
     189            if (value == null) {
     190                string = "null";
     191            } else if (value instanceof Collection) {
     192                string = makeCollectionNice((Collection<?>) value);
     193            } else if (value.getClass().isArray()) {
     194                string = makeCollectionNice(Arrays.asList(value));
     195            } else {
     196                string = value.toString();
     197            }
     198        } catch (RuntimeException t) {
     199            Main.warn(t);
     200            string = "<Error calling toString()>";
     201        }
     202        sections.getLast().put(key, string);
     203        return this;
     204    }
     205
     206    private static String makeCollectionNice(Collection<?> value) {
     207        int lines = 0;
     208        StringBuilder str = new StringBuilder();
     209        for (Object e : value) {
     210            str.append("\n    - ");
     211            if (lines <= MAX_COLLECTION_ENTRIES) {
     212                str.append(e);
     213            } else {
     214                str.append("\n    ... (");
     215                str.append(value.size());
     216                str.append(" entries)");
     217                break;
     218            }
     219        }
     220        return str.toString();
     221    }
     222
     223    @Override
     224    public String toString() {
     225        StringBuilder builder = new StringBuilder();
     226        builder.append("CrashReportedException [on thread ");
     227        builder.append(caughtOnThread);
     228        builder.append("]");
     229        return builder.toString();
     230    }
     231
     232    private static class SectionEntry {
     233        private final String key;
     234        private final String value;
     235
     236        SectionEntry(String key, String value) {
     237            this.key = key;
     238            this.value = value;
     239
     240        }
     241
     242        /**
     243         * Prints this entry to the output stream in a line.
     244         * @param out The stream to print to.
     245         */
     246        public void print(PrintWriter out) {
     247            out.print(" - ");
     248            out.print(key);
     249            out.print(": ");
     250            out.println(value);
     251        }
     252    }
     253
     254    private static class Section {
     255
     256        private String sectionName;
     257        private ArrayList<SectionEntry> entries = new ArrayList<>();
     258
     259        Section(String sectionName) {
     260            this.sectionName = sectionName;
     261        }
     262
     263        /**
     264         * Add a key/value entry to this section.
     265         * @param key The key. Need not be unique.
     266         * @param value The value.
     267         */
     268        public void put(String key, String value) {
     269            entries.add(new SectionEntry(key, value));
     270        }
     271
     272        /**
     273         * Prints this section to the output stream.
     274         * @param out The stream to print to.
     275         */
     276        public void printSection(PrintWriter out) {
     277            out.println(sectionName + ":");
     278            if (entries.isEmpty()) {
     279                out.println("No data collected.");
     280            } else {
     281                for (SectionEntry e : entries) {
     282                    e.print(out);
     283                }
     284            }
     285        }
     286
     287    }
     288
     289}
  • new file test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java

    diff --git a/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java b/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java
    new file mode 100644
    index 0000000..4ac4e45
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools.bugreport;
     3
     4import static org.junit.Assert.assertEquals;
     5
     6import org.junit.Test;
     7
     8/**
     9 * Tests the bug report class.
     10 * @author Michael Zangl
     11 * @since xxx
     12 */
     13public class BugReportTest {
     14
     15    /**
     16     * Test {@link BugReport#getCallingMethod(int)}
     17     */
     18    @Test
     19    public void testGetCallingMethod() {
     20        assertEquals("BugReportTest#testGetCallingMethod", BugReport.getCallingMethod(1));
     21        assertEquals("BugReportTest#testGetCallingMethod", testGetCallingMethod2());
     22    }
     23
     24    private String testGetCallingMethod2() {
     25        return BugReport.getCallingMethod(2);
     26    }
     27
     28}
  • new file test/unit/org/openstreetmap/josm/tools/bugreport/ReportedExceptionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/tools/bugreport/ReportedExceptionTest.java b/test/unit/org/openstreetmap/josm/tools/bugreport/ReportedExceptionTest.java
    new file mode 100644
    index 0000000..4346b10
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools.bugreport;
     3
     4import static org.junit.Assert.assertEquals;
     5
     6import java.util.Arrays;
     7
     8import org.junit.Test;
     9
     10/**
     11 * Tests the {@link ReportedException} class.
     12 * @author Michael Zangl
     13 * @since xxx
     14 */
     15public class ReportedExceptionTest {
     16    private final class CauseOverwriteException extends RuntimeException {
     17        private Throwable myCause;
     18
     19        private CauseOverwriteException(String message) {
     20            super(message);
     21        }
     22
     23        @Override
     24        public synchronized Throwable getCause() {
     25            return myCause;
     26        }
     27    }
     28
     29    /**
     30     * Tests that {@link ReportedException#put(String, Object)} handles null values
     31     */
     32    @Test
     33    public void testPutDoesHandleNull() {
     34        ReportedException e = new ReportedException(new RuntimeException());
     35        e.startSection("test");
     36        Object[] a = new Object[] {
     37                new Object(), null };
     38        e.put("testObject", null);
     39        e.put("testArray", a);
     40        e.put("testList", Arrays.asList(a));
     41    }
     42
     43    /**
     44     * Tests that {@link ReportedException#put(String, Object)} handles exceptions during toString fine.
     45     */
     46    @Test
     47    public void testPutDoesNotThrow() {
     48        ReportedException e = new ReportedException(new RuntimeException());
     49        e.startSection("test");
     50        Object o = new Object() {
     51            @Override
     52            public String toString() {
     53                throw new IllegalArgumentException("");
     54            }
     55        };
     56        Object[] a = new Object[] {
     57                new Object(), o };
     58        e.put("testObject", o);
     59        e.put("testArray", a);
     60        e.put("testList", Arrays.asList(a));
     61    }
     62
     63    /**
     64     * Tests that {@link ReportedException#isSame(ReportedException)} works as expected.
     65     */
     66    @Test
     67    public void testIsSame() {
     68        // Do not break this line! All exceptions need to be created in the same line.
     69        // CHECKSTYLE.OFF: LineLength
     70        // @formatter:off
     71        ReportedException[] testExceptions = new ReportedException[] {
     72                /* 0 */ genException1(), /* 1, same as 0 */ genException1(), /* 2 */ genException2("x"), /* 3, same as 2 */ genException2("x"), /* 4, has different message than 2 */ genException2("y"), /* 5, has different stack trace than 2 */ genException3("x"), /* 6 */ genException4(true), /* 7, has different cause than 6 */ genException4(false), /* 8, has a cycle and should not crash */ genExceptionCycle() };
     73        // @formatter:on
     74        // CHECKSTYLE.ON: LineLength
     75
     76        for (int i = 0; i < testExceptions.length; i++) {
     77            for (int j = 0; j < testExceptions.length; j++) {
     78                boolean is01 = (i == 0 || i == 1) && (j == 0 || j == 1);
     79                boolean is23 = (i == 2 || i == 3) && (j == 2 || j == 3);
     80                assertEquals(i + ", " + j, is01 || is23 || i == j, testExceptions[i].isSame(testExceptions[j]));
     81            }
     82        }
     83    }
     84
     85    private ReportedException genException1() {
     86        RuntimeException e = new RuntimeException();
     87        return BugReport.intercept(e);
     88    }
     89
     90    private ReportedException genException2(String message) {
     91        RuntimeException e = new RuntimeException(message);
     92        RuntimeException e2 = new RuntimeException(e);
     93        return BugReport.intercept(e2);
     94    }
     95
     96    private ReportedException genException3(String message) {
     97        return genException2(message);
     98    }
     99
     100    private ReportedException genException4(boolean addCause) {
     101        RuntimeException e = new RuntimeException("x");
     102        RuntimeException e2 = new RuntimeException("x", addCause ? e : null);
     103        return BugReport.intercept(e2);
     104    }
     105
     106    private ReportedException genExceptionCycle() {
     107        CauseOverwriteException e = new CauseOverwriteException("x");
     108        RuntimeException e2 = new RuntimeException("x", e);
     109        e.myCause = e2;
     110        return BugReport.intercept(e2);
     111    }
     112}