Ticket #22183: 22183.patch
| File 22183.patch, 11.3 KB (added by , 4 years ago) |
|---|
-
src/org/openstreetmap/josm/gui/progress/swing/ProgressMonitorExecutor.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/progress/swing/ProgressMonitorExecutor.java b/src/org/openstreetmap/josm/gui/progress/swing/ProgressMonitorExecutor.java
a b 10 10 11 11 import org.openstreetmap.josm.tools.Logging; 12 12 import org.openstreetmap.josm.tools.Utils; 13 import org.openstreetmap.josm.tools.bugreport.BugReport; 13 14 14 15 /** 15 16 * Executor that displays the progress monitor to the user. … … 58 59 } 59 60 if (t != null) { 60 61 Logging.error("Thread {0} raised {1}", Thread.currentThread().getName(), t); 62 BugReport.addSuppressedException(t); 61 63 } 62 64 } 63 65 } -
src/org/openstreetmap/josm/gui/util/GuiHelper.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/util/GuiHelper.java b/src/org/openstreetmap/josm/gui/util/GuiHelper.java
a b 211 211 */ 212 212 static void handleEDTException(Throwable t) { 213 213 Logging.logWithStackTrace(Logging.LEVEL_ERROR, t, "Exception raised in EDT"); 214 BugReport.addSuppressedException(t); 214 215 } 215 216 216 217 /** -
src/org/openstreetmap/josm/tools/bugreport/BugReport.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReport.java b/src/org/openstreetmap/josm/tools/bugreport/BugReport.java
a b 4 4 import java.io.PrintWriter; 5 5 import java.io.Serializable; 6 6 import java.io.StringWriter; 7 import java.time.Instant; 8 import java.util.ArrayDeque; 9 import java.util.Deque; 7 10 import java.util.concurrent.CopyOnWriteArrayList; 8 11 import java.util.function.Predicate; 9 12 13 import org.openstreetmap.josm.tools.Pair; 14 10 15 /** 11 16 * This class contains utility methods to create and handle a bug report. 12 17 * <p> … … 39 44 */ 40 45 public final class BugReport implements Serializable { 41 46 private static final long serialVersionUID = 1L; 47 /** The maximum suppressed exceptions to keep to report */ 48 private static final byte MAXIMUM_SUPPRESSED_EXCEPTIONS = 4; 49 /** The list of suppressed exceptions, Pair<time reported, exception> */ 50 private static final Deque<Pair<Instant, Throwable>> SUPPRESSED_EXCEPTIONS = new ArrayDeque<>(MAXIMUM_SUPPRESSED_EXCEPTIONS); 42 51 43 52 private boolean includeStatusReport = true; 44 53 private boolean includeData = true; … … 55 64 includeAllStackTraces = e.mayHaveConcurrentSource(); 56 65 } 57 66 67 /** 68 * Add a suppressed exception. Mostly useful for when a chain of exceptions causes an actual bug report. 69 * This should only be used when an exception is raised in {@link org.openstreetmap.josm.gui.util.GuiHelper} 70 * or {@link org.openstreetmap.josm.gui.progress.swing.ProgressMonitorExecutor} at this time. 71 * @param t The throwable raised. If {@code null}, we add a new {@code NullPointerException} instead. 72 * @since xxx 73 */ 74 public static void addSuppressedException(Throwable t) { 75 SUPPRESSED_EXCEPTIONS.add(new Pair<>(Instant.now(), t != null ? t : new NullPointerException())); 76 // Ensure we aren't keeping exceptions forever 77 while (SUPPRESSED_EXCEPTIONS.size() > MAXIMUM_SUPPRESSED_EXCEPTIONS) { 78 SUPPRESSED_EXCEPTIONS.pop(); 79 } 80 } 81 58 82 /** 59 83 * Determines if this report should include a system status report 60 84 * @return <code>true</code> to include it. … … 135 159 if (isIncludeAllStackTraces()) { 136 160 exception.printReportThreadsTo(out); 137 161 } 162 if (!SUPPRESSED_EXCEPTIONS.isEmpty()) { 163 out.println("=== ADDITIONAL EXCEPTIONS ==="); 164 while (SUPPRESSED_EXCEPTIONS.peek() != null) { 165 Pair<Instant, Throwable> currentException = SUPPRESSED_EXCEPTIONS.pop(); 166 out.println("==== Exception at " + currentException.a.toEpochMilli() + " ===="); 167 currentException.b.printStackTrace(out); 168 } 169 } 138 170 return stringWriter.toString().replaceAll("\r", ""); 139 171 } 140 172 -
test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java b/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.tools.bugreport; 3 3 4 import static org.junit.jupiter.api.Assertions.assertAll; 5 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 4 6 import static org.junit.jupiter.api.Assertions.assertEquals; 7 import static org.junit.jupiter.api.Assertions.assertFalse; 5 8 import static org.junit.jupiter.api.Assertions.assertSame; 6 9 import static org.junit.jupiter.api.Assertions.assertTrue; 7 10 8 11 import java.io.IOException; 9 12 import java.io.PrintWriter; 10 13 import java.io.StringWriter; 14 import java.lang.reflect.Field; 15 import java.util.function.Consumer; 16 import java.util.regex.Matcher; 17 import java.util.regex.Pattern; 18 import java.util.stream.Stream; 11 19 12 20 import org.junit.jupiter.api.Test; 21 import org.junit.jupiter.params.ParameterizedTest; 22 import org.junit.jupiter.params.provider.Arguments; 23 import org.junit.jupiter.params.provider.MethodSource; 24 import org.junit.platform.commons.util.ReflectionUtils; 13 25 import org.openstreetmap.josm.actions.ShowStatusReportAction; 26 import org.openstreetmap.josm.gui.MainApplication; 27 import org.openstreetmap.josm.gui.util.GuiHelper; 14 28 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 29 import org.openstreetmap.josm.tools.Logging; 15 30 16 31 /** 17 32 * Tests the bug report class. … … 69 84 private String testGetCallingMethod2() { 70 85 return BugReport.getCallingMethod(2); 71 86 } 87 88 @Test 89 void testSuppressedExceptionsOrder() { 90 final String methodName = "testSuppressedExceptionsOrder"; 91 BugReport.addSuppressedException(new NullPointerException(methodName)); 92 BugReport.addSuppressedException(new IllegalStateException(methodName)); 93 BugReport bugReport = new BugReport(BugReport.intercept(new IOException(methodName))); 94 final String report = assertDoesNotThrow(() -> bugReport.getReportText(methodName)); 95 assertAll(() -> assertTrue(report.contains("NullPointerException")), 96 () -> assertTrue(report.contains("IOException")), 97 () -> assertTrue(report.contains("IllegalStateException"))); 98 int ioe = report.indexOf("IOException"); 99 int npe = report.indexOf("NullPointerException"); 100 int ise = report.indexOf("IllegalStateException"); 101 assertAll("Ordering of exceptions is wrong", 102 () -> assertTrue(ioe < npe, "IOException should be reported before NullPointerException"), 103 () -> assertTrue(npe < ise, "NullPointerException should be reported before IllegalStateException")); 104 } 105 106 static Stream<Arguments> testSuppressedExceptions() { 107 return Stream.of( 108 Arguments.of("GuiHelper::runInEDTAndWaitAndReturn", 109 (Consumer<Runnable>) r -> GuiHelper.runInEDTAndWaitAndReturn(() -> { 110 r.run(); 111 return null; 112 })), 113 Arguments.of("GuiHelper::runInEDTAndWait", (Consumer<Runnable>) GuiHelper::runInEDTAndWait), 114 Arguments.of("MainApplication.worker", (Consumer<Runnable>) MainApplication.worker::execute) 115 ); 116 } 117 118 @ParameterizedTest 119 @MethodSource 120 void testSuppressedExceptions(String workerName, Consumer<Runnable> worker) { 121 // Throw a npe in the worker. Workers might give us the exception, wrapped or otherwise. 122 try { 123 worker.accept(() -> { 124 throw new NullPointerException(); 125 }); 126 } catch (Exception e) { 127 // pass. MainApplication.worker can continue throwing the NPE; 128 Logging.trace(e); 129 } 130 // Ensure that the threads are synced 131 assertDoesNotThrow(() -> worker.accept(() -> { /* sync */ })); 132 // Now throw an exception 133 BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptions"))); 134 String report = bugReport.getReportText(workerName); 135 assertTrue(report.contains("IOException")); 136 assertTrue(report.contains("NullPointerException")); 137 } 138 139 @Test 140 void testSuppressedExceptionsReportedOnce() { 141 // Add the exception 142 BugReport.addSuppressedException(new NullPointerException("testSuppressedExceptionsReportedOnce")); 143 BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptionsReportedOnce"))); 144 // Get the report which clears the suppressed exceptions 145 String report = bugReport.getReportText(""); 146 assertTrue(report.contains("IOException")); 147 assertTrue(report.contains("NullPointerException")); 148 149 BugReport bugReport2 = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptionsReportedOnce"))); 150 String report2 = bugReport2.getReportText(""); 151 assertTrue(report2.contains("IOException")); 152 assertFalse(report2.contains("NullPointerException")); 153 } 154 155 @Test 156 void testManyExceptions() throws ReflectiveOperationException { 157 Field suppressedExceptions = BugReport.class.getDeclaredField("MAXIMUM_SUPPRESSED_EXCEPTIONS"); 158 ReflectionUtils.makeAccessible(suppressedExceptions); 159 final byte expected = suppressedExceptions.getByte(null); 160 final int end = 2 * expected; 161 // Add many suppressed exceptions 162 for (int i = 0; i < end; i++) { 163 BugReport.addSuppressedException(new NullPointerException("NPE: " + i)); 164 } 165 BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testManyExceptions"))); 166 String report = bugReport.getReportText(""); 167 Matcher matcher = Pattern.compile("NPE: (\\d+)").matcher(report); 168 for (int i = end - expected; i < end; ++i) { 169 assertTrue(matcher.find()); 170 assertEquals(Integer.toString(i), matcher.group(1)); 171 } 172 assertFalse(matcher.find()); 173 } 72 174 }
