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

Last change on this file since 18598 was 18598, checked in by taylor.smock, 3 years ago

FlatLaf uses properties files which occasionally have breaking changes

In the event that a properties file is misconfigured, FlatLaf will throw
an IllegalArgumentException, which causes JOSM to exit, which is not ideal,
since recovering is not intuitive.

This will roll back the LAF to the platform default, so people can start JOSM.
Which isn't ideal, but is better than not starting indefinitely.

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