Index: trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java
===================================================================
--- trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java	(revision 15503)
+++ trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java	(revision 15508)
@@ -8,4 +8,6 @@
 import java.awt.GraphicsEnvironment;
 import java.awt.HeadlessException;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -14,4 +16,5 @@
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -19,6 +22,8 @@
 
 import org.apache.commons.lang3.exception.ExceptionUtils;
-import org.junit.Rule;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
 import org.junit.Test;
+import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.gpx.GpxData;
@@ -31,4 +36,5 @@
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -40,10 +46,22 @@
 public class PluginHandlerTestIT {
 
+    private static List<String> errorsToIgnore = new ArrayList<>();
     /**
      * Setup test.
      */
-    @Rule
+    @ClassRule
     @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules().main().projection().preferences().https().timeout(10*60*1000);
+    public static JOSMTestRules test = new JOSMTestRules().main().projection().preferences().https()
+            .timeout(10 * 60 * 1000);
+
+    /**
+     * Setup test
+     *
+     * @throws IOException in case of I/O error
+     */
+    @BeforeClass
+    public static void beforeClass() throws IOException {
+        errorsToIgnore.addAll(TestUtils.getIgnoredErrorMessages(PluginHandlerTestIT.class));
+    }
 
     /**
@@ -75,11 +93,51 @@
         }
 
+        Map<String, Throwable> noRestartExceptions = new HashMap<>();
+        testCompletelyRestartlessPlugins(loadedPlugins, noRestartExceptions);
+
         debugPrint(invalidManifestEntries);
         debugPrint(loadingExceptions);
         debugPrint(layerExceptions);
+        debugPrint(noRestartExceptions);
+
+        invalidManifestEntries = filterKnownErrors(invalidManifestEntries);
+        loadingExceptions = filterKnownErrors(loadingExceptions);
+        layerExceptions = filterKnownErrors(layerExceptions);
+        noRestartExceptions = filterKnownErrors(noRestartExceptions);
+
         String msg = Arrays.toString(invalidManifestEntries.entrySet().toArray()) + '\n' +
                      Arrays.toString(loadingExceptions.entrySet().toArray()) + '\n' +
-                     Arrays.toString(layerExceptions.entrySet().toArray());
+                Arrays.toString(layerExceptions.entrySet().toArray()) + '\n'
+                + Arrays.toString(noRestartExceptions.entrySet().toArray());
         assertTrue(msg, invalidManifestEntries.isEmpty() && loadingExceptions.isEmpty() && layerExceptions.isEmpty());
+    }
+
+    private static void testCompletelyRestartlessPlugins(List<PluginInformation> loadedPlugins,
+            Map<String, Throwable> noRestartExceptions) {
+        try {
+            List<PluginInformation> restartable = loadedPlugins.parallelStream()
+                    .filter(info -> PluginHandler.getPlugin(info.name) instanceof Destroyable)
+                    .collect(Collectors.toList());
+            // ensure good plugin behavior with regards to Destroyable (i.e., they can be
+            // removed and readded)
+            for (int i = 0; i < 2; i++) {
+                assertFalse(PluginHandler.removePlugins(restartable));
+                assertTrue(restartable.stream().noneMatch(info -> PluginHandler.getPlugins().contains(info)));
+                loadPlugins(restartable);
+            }
+
+            assertTrue(PluginHandler.removePlugins(loadedPlugins));
+            assertTrue(restartable.parallelStream().noneMatch(info -> PluginHandler.getPlugins().contains(info)));
+        } catch (Exception | LinkageError t) {
+            Throwable root = ExceptionUtils.getRootCause(t);
+            root.printStackTrace();
+            noRestartExceptions.put(findFaultyPlugin(loadedPlugins, root), root);
+        }
+    }
+
+    private static <T> Map<String, T> filterKnownErrors(Map<String, T> errorMap) {
+        return errorMap.entrySet().parallelStream()
+                .filter(entry -> !errorsToIgnore.contains(convertEntryToString(entry)))
+                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
     }
 
@@ -87,6 +145,10 @@
         System.out.println(invalidManifestEntries.entrySet()
                 .stream()
-                .map(e -> e.getKey() + "=\"" + e.getValue() + "\"")
+                .map(e -> convertEntryToString(e))
                 .collect(Collectors.joining(", ")));
+    }
+
+    private static String convertEntryToString(Entry<String, ?> entry) {
+        return entry.getKey() + "=\"" + entry.getValue() + "\"";
     }
 
@@ -134,4 +196,8 @@
         downloadPlugins(plugins);
 
+        loadPlugins(plugins);
+    }
+
+    static void loadPlugins(List<PluginInformation> plugins) {
         // Load early plugins
         PluginHandler.loadEarlyPlugins(null, plugins, null);
