source: josm/trunk/test/unit/org/openstreetmap/josm/gui/MainApplicationTest.java@ 19359

Last change on this file since 19359 was 19209, checked in by taylor.smock, 10 months ago

Add @Plugins annotation to clean up after plugins have been loaded in tests

This should help reduce the issues related to plugins not being unloaded after
PluginHandlerTestIT when tests are run locally with a UI -- the primary cause
of problems is the SDS plugin though.

  • Property svn:eol-style set to native
File size: 15.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
5import static org.junit.jupiter.api.Assertions.assertEquals;
6import static org.junit.jupiter.api.Assertions.assertFalse;
7import static org.junit.jupiter.api.Assertions.assertInstanceOf;
8import static org.junit.jupiter.api.Assertions.assertNotNull;
9import static org.junit.jupiter.api.Assertions.assertNull;
10import static org.junit.jupiter.api.Assertions.assertTrue;
11import static org.junit.jupiter.api.Assumptions.assumeFalse;
12
13import java.awt.BorderLayout;
14import java.awt.event.KeyEvent;
15import java.io.ByteArrayOutputStream;
16import java.io.IOException;
17import java.io.PrintStream;
18import java.net.MalformedURLException;
19import java.net.URL;
20import java.nio.charset.StandardCharsets;
21import java.nio.file.Paths;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.Collections;
25import java.util.List;
26import java.util.concurrent.ExecutionException;
27import java.util.concurrent.Future;
28import java.util.concurrent.atomic.AtomicReference;
29import java.util.jar.Attributes;
30
31import javax.swing.JComponent;
32import javax.swing.JPanel;
33import javax.swing.UIDefaults;
34import javax.swing.UIManager;
35import javax.swing.plaf.metal.MetalLookAndFeel;
36
37import org.awaitility.Awaitility;
38import org.awaitility.Durations;
39import org.junit.jupiter.api.Test;
40import org.junit.jupiter.api.Timeout;
41import org.openstreetmap.josm.TestUtils;
42import org.openstreetmap.josm.actions.JosmAction;
43import org.openstreetmap.josm.actions.OpenLocationAction;
44import org.openstreetmap.josm.data.Version;
45import org.openstreetmap.josm.data.osm.DataSet;
46import org.openstreetmap.josm.gui.SplashScreen.SplashProgressMonitor;
47import org.openstreetmap.josm.gui.layer.GpxLayer;
48import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
49import org.openstreetmap.josm.gui.preferences.display.LafPreference;
50import org.openstreetmap.josm.plugins.PluginClassLoader;
51import org.openstreetmap.josm.plugins.PluginHandler;
52import org.openstreetmap.josm.plugins.PluginHandlerTestIT;
53import org.openstreetmap.josm.plugins.PluginInformation;
54import org.openstreetmap.josm.plugins.PluginListParseException;
55import org.openstreetmap.josm.plugins.PluginListParser;
56import org.openstreetmap.josm.spi.preferences.Config;
57import org.openstreetmap.josm.testutils.annotations.HTTPS;
58import org.openstreetmap.josm.testutils.annotations.Main;
59import org.openstreetmap.josm.testutils.annotations.OsmApi;
60import org.openstreetmap.josm.testutils.annotations.Plugins;
61import org.openstreetmap.josm.testutils.annotations.Projection;
62import org.openstreetmap.josm.tools.Logging;
63import org.openstreetmap.josm.tools.PlatformManager;
64import org.openstreetmap.josm.tools.Shortcut;
65import org.openstreetmap.josm.tools.bugreport.BugReportQueue;
66
67import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
68import mockit.Mock;
69import mockit.MockUp;
70
71/**
72 * Unit tests of {@link MainApplication} class.
73 */
74@HTTPS
75@Main
76@OsmApi(OsmApi.APIType.DEV)
77@Projection
78@Timeout(20)
79public class MainApplicationTest {
80 /**
81 * Make sure {@link MainApplication#contentPanePrivate} is initialized.
82 */
83 public static void initContentPane() {
84 // init every time to avoid "Keystroke %s is already assigned to %s"
85 MainApplication.contentPanePrivate = new JPanel(new BorderLayout());
86 }
87
88 /**
89 * Returns {@link MainApplication#contentPanePrivate} (not public).
90 * @return {@link MainApplication#contentPanePrivate}
91 */
92 public static JComponent getContentPane() {
93 return MainApplication.contentPanePrivate;
94 }
95
96 /**
97 * Make sure {@code MainApplication.mainPanel} is initialized.
98 * @param reAddListeners {@code true} to re-add listeners
99 */
100 public static void initMainPanel(boolean reAddListeners) {
101 if (MainApplication.mainPanel == null) {
102 MainApplication.mainPanel = new MainPanel(MainApplication.getLayerManager());
103 }
104 if (reAddListeners) {
105 MainApplication.mainPanel.reAddListeners();
106 }
107 }
108
109 /**
110 * Make sure {@code MainApplication.menu} is initialized.
111 */
112 public static void initMainMenu() {
113 MainApplication.menu = new MainMenu();
114 }
115
116 /**
117 * Make sure {@link MainApplication#toolbar} is initialized.
118 */
119 public static void initToolbar() {
120 // init every time to avoid "Registered toolbar action"
121 MainApplication.toolbar = new ToolbarPreferences();
122 }
123
124 @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING")
125 private void testShow(final String arg, String expected) throws InterruptedException, IOException {
126 PrintStream old = System.out;
127 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
128 System.setOut(new PrintStream(baos));
129 Thread t = new Thread(() -> MainApplication.main(new String[] {arg}));
130 t.start();
131 t.join();
132 System.out.flush();
133 assertEquals(expected, baos.toString(StandardCharsets.UTF_8).trim());
134 } finally {
135 System.setOut(old);
136 }
137 }
138
139 /**
140 * Test of {@link MainApplication#main} with argument {@code --version}.
141 * @throws Exception in case of error
142 */
143 @Test
144 void testShowVersion() throws Exception {
145 testShow("--version", Version.getInstance().getAgentString());
146 }
147
148 /**
149 * Test of {@link MainApplication#main} with argument {@code --help}.
150 * @throws Exception in case of error
151 */
152 @Test
153 void testShowHelp() throws Exception {
154 testShow("--help", MainApplication.getHelp().trim());
155 }
156
157 /**
158 * Unit test of {@link DownloadParamType#paramType} method.
159 */
160 @Test
161 void testParamType() {
162 assertEquals(DownloadParamType.bounds, DownloadParamType.paramType("48.000,16.000,48.001,16.001"));
163 assertEquals(DownloadParamType.fileName, DownloadParamType.paramType("data.osm"));
164 assertEquals(DownloadParamType.fileUrl, DownloadParamType.paramType("file:///home/foo/data.osm"));
165 assertEquals(DownloadParamType.fileUrl, DownloadParamType.paramType("file://C:\\Users\\foo\\data.osm"));
166 assertEquals(DownloadParamType.httpUrl, DownloadParamType.paramType("http://somewhere.com/data.osm"));
167 assertEquals(DownloadParamType.httpUrl, DownloadParamType.paramType("https://somewhere.com/data.osm"));
168 }
169
170 /**
171 * Test of {@link MainApplication#updateAndLoadEarlyPlugins} and {@link MainApplication#loadLatePlugins} methods.
172 * @throws PluginListParseException if an error occurs
173 */
174 @Plugins
175 @Test
176 void testUpdateAndLoadPlugins() throws PluginListParseException {
177 final String old = System.getProperty("josm.plugins");
178 try {
179 System.setProperty("josm.plugins", "buildings_tools,log4j");
180 SplashProgressMonitor monitor = new SplashProgressMonitor("foo", e -> {
181 // Do nothing
182 });
183 Collection<PluginInformation> plugins = MainApplication.updateAndLoadEarlyPlugins(null, monitor);
184 if (plugins.isEmpty()) {
185 PluginHandlerTestIT.downloadPlugins(Arrays.asList(
186 newPluginInformation("buildings_tools"),
187 newPluginInformation("log4j")));
188 plugins = MainApplication.updateAndLoadEarlyPlugins(null, monitor);
189 }
190 assertEquals(2, plugins.size());
191 assertNotNull(PluginHandler.getPlugin("log4j"));
192 assertNull(PluginHandler.getPlugin("buildings_tools"));
193 MainApplication.loadLatePlugins(null, monitor, plugins);
194 assertNotNull(PluginHandler.getPlugin("buildings_tools"));
195 } finally {
196 if (old != null) {
197 System.setProperty("josm.plugins", old);
198 } else {
199 System.clearProperty("josm.plugins");
200 }
201 }
202 }
203
204 /**
205 * Unit test of {@link MainApplication#setupUIManager}.
206 */
207 @Test
208 void testSetupUIManager() {
209 TestUtils.assumeWorkingJMockit();
210 assumeFalse(PlatformManager.isPlatformWindows() && "True".equals(System.getenv("APPVEYOR")));
211 assertDoesNotThrow(MainApplication::setupUIManager);
212 assertEquals(Config.getPref().get("laf", PlatformManager.getPlatform().getDefaultStyle()),
213 UIManager.getLookAndFeel().getClass().getCanonicalName());
214 try {
215 LafPreference.LAF.put(BadLaf.class.getName());
216 new PluginHandlerMock();
217 AtomicReference<Throwable> exceptionAtomicReference = new AtomicReference<>();
218 BugReportQueue.getInstance().setBugReportHandler((e, index) -> {
219 exceptionAtomicReference.set(e.getCause());
220 return BugReportQueue.SuppressionMode.NONE;
221 });
222 assertDoesNotThrow(MainApplication::setupUIManager);
223
224 /* We cannot sync on the worker or EDT threads since the bug report handler makes a new thread */
225 Awaitility.await().atMost(Durations.ONE_HUNDRED_MILLISECONDS)
226 .pollDelay(Durations.ONE_MILLISECOND)
227 .until(() -> !BugReportQueue.getInstance().exceptionHandlingInProgress());
228 assertNotNull(exceptionAtomicReference.get());
229 assertInstanceOf(UnsupportedOperationException.class, exceptionAtomicReference.get());
230 // The LAF only resets on restart, so don't bother checking that it switched back in UIManager
231 assertEquals(LafPreference.LAF.getDefaultValue(), LafPreference.LAF.get());
232 } finally {
233 BugReportQueue.getInstance().setBugReportHandler(BugReportQueue.FALLBACK_BUGREPORT_HANDLER);
234 // Make certain we reset the LAF
235 LafPreference.LAF.remove();
236 assertDoesNotThrow(MainApplication::setupUIManager);
237 assertEquals(Config.getPref().get("laf", PlatformManager.getPlatform().getDefaultStyle()),
238 UIManager.getLookAndFeel().getClass().getCanonicalName());
239 }
240 }
241
242 private static PluginInformation newPluginInformation(String plugin) throws PluginListParseException {
243 return PluginListParser.createInfo(plugin+".jar", "https://josm.openstreetmap.de/osmsvn/applications/editors/josm/dist/"+plugin+".jar",
244 new Attributes());
245 }
246
247 /**
248 * Unit test of {@link MainApplication#postConstructorProcessCmdLine} - empty case.
249 */
250 @Test
251 void testPostConstructorProcessCmdLineEmpty() {
252 // Check the method accepts no arguments
253 assertDoesNotThrow(() -> MainApplication.postConstructorProcessCmdLine(new ProgramArguments()));
254 }
255
256 private static void doTestPostConstructorProcessCmdLine(String download, String downloadGps, boolean gpx) {
257 assertNull(MainApplication.getLayerManager().getEditDataSet());
258 for (Future<?> f : MainApplication.postConstructorProcessCmdLine(new ProgramArguments("--download=" + download,
259 "--downloadgps=" + downloadGps,
260 "--selection=type: node"))) {
261 try {
262 f.get();
263 } catch (InterruptedException | ExecutionException e) {
264 Logging.error(e);
265 }
266 }
267 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
268 assertNotNull(ds);
269 assertFalse(ds.getSelected().isEmpty());
270 MainApplication.getLayerManager().removeLayer(MainApplication.getLayerManager().getEditLayer());
271 if (gpx) {
272 List<GpxLayer> gpxLayers = MainApplication.getLayerManager().getLayersOfType(GpxLayer.class);
273 assertEquals(1, gpxLayers.size());
274 MainApplication.getLayerManager().removeLayer(gpxLayers.iterator().next());
275 }
276 }
277
278 /**
279 * Unit test of {@link MainApplication#postConstructorProcessCmdLine} - nominal case with bounds.
280 * This test assumes the DEV API contains nodes around 0,0 and GPX tracks around London
281 */
282 @Test
283 void testPostConstructorProcessCmdLineBounds() {
284 doTestPostConstructorProcessCmdLine(
285 "-47.20,-126.75,-47.10,-126.65",
286 "51.35,-0.4,51.60,0.2", true);
287 }
288
289 /**
290 * Unit test of {@link MainApplication#postConstructorProcessCmdLine} - nominal case with http/https URLs.
291 * This test assumes the DEV API contains nodes around -47.15, -126.7 (R'lyeh) and GPX tracks around London
292 */
293 @Test
294 void testPostConstructorProcessCmdLineHttpUrl() {
295 doTestPostConstructorProcessCmdLine(
296 "https://api06.dev.openstreetmap.org/api/0.6/map?bbox=-126.75,-47.20,-126.65,-47.10",
297 "https://master.apis.dev.openstreetmap.org/api/0.6/trackpoints?bbox=-0.4,51.35,0.2,51.6&page=0", true);
298 }
299
300 /**
301 * Unit test of {@link MainApplication#postConstructorProcessCmdLine} - nominal case with file URLs.
302 * @throws MalformedURLException if an error occurs
303 */
304 @Test
305 void testPostConstructorProcessCmdLineFileUrl() throws MalformedURLException {
306 doTestPostConstructorProcessCmdLine(
307 Paths.get(TestUtils.getTestDataRoot() + "multipolygon.osm").toUri().toURL().toExternalForm(),
308 Paths.get(TestUtils.getTestDataRoot() + "minimal.gpx").toUri().toURL().toExternalForm(), false);
309 }
310
311 /**
312 * Unit test of {@link MainApplication#postConstructorProcessCmdLine} - nominal case with file names.
313 */
314 @Test
315 void testPostConstructorProcessCmdLineFilename() {
316 doTestPostConstructorProcessCmdLine(
317 Paths.get(TestUtils.getTestDataRoot() + "multipolygon.osm").toFile().getAbsolutePath(),
318 Paths.get(TestUtils.getTestDataRoot() + "minimal.gpx").toFile().getAbsolutePath(), false);
319 }
320
321 /**
322 * Unit test of {@link MainApplication#getRegisteredActionShortcut}.
323 */
324 @Test
325 void testGetRegisteredActionShortcut() {
326 Shortcut noKeystroke = Shortcut.registerShortcut("no", "keystroke", 0, 0);
327 assertNull(noKeystroke.getKeyStroke());
328 assertNull(MainApplication.getRegisteredActionShortcut(noKeystroke));
329 Shortcut noAction = Shortcut.registerShortcut("foo", "bar", KeyEvent.VK_AMPERSAND, Shortcut.SHIFT);
330 assertNotNull(noAction.getKeyStroke());
331 assertNull(MainApplication.getRegisteredActionShortcut(noAction));
332 JosmAction action = new OpenLocationAction();
333 assertEquals(action, MainApplication.getRegisteredActionShortcut(action.getShortcut()));
334 }
335
336 /**
337 * Unit test of {@link MainApplication#addMapFrameListener} and {@link MainApplication#removeMapFrameListener}.
338 */
339 @Test
340 void testMapFrameListener() {
341 MapFrameListener listener = (o, n) -> { };
342 assertTrue(MainApplication.addMapFrameListener(listener));
343 assertFalse(MainApplication.addMapFrameListener(null));
344 assertTrue(MainApplication.removeMapFrameListener(listener));
345 assertFalse(MainApplication.removeMapFrameListener(null));
346 }
347
348 /**
349 * Unit test of {@link DownloadParamType} enum.
350 */
351 @Test
352 void testEnumDownloadParamType() {
353 TestUtils.superficialEnumCodeCoverage(DownloadParamType.class);
354 }
355
356 /**
357 * This class exists to test a failure in non-default UI loading
358 */
359 public static class BadLaf extends MetalLookAndFeel {
360 @Override
361 public UIDefaults getDefaults() {
362 throw new UnsupportedOperationException("Test failure loading");
363 }
364 }
365
366 /**
367 * A mock class for returning a fake plugin class loader for {@link #testSetupUIManager()}
368 */
369 public static class PluginHandlerMock extends MockUp<PluginHandler> {
370 @Mock
371 public static Collection<PluginClassLoader> getPluginClassLoaders() {
372 return Collections.singleton(new PluginClassLoader(new URL[0], BadLaf.class.getClassLoader(), Collections.emptyList()));
373 }
374 }
375}
Note: See TracBrowser for help on using the repository browser.