Changeset 18636 in josm for trunk/test/unit


Ignore:
Timestamp:
2023-01-17T14:41:59+01:00 (16 months ago)
Author:
taylor.smock
Message:

Fix #21423: Prevent error codes from clashing

This works by creating a unique code using the test class name. The new
format for ignore entries will look like UNIQUECODE_CODE_MESSAGE_STATE.
The switchover date for the new entries is set at 2024-01-01 in order to
give users sufficient time to update, such that if a user has multiple
installations of JOSM, all versions will be able to use the same ignore
list. The compatibility code is intended to be removed in 2025.

Location:
trunk/test/unit/org/openstreetmap/josm
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java

    r18267 r18636  
    44import static org.junit.jupiter.api.Assertions.assertEquals;
    55import static org.junit.jupiter.api.Assertions.assertFalse;
     6import static org.junit.jupiter.api.Assertions.assertNotNull;
    67import static org.junit.jupiter.api.Assertions.assertTrue;
    78
     
    1718import java.util.Map;
    1819import java.util.Map.Entry;
     20import java.util.Objects;
    1921import java.util.Set;
    2022import java.util.function.Consumer;
     23import java.util.logging.Handler;
     24import java.util.logging.LogRecord;
    2125import java.util.stream.Collectors;
    2226
     
    2428import org.junit.jupiter.api.Test;
    2529import org.junit.jupiter.api.extension.RegisterExtension;
     30import org.junit.platform.commons.util.ReflectionUtils;
    2631import org.openstreetmap.josm.TestUtils;
    2732import org.openstreetmap.josm.data.Preferences;
     
    7479        Map<String, Throwable> loadingExceptions = PluginHandler.pluginLoadingExceptions.entrySet().stream()
    7580                .filter(e -> !(Utils.getRootCause(e.getValue()) instanceof HeadlessException))
    76                 .collect(Collectors.toMap(e -> e.getKey(), e -> Utils.getRootCause(e.getValue())));
     81                .collect(Collectors.toMap(Map.Entry::getKey, e -> Utils.getRootCause(e.getValue())));
    7782
    7883        List<PluginInformation> loadedPlugins = PluginHandler.getPlugins();
     
    9398        }
    9499
     100        Map<String, String> testCodeHashCollisions = checkForHashCollisions();
     101
    95102        Map<String, Throwable> noRestartExceptions = new HashMap<>();
    96103        testCompletelyRestartlessPlugins(loadedPlugins, noRestartExceptions);
     
    100107        debugPrint(layerExceptions);
    101108        debugPrint(noRestartExceptions);
     109        debugPrint(testCodeHashCollisions);
    102110
    103111        invalidManifestEntries = filterKnownErrors(invalidManifestEntries);
     
    105113        layerExceptions = filterKnownErrors(layerExceptions);
    106114        noRestartExceptions = filterKnownErrors(noRestartExceptions);
     115        testCodeHashCollisions = filterKnownErrors(testCodeHashCollisions);
    107116
    108117        String msg = errMsg("invalidManifestEntries", invalidManifestEntries) + '\n' +
    109118                errMsg("loadingExceptions", loadingExceptions) + '\n' +
    110119                errMsg("layerExceptions", layerExceptions) + '\n' +
    111                 errMsg("noRestartExceptions", noRestartExceptions);
     120                errMsg("noRestartExceptions", noRestartExceptions) + '\n' +
     121                errMsg("testCodeHashCollisions", testCodeHashCollisions);
    112122        assertTrue(invalidManifestEntries.isEmpty()
    113123                && loadingExceptions.isEmpty()
    114124                && layerExceptions.isEmpty()
    115                 && noRestartExceptions.isEmpty(), msg);
     125                && noRestartExceptions.isEmpty()
     126                && testCodeHashCollisions.isEmpty(), msg);
    116127    }
    117128
     
    122133    private static void testCompletelyRestartlessPlugins(List<PluginInformation> loadedPlugins,
    123134            Map<String, Throwable> noRestartExceptions) {
     135        final List<LogRecord> records = new ArrayList<>();
     136        Handler tempHandler = new Handler() {
     137            @Override
     138            public void publish(LogRecord record) {
     139                records.add(record);
     140            }
     141
     142            @Override
     143            public void flush() { /* Do nothing */ }
     144
     145            @Override
     146            public void close() throws SecurityException { /* Do nothing */ }
     147        };
     148        Logging.getLogger().addHandler(tempHandler);
    124149        try {
    125150            List<PluginInformation> restartable = loadedPlugins.parallelStream()
     
    142167            root.printStackTrace();
    143168            noRestartExceptions.put(findFaultyPlugin(loadedPlugins, root), root);
    144         }
     169            records.removeIf(record -> Objects.equals(Utils.getRootCause(record.getThrown()), root));
     170        } catch (AssertionError assertionError) {
     171            noRestartExceptions.put("Plugin load/unload failed", assertionError);
     172        } finally {
     173            Logging.getLogger().removeHandler(tempHandler);
     174            for (LogRecord record : records) {
     175                if (record.getThrown() != null) {
     176                    Throwable root = Utils.getRootCause(record.getThrown());
     177                    root.printStackTrace();
     178                    noRestartExceptions.put(findFaultyPlugin(loadedPlugins, root), root);
     179                }
     180            }
     181        }
     182    }
     183
     184    private static Map<String, String> checkForHashCollisions() {
     185        Map<Integer, List<String>> codes = new HashMap<>();
     186        for (Class<?> clazz : ReflectionUtils.findAllClassesInPackage("org.openstreetmap",
     187                org.openstreetmap.josm.data.validation.Test.class::isAssignableFrom, s -> true)) {
     188            if (org.openstreetmap.josm.data.validation.Test.class.isAssignableFrom(clazz)
     189            && !Objects.equals(org.openstreetmap.josm.data.validation.Test.class, clazz)) {
     190                // clazz.getName().hashCode() is how the base error codes are calculated since xxx
     191                // We want to avoid cases where the hashcode is too close, so we want to
     192                // ensure that there is at least 1m available codes after the hashCode.
     193                // This is needed since some plugins pick some really large number, and count up from there.
     194                int hashCeil = (int) Math.ceil(clazz.getName().hashCode() / 1_000_000d);
     195                int hashFloor = (int) Math.floor(clazz.getName().hashCode() / 1_000_000d);
     196                codes.computeIfAbsent(hashCeil, k -> new ArrayList<>()).add(clazz.getName());
     197                codes.computeIfAbsent(hashFloor, k -> new ArrayList<>()).add(clazz.getName());
     198            }
     199        }
     200        return codes.entrySet().stream().filter(entry -> entry.getValue().size() > 1).collect(
     201                Collectors.toMap(entry -> entry.getKey().toString(), entry -> String.join(", ", entry.getValue())));
    145202    }
    146203
     
    154211        System.out.println(invalidManifestEntries.entrySet()
    155212                .stream()
    156                 .map(e -> convertEntryToString(e))
     213                .map(PluginHandlerTestIT::convertEntryToString)
    157214                .collect(Collectors.joining(", ")));
    158215    }
     
    242299            try {
    243300                ClassLoader cl = PluginHandler.getPluginClassLoader(p.getName());
     301                assertNotNull(cl);
    244302                String pluginPackage = cl.loadClass(p.className).getPackage().getName();
    245303                for (StackTraceElement e : root.getStackTrace()) {
Note: See TracChangeset for help on using the changeset viewer.