source: josm/trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java@ 17360

Last change on this file since 17360 was 17288, checked in by Don-vip, 3 years ago

see #16567 - fix integration tests

File size: 11.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins;
3
4import static org.junit.jupiter.api.Assertions.assertEquals;
5import static org.junit.jupiter.api.Assertions.assertFalse;
6import static org.junit.jupiter.api.Assertions.assertTrue;
7
8import java.awt.GraphicsEnvironment;
9import java.awt.HeadlessException;
10import java.io.IOException;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.HashMap;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Map;
18import java.util.Map.Entry;
19import java.util.Set;
20import java.util.function.Consumer;
21import java.util.stream.Collectors;
22
23import org.junit.jupiter.api.BeforeAll;
24import org.junit.jupiter.api.Test;
25import org.junit.jupiter.api.extension.RegisterExtension;
26import org.openstreetmap.josm.TestUtils;
27import org.openstreetmap.josm.data.Preferences;
28import org.openstreetmap.josm.data.gpx.GpxData;
29import org.openstreetmap.josm.data.osm.DataSet;
30import org.openstreetmap.josm.gui.MainApplication;
31import org.openstreetmap.josm.gui.layer.GpxLayer;
32import org.openstreetmap.josm.gui.layer.Layer;
33import org.openstreetmap.josm.gui.layer.OsmDataLayer;
34import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
35import org.openstreetmap.josm.spi.preferences.Config;
36import org.openstreetmap.josm.testutils.JOSMTestRules;
37import org.openstreetmap.josm.tools.Destroyable;
38import org.openstreetmap.josm.tools.Utils;
39
40import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
41
42/**
43 * Integration tests of {@link PluginHandler} class.
44 */
45public class PluginHandlerTestIT {
46
47 private static final List<String> errorsToIgnore = new ArrayList<>();
48 /**
49 * Setup test.
50 */
51 @RegisterExtension
52 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
53 public static JOSMTestRules test = new JOSMTestRules().main().projection().preferences().https()
54 .timeout(10 * 60 * 1000);
55
56 /**
57 * Setup test
58 *
59 * @throws IOException in case of I/O error
60 */
61 @BeforeAll
62 public static void beforeClass() throws IOException {
63 errorsToIgnore.addAll(TestUtils.getIgnoredErrorMessages(PluginHandlerTestIT.class));
64 }
65
66 /**
67 * Test that available plugins rules can be loaded.
68 */
69 @Test
70 void testValidityOfAvailablePlugins() {
71 loadAllPlugins();
72
73 Map<String, Throwable> loadingExceptions = PluginHandler.pluginLoadingExceptions.entrySet().stream()
74 .filter(e -> !(Utils.getRootCause(e.getValue()) instanceof HeadlessException))
75 .collect(Collectors.toMap(e -> e.getKey(), e -> Utils.getRootCause(e.getValue())));
76
77 List<PluginInformation> loadedPlugins = PluginHandler.getPlugins();
78 Map<String, List<String>> invalidManifestEntries = loadedPlugins.stream().filter(pi -> !pi.invalidManifestEntries.isEmpty())
79 .collect(Collectors.toMap(pi -> pi.name, pi -> pi.invalidManifestEntries));
80
81 // Add/remove layers twice to test basic plugin good behaviour
82 Map<String, Throwable> layerExceptions = new HashMap<>();
83 for (int i = 0; i < 2; i++) {
84 OsmDataLayer layer = new OsmDataLayer(new DataSet(), "Layer "+i, null);
85 testPlugin(MainApplication.getLayerManager()::addLayer, layer, layerExceptions, loadedPlugins);
86 testPlugin(MainApplication.getLayerManager()::removeLayer, layer, layerExceptions, loadedPlugins);
87 }
88 for (int i = 0; i < 2; i++) {
89 GpxLayer layer = new GpxLayer(new GpxData(), "Layer "+i);
90 testPlugin(MainApplication.getLayerManager()::addLayer, layer, layerExceptions, loadedPlugins);
91 testPlugin(MainApplication.getLayerManager()::removeLayer, layer, layerExceptions, loadedPlugins);
92 }
93
94 Map<String, Throwable> noRestartExceptions = new HashMap<>();
95 testCompletelyRestartlessPlugins(loadedPlugins, noRestartExceptions);
96
97 debugPrint(invalidManifestEntries);
98 debugPrint(loadingExceptions);
99 debugPrint(layerExceptions);
100 debugPrint(noRestartExceptions);
101
102 invalidManifestEntries = filterKnownErrors(invalidManifestEntries);
103 loadingExceptions = filterKnownErrors(loadingExceptions);
104 layerExceptions = filterKnownErrors(layerExceptions);
105 noRestartExceptions = filterKnownErrors(noRestartExceptions);
106
107 String msg = Arrays.toString(invalidManifestEntries.entrySet().toArray()) + '\n' +
108 Arrays.toString(loadingExceptions.entrySet().toArray()) + '\n' +
109 Arrays.toString(layerExceptions.entrySet().toArray()) + '\n'
110 + Arrays.toString(noRestartExceptions.entrySet().toArray());
111 assertTrue(invalidManifestEntries.isEmpty() && loadingExceptions.isEmpty() && layerExceptions.isEmpty(), msg);
112 }
113
114 private static void testCompletelyRestartlessPlugins(List<PluginInformation> loadedPlugins,
115 Map<String, Throwable> noRestartExceptions) {
116 try {
117 List<PluginInformation> restartable = loadedPlugins.parallelStream()
118 .filter(info -> PluginHandler.getPlugin(info.name) instanceof Destroyable)
119 .collect(Collectors.toList());
120 // ensure good plugin behavior with regards to Destroyable (i.e., they can be
121 // removed and readded)
122 for (int i = 0; i < 2; i++) {
123 assertFalse(PluginHandler.removePlugins(restartable));
124 assertTrue(restartable.stream().noneMatch(info -> PluginHandler.getPlugins().contains(info)));
125 loadPlugins(restartable);
126 }
127
128 assertTrue(PluginHandler.removePlugins(loadedPlugins));
129 assertTrue(restartable.parallelStream().noneMatch(info -> PluginHandler.getPlugins().contains(info)));
130 } catch (Exception | LinkageError t) {
131 Throwable root = Utils.getRootCause(t);
132 root.printStackTrace();
133 noRestartExceptions.put(findFaultyPlugin(loadedPlugins, root), root);
134 }
135 }
136
137 private static <T> Map<String, T> filterKnownErrors(Map<String, T> errorMap) {
138 return errorMap.entrySet().parallelStream()
139 .filter(entry -> !errorsToIgnore.contains(convertEntryToString(entry)))
140 .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
141 }
142
143 private static void debugPrint(Map<String, ?> invalidManifestEntries) {
144 System.out.println(invalidManifestEntries.entrySet()
145 .stream()
146 .map(e -> convertEntryToString(e))
147 .collect(Collectors.joining(", ")));
148 }
149
150 private static String convertEntryToString(Entry<String, ?> entry) {
151 return entry.getKey() + "=\"" + entry.getValue() + "\"";
152 }
153
154 /**
155 * Downloads and loads all JOSM plugins.
156 */
157 public static void loadAllPlugins() {
158 // Download complete list of plugins
159 ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask(
160 Preferences.main().getOnlinePluginSites());
161 pluginInfoDownloadTask.run();
162 List<PluginInformation> plugins = pluginInfoDownloadTask.getAvailablePlugins();
163 System.out.println("Original plugin list contains " + plugins.size() + " plugins");
164 assertFalse(plugins.isEmpty());
165 PluginInformation info = plugins.get(0);
166 assertFalse(info.getName().isEmpty());
167 assertFalse(info.getClass().getName().isEmpty());
168
169 // Filter deprecated and unmaintained ones, or those not responsive enough to match our continuous integration needs
170 List<String> uncooperatingPlugins = Arrays.asList("ebdirigo", "scoutsigns", "josm-config");
171 Set<String> deprecatedPlugins = PluginHandler.getDeprecatedAndUnmaintainedPlugins();
172 for (Iterator<PluginInformation> it = plugins.iterator(); it.hasNext();) {
173 PluginInformation pi = it.next();
174 if (deprecatedPlugins.contains(pi.name) || uncooperatingPlugins.contains(pi.name)) {
175 System.out.println("Ignoring " + pi.name + " (deprecated, unmaintained, or uncooperative)");
176 it.remove();
177 }
178 }
179
180 // On Java < 11 and headless mode, filter plugins requiring JavaFX as Monocle is not available
181 int javaVersion = Utils.getJavaVersion();
182 if (GraphicsEnvironment.isHeadless() && javaVersion < 11) {
183 for (Iterator<PluginInformation> it = plugins.iterator(); it.hasNext();) {
184 PluginInformation pi = it.next();
185 if (pi.getRequiredPlugins().contains("javafx")) {
186 System.out.println("Ignoring " + pi.name + " (requiring JavaFX and we're using Java < 11 in headless mode)");
187 it.remove();
188 }
189 }
190 }
191
192 System.out.println("Filtered plugin list contains " + plugins.size() + " plugins");
193
194 // Download plugins
195 downloadPlugins(plugins);
196
197 loadPlugins(plugins);
198 }
199
200 static void loadPlugins(List<PluginInformation> plugins) {
201 // Load early plugins
202 PluginHandler.loadEarlyPlugins(null, plugins, null);
203
204 // Load late plugins
205 PluginHandler.loadLatePlugins(null, plugins, null);
206 }
207
208 void testPlugin(Consumer<Layer> consumer, Layer layer,
209 Map<String, Throwable> layerExceptions, Collection<PluginInformation> loadedPlugins) {
210 try {
211 consumer.accept(layer);
212 } catch (Exception | LinkageError t) {
213 Throwable root = Utils.getRootCause(t);
214 root.printStackTrace();
215 layerExceptions.put(findFaultyPlugin(loadedPlugins, root), root);
216 }
217 }
218
219 private static String findFaultyPlugin(Collection<PluginInformation> plugins, Throwable root) {
220 for (PluginInformation p : plugins) {
221 try {
222 ClassLoader cl = PluginHandler.getPluginClassLoader(p.getName());
223 String pluginPackage = cl.loadClass(p.className).getPackage().getName();
224 for (StackTraceElement e : root.getStackTrace()) {
225 try {
226 String stackPackage = cl.loadClass(e.getClassName()).getPackage().getName();
227 if (stackPackage.startsWith(pluginPackage)) {
228 return p.name;
229 }
230 } catch (ClassNotFoundException ex) {
231 System.err.println(ex.getMessage());
232 continue;
233 }
234 }
235 } catch (ClassNotFoundException ex) {
236 System.err.println(ex.getMessage());
237 continue;
238 }
239 }
240 return "<unknown>";
241 }
242
243 /**
244 * Download plugins
245 * @param plugins plugins to download
246 */
247 public static void downloadPlugins(Collection<PluginInformation> plugins) {
248 // Update the locally installed plugins
249 PluginDownloadTask pluginDownloadTask = new PluginDownloadTask(NullProgressMonitor.INSTANCE, plugins, null);
250 // Increase default timeout to avoid random network errors on big jar files
251 int defTimeout = Config.getPref().getInt("socket.timeout.read", 30);
252 Config.getPref().putInt("socket.timeout.read", 2 * defTimeout);
253 pluginDownloadTask.run();
254 // Restore default timeout
255 Config.getPref().putInt("socket.timeout.read", defTimeout);
256 assertTrue(pluginDownloadTask.getFailedPlugins().isEmpty(), pluginDownloadTask.getFailedPlugins()::toString);
257 assertEquals(plugins.size(), pluginDownloadTask.getDownloadedPlugins().size());
258
259 // Update Plugin info for downloaded plugins
260 PluginHandler.refreshLocalUpdatedPluginInfo(pluginDownloadTask.getDownloadedPlugins());
261 }
262}
Note: See TracBrowser for help on using the repository browser.