1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.tools.bugreport;
|
---|
3 |
|
---|
4 | import static org.junit.jupiter.api.Assertions.assertAll;
|
---|
5 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
---|
6 | import static org.junit.jupiter.api.Assertions.assertEquals;
|
---|
7 | import static org.junit.jupiter.api.Assertions.assertFalse;
|
---|
8 | import static org.junit.jupiter.api.Assertions.assertSame;
|
---|
9 | import static org.junit.jupiter.api.Assertions.assertTrue;
|
---|
10 |
|
---|
11 | import java.io.IOException;
|
---|
12 | import java.io.PrintWriter;
|
---|
13 | import java.io.StringWriter;
|
---|
14 | import java.lang.reflect.Field;
|
---|
15 | import java.util.concurrent.TimeUnit;
|
---|
16 | import java.util.function.Consumer;
|
---|
17 | import java.util.logging.Handler;
|
---|
18 | import java.util.regex.Matcher;
|
---|
19 | import java.util.regex.Pattern;
|
---|
20 | import java.util.stream.Stream;
|
---|
21 |
|
---|
22 | import org.junit.jupiter.api.AfterAll;
|
---|
23 | import org.junit.jupiter.api.BeforeAll;
|
---|
24 | import org.junit.jupiter.api.Test;
|
---|
25 | import org.junit.jupiter.params.ParameterizedTest;
|
---|
26 | import org.junit.jupiter.params.provider.Arguments;
|
---|
27 | import org.junit.jupiter.params.provider.MethodSource;
|
---|
28 | import org.junit.platform.commons.util.ReflectionUtils;
|
---|
29 | import org.openstreetmap.josm.actions.ShowStatusReportAction;
|
---|
30 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
31 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
32 | import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
|
---|
33 | import org.openstreetmap.josm.tools.Logging;
|
---|
34 |
|
---|
35 | /**
|
---|
36 | * Tests the bug report class.
|
---|
37 | * @author Michael Zangl
|
---|
38 | */
|
---|
39 | // Preferences for the report text
|
---|
40 | @BasicPreferences
|
---|
41 | class BugReportTest {
|
---|
42 | private static Handler[] handlers;
|
---|
43 |
|
---|
44 | @AfterAll
|
---|
45 | static void cleanup() {
|
---|
46 | // Clear queue
|
---|
47 | new BugReport(BugReport.intercept(new NullPointerException())).getReportText("");
|
---|
48 | Logging.clearLastErrorAndWarnings();
|
---|
49 | for (Handler handler : handlers) {
|
---|
50 | Logging.getLogger().addHandler(handler);
|
---|
51 | }
|
---|
52 | }
|
---|
53 |
|
---|
54 | @BeforeAll
|
---|
55 | static void setup() {
|
---|
56 | handlers = Logging.getLogger().getHandlers();
|
---|
57 | // Avoid console spam
|
---|
58 | for (Handler handler : handlers) {
|
---|
59 | Logging.getLogger().removeHandler(handler);
|
---|
60 | }
|
---|
61 | }
|
---|
62 |
|
---|
63 | /**
|
---|
64 | * Test {@link BugReport#getReportText}
|
---|
65 | */
|
---|
66 | @Test
|
---|
67 | void testReportText() {
|
---|
68 | ReportedException e = interceptInChildMethod(new IOException("test-exception-message"));
|
---|
69 | e.put("test-key", "test-value");
|
---|
70 | String text = new BugReport(e).getReportText(ShowStatusReportAction.getReportHeader());
|
---|
71 |
|
---|
72 | assertTrue(text.contains("test-exception-message"));
|
---|
73 | assertTrue(text.contains("interceptInChildMethod"));
|
---|
74 | assertTrue(text.contains("testReportText")); // stack trace
|
---|
75 | assertTrue(text.contains("test-key: test-value"));
|
---|
76 | }
|
---|
77 |
|
---|
78 | /**
|
---|
79 | * Test {@link BugReport#intercept(Throwable)}
|
---|
80 | */
|
---|
81 | @Test
|
---|
82 | void testIntercept() {
|
---|
83 | IOException base = new IOException("test");
|
---|
84 | ReportedException intercepted = interceptInChildMethod(base);
|
---|
85 | assertEquals(intercepted.getCause(), base);
|
---|
86 |
|
---|
87 | StringWriter out = new StringWriter();
|
---|
88 | intercepted.printReportDataTo(new PrintWriter(out));
|
---|
89 |
|
---|
90 | assertTrue(out.toString().contains("interceptInChildMethod")); // calling method.
|
---|
91 |
|
---|
92 | assertSame(intercepted, BugReport.intercept(intercepted));
|
---|
93 | }
|
---|
94 |
|
---|
95 | private ReportedException interceptInChildMethod(IOException base) {
|
---|
96 | return BugReport.intercept(base);
|
---|
97 | }
|
---|
98 |
|
---|
99 | /**
|
---|
100 | * Test {@link BugReport#getCallingMethod(int)}
|
---|
101 | */
|
---|
102 | @Test
|
---|
103 | void testGetCallingMethod() {
|
---|
104 | assertEquals("BugReportTest#testGetCallingMethod", BugReport.getCallingMethod(1));
|
---|
105 | assertEquals("BugReportTest#testGetCallingMethod", testGetCallingMethod2());
|
---|
106 | assertEquals("?", BugReport.getCallingMethod(100));
|
---|
107 | }
|
---|
108 |
|
---|
109 | private String testGetCallingMethod2() {
|
---|
110 | return BugReport.getCallingMethod(2);
|
---|
111 | }
|
---|
112 |
|
---|
113 | @Test
|
---|
114 | void testSuppressedExceptionsOrder() {
|
---|
115 | final String methodName = "testSuppressedExceptionsOrder";
|
---|
116 | BugReport.addSuppressedException(new NullPointerException(methodName));
|
---|
117 | BugReport.addSuppressedException(new IllegalStateException(methodName));
|
---|
118 | BugReport bugReport = new BugReport(BugReport.intercept(new IOException(methodName)));
|
---|
119 | final String report = assertDoesNotThrow(() -> bugReport.getReportText(methodName));
|
---|
120 | assertAll(() -> assertTrue(report.contains("NullPointerException")),
|
---|
121 | () -> assertTrue(report.contains("IOException")),
|
---|
122 | () -> assertTrue(report.contains("IllegalStateException")));
|
---|
123 | int ioe = report.indexOf("IOException");
|
---|
124 | int npe = report.indexOf("NullPointerException");
|
---|
125 | int ise = report.indexOf("IllegalStateException");
|
---|
126 | assertAll("Ordering of exceptions is wrong",
|
---|
127 | () -> assertTrue(ioe < npe, "IOException should be reported before NullPointerException"),
|
---|
128 | () -> assertTrue(npe < ise, "NullPointerException should be reported before IllegalStateException"));
|
---|
129 | }
|
---|
130 |
|
---|
131 | static Stream<Arguments> testSuppressedExceptions() {
|
---|
132 | return Stream.of(
|
---|
133 | Arguments.of("GuiHelper::runInEDTAndWaitAndReturn",
|
---|
134 | (Consumer<Runnable>) r -> GuiHelper.runInEDTAndWaitAndReturn(() -> {
|
---|
135 | r.run();
|
---|
136 | return null;
|
---|
137 | })),
|
---|
138 | Arguments.of("GuiHelper::runInEDTAndWait", (Consumer<Runnable>) GuiHelper::runInEDTAndWait),
|
---|
139 | Arguments.of("MainApplication.worker", (Consumer<Runnable>) runnable -> {
|
---|
140 | MainApplication.worker.execute(runnable);
|
---|
141 | assertDoesNotThrow(() -> MainApplication.worker.submit(() -> { /* Sync thread */ }).get(1, TimeUnit.SECONDS));
|
---|
142 | })
|
---|
143 | );
|
---|
144 | }
|
---|
145 |
|
---|
146 | @ParameterizedTest
|
---|
147 | @MethodSource
|
---|
148 | void testSuppressedExceptions(String workerName, Consumer<Runnable> worker) {
|
---|
149 | // Throw a npe in the worker. Workers might give us the exception, wrapped or otherwise.
|
---|
150 | try {
|
---|
151 | worker.accept(() -> {
|
---|
152 | throw new NullPointerException();
|
---|
153 | });
|
---|
154 | } catch (Exception e) {
|
---|
155 | // pass. MainApplication.worker can continue throwing the NPE;
|
---|
156 | Logging.trace(e);
|
---|
157 | }
|
---|
158 | // Now throw an exception
|
---|
159 | BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptions")));
|
---|
160 | String report = bugReport.getReportText(workerName);
|
---|
161 | assertTrue(report.contains("IOException"));
|
---|
162 | assertTrue(report.contains("NullPointerException"));
|
---|
163 | }
|
---|
164 |
|
---|
165 | @Test
|
---|
166 | void testSuppressedExceptionsReportedOnce() {
|
---|
167 | // Add the exception
|
---|
168 | BugReport.addSuppressedException(new NullPointerException("testSuppressedExceptionsReportedOnce"));
|
---|
169 | BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptionsReportedOnce")));
|
---|
170 | // Get the report which clears the suppressed exceptions
|
---|
171 | String report = bugReport.getReportText("");
|
---|
172 | assertTrue(report.contains("IOException"));
|
---|
173 | assertTrue(report.contains("NullPointerException"));
|
---|
174 |
|
---|
175 | BugReport bugReport2 = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptionsReportedOnce")));
|
---|
176 | String report2 = bugReport2.getReportText("");
|
---|
177 | assertTrue(report2.contains("IOException"));
|
---|
178 | assertFalse(report2.contains("NullPointerException"));
|
---|
179 | }
|
---|
180 |
|
---|
181 | @Test
|
---|
182 | void testManyExceptions() throws ReflectiveOperationException {
|
---|
183 | Field suppressedExceptions = BugReport.class.getDeclaredField("MAXIMUM_SUPPRESSED_EXCEPTIONS");
|
---|
184 | ReflectionUtils.makeAccessible(suppressedExceptions);
|
---|
185 | final byte expected = suppressedExceptions.getByte(null);
|
---|
186 | final int end = 2 * expected;
|
---|
187 | // Add many suppressed exceptions
|
---|
188 | for (int i = 0; i < end; i++) {
|
---|
189 | BugReport.addSuppressedException(new NullPointerException("NPE: " + i));
|
---|
190 | }
|
---|
191 | BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testManyExceptions")));
|
---|
192 | String report = bugReport.getReportText("");
|
---|
193 | Matcher matcher = Pattern.compile("NPE: (\\d+)").matcher(report);
|
---|
194 | for (int i = end - expected; i < end; ++i) {
|
---|
195 | assertTrue(matcher.find());
|
---|
196 | assertEquals(Integer.toString(i), matcher.group(1));
|
---|
197 | }
|
---|
198 | assertFalse(matcher.find());
|
---|
199 | }
|
---|
200 |
|
---|
201 | @Test
|
---|
202 | void testNullException() {
|
---|
203 | // This should add a NPE to the suppressed exceptions
|
---|
204 | assertDoesNotThrow(() -> BugReport.addSuppressedException(null));
|
---|
205 | BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testNullException")));
|
---|
206 | // Getting the report text should not throw an exception.
|
---|
207 | String report = assertDoesNotThrow(() -> bugReport.getReportText(""));
|
---|
208 | assertTrue(report.contains("IOException"));
|
---|
209 | assertTrue(report.contains("NullPointerException"));
|
---|
210 | }
|
---|
211 | }
|
---|