Ignore:
Timestamp:
2022-09-06T18:40:21+02:00 (2 years ago)
Author:
taylor.smock
Message:

See #22183: NoClassDefFoundError: Could not initialize class org.openstreetmap.josm.actions.SessionSaveAction

The root cause is a suppressed NPE whose stack trace is not given
in the bug report.

This adds a method to BugReport to add suppressed exceptions to a report.
In the future, if we keep the code long-term, methods in Logging which take
exceptions may assume the exception is suppressed, and add it to the
suppressed exceptions.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportTest.java

    r18037 r18549  
    22package org.openstreetmap.josm.tools.bugreport;
    33
     4import static org.junit.jupiter.api.Assertions.assertAll;
     5import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
    46import static org.junit.jupiter.api.Assertions.assertEquals;
     7import static org.junit.jupiter.api.Assertions.assertFalse;
    58import static org.junit.jupiter.api.Assertions.assertSame;
    69import static org.junit.jupiter.api.Assertions.assertTrue;
     
    912import java.io.PrintWriter;
    1013import java.io.StringWriter;
    11 
     14import java.lang.reflect.Field;
     15import java.util.function.Consumer;
     16import java.util.logging.Handler;
     17import java.util.regex.Matcher;
     18import java.util.regex.Pattern;
     19import java.util.stream.Stream;
     20
     21import org.junit.jupiter.api.AfterAll;
     22import org.junit.jupiter.api.BeforeAll;
    1223import org.junit.jupiter.api.Test;
     24import org.junit.jupiter.params.ParameterizedTest;
     25import org.junit.jupiter.params.provider.Arguments;
     26import org.junit.jupiter.params.provider.MethodSource;
     27import org.junit.platform.commons.util.ReflectionUtils;
    1328import org.openstreetmap.josm.actions.ShowStatusReportAction;
     29import org.openstreetmap.josm.gui.MainApplication;
     30import org.openstreetmap.josm.gui.util.GuiHelper;
    1431import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
     32import org.openstreetmap.josm.tools.Logging;
    1533
    1634/**
     
    2139@BasicPreferences
    2240class BugReportTest {
     41    private static Handler[] handlers;
     42
     43    @AfterAll
     44    static void cleanup() {
     45        // Clear queue
     46        new BugReport(BugReport.intercept(new NullPointerException())).getReportText("");
     47        Logging.clearLastErrorAndWarnings();
     48        for (Handler handler : handlers) {
     49            Logging.getLogger().addHandler(handler);
     50        }
     51    }
     52
     53    @BeforeAll
     54    static void setup() {
     55        handlers = Logging.getLogger().getHandlers();
     56        // Avoid console spam
     57        for (Handler handler : handlers) {
     58            Logging.getLogger().removeHandler(handler);
     59        }
     60    }
     61
    2362    /**
    2463     * Test {@link BugReport#getReportText}
     
    70109        return BugReport.getCallingMethod(2);
    71110    }
     111
     112    @Test
     113    void testSuppressedExceptionsOrder() {
     114        final String methodName = "testSuppressedExceptionsOrder";
     115        BugReport.addSuppressedException(new NullPointerException(methodName));
     116        BugReport.addSuppressedException(new IllegalStateException(methodName));
     117        BugReport bugReport = new BugReport(BugReport.intercept(new IOException(methodName)));
     118        final String report = assertDoesNotThrow(() -> bugReport.getReportText(methodName));
     119        assertAll(() -> assertTrue(report.contains("NullPointerException")),
     120                () -> assertTrue(report.contains("IOException")),
     121                () -> assertTrue(report.contains("IllegalStateException")));
     122        int ioe = report.indexOf("IOException");
     123        int npe = report.indexOf("NullPointerException");
     124        int ise = report.indexOf("IllegalStateException");
     125        assertAll("Ordering of exceptions is wrong",
     126                () -> assertTrue(ioe < npe, "IOException should be reported before NullPointerException"),
     127                () -> assertTrue(npe < ise, "NullPointerException should be reported before IllegalStateException"));
     128    }
     129
     130    static Stream<Arguments> testSuppressedExceptions() {
     131        return Stream.of(
     132                Arguments.of("GuiHelper::runInEDTAndWaitAndReturn",
     133                        (Consumer<Runnable>) r -> GuiHelper.runInEDTAndWaitAndReturn(() -> {
     134                            r.run();
     135                            return null;
     136                        })),
     137                Arguments.of("GuiHelper::runInEDTAndWait", (Consumer<Runnable>) GuiHelper::runInEDTAndWait),
     138                Arguments.of("MainApplication.worker", (Consumer<Runnable>) MainApplication.worker::execute)
     139        );
     140    }
     141
     142    @ParameterizedTest
     143    @MethodSource
     144    void testSuppressedExceptions(String workerName, Consumer<Runnable> worker) {
     145        // Throw a npe in the worker. Workers might give us the exception, wrapped or otherwise.
     146        try {
     147            worker.accept(() -> {
     148                throw new NullPointerException();
     149            });
     150        } catch (Exception e) {
     151            // pass. MainApplication.worker can continue throwing the NPE;
     152            Logging.trace(e);
     153        }
     154        // Ensure that the threads are synced
     155        assertDoesNotThrow(() -> worker.accept(() -> { /* sync */ }));
     156        // Now throw an exception
     157        BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptions")));
     158        String report = bugReport.getReportText(workerName);
     159        assertTrue(report.contains("IOException"));
     160        assertTrue(report.contains("NullPointerException"));
     161    }
     162
     163    @Test
     164    void testSuppressedExceptionsReportedOnce() {
     165        // Add the exception
     166        BugReport.addSuppressedException(new NullPointerException("testSuppressedExceptionsReportedOnce"));
     167        BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptionsReportedOnce")));
     168        // Get the report which clears the suppressed exceptions
     169        String report = bugReport.getReportText("");
     170        assertTrue(report.contains("IOException"));
     171        assertTrue(report.contains("NullPointerException"));
     172
     173        BugReport bugReport2 = new BugReport(BugReport.intercept(new IOException("testSuppressedExceptionsReportedOnce")));
     174        String report2 = bugReport2.getReportText("");
     175        assertTrue(report2.contains("IOException"));
     176        assertFalse(report2.contains("NullPointerException"));
     177    }
     178
     179    @Test
     180    void testManyExceptions() throws ReflectiveOperationException {
     181        Field suppressedExceptions = BugReport.class.getDeclaredField("MAXIMUM_SUPPRESSED_EXCEPTIONS");
     182        ReflectionUtils.makeAccessible(suppressedExceptions);
     183        final byte expected = suppressedExceptions.getByte(null);
     184        final int end = 2 * expected;
     185        // Add many suppressed exceptions
     186        for (int i = 0; i < end; i++) {
     187            BugReport.addSuppressedException(new NullPointerException("NPE: " + i));
     188        }
     189        BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testManyExceptions")));
     190        String report = bugReport.getReportText("");
     191        Matcher matcher = Pattern.compile("NPE: (\\d+)").matcher(report);
     192        for (int i = end - expected; i < end; ++i) {
     193            assertTrue(matcher.find());
     194            assertEquals(Integer.toString(i), matcher.group(1));
     195        }
     196        assertFalse(matcher.find());
     197    }
     198
     199    @Test
     200    void testNullException() {
     201        // This should add a NPE to the suppressed exceptions
     202        assertDoesNotThrow(() -> BugReport.addSuppressedException(null));
     203        BugReport bugReport = new BugReport(BugReport.intercept(new IOException("testNullException")));
     204        // Getting the report text should not throw an exception.
     205        String report = assertDoesNotThrow(() -> bugReport.getReportText(""));
     206        assertTrue(report.contains("IOException"));
     207        assertTrue(report.contains("NullPointerException"));
     208    }
    72209}
Note: See TracChangeset for help on using the changeset viewer.