source: josm/trunk/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java@ 13021

Last change on this file since 13021 was 13021, checked in by bastiK, 7 years ago

make Preferences more generic (see #15229, closes #15451)

  • extract base directories
  • move updateSystemProperties() as it is specific to JOSM-core as a "prefefences client"
File size: 15.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.testutils;
3
4import java.io.File;
5import java.io.IOException;
6import java.security.GeneralSecurityException;
7import java.text.MessageFormat;
8import java.util.TimeZone;
9
10import org.junit.rules.TemporaryFolder;
11import org.junit.rules.TestRule;
12import org.junit.runner.Description;
13import org.junit.runners.model.InitializationError;
14import org.junit.runners.model.Statement;
15import org.openstreetmap.josm.JOSMFixture;
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.actions.DeleteAction;
18import org.openstreetmap.josm.command.DeleteCommand;
19import org.openstreetmap.josm.data.UserIdentityManager;
20import org.openstreetmap.josm.data.osm.User;
21import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
22import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
23import org.openstreetmap.josm.data.projection.Projections;
24import org.openstreetmap.josm.gui.MainApplication;
25import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
26import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
27import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
28import org.openstreetmap.josm.gui.util.GuiHelper;
29import org.openstreetmap.josm.io.CertificateAmendment;
30import org.openstreetmap.josm.io.OsmApi;
31import org.openstreetmap.josm.io.OsmApiInitializationException;
32import org.openstreetmap.josm.io.OsmConnection;
33import org.openstreetmap.josm.io.OsmTransferCanceledException;
34import org.openstreetmap.josm.spi.preferences.Config;
35import org.openstreetmap.josm.tools.I18n;
36import org.openstreetmap.josm.tools.JosmRuntimeException;
37import org.openstreetmap.josm.tools.Logging;
38import org.openstreetmap.josm.tools.MemoryManagerTest;
39import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
40import org.openstreetmap.josm.tools.Territories;
41import org.openstreetmap.josm.tools.date.DateUtils;
42
43import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44
45/**
46 * This class runs a test in an environment that resembles the one used by the JOSM main application.
47 * <p>
48 * The environment is reset before every test. You can specify the components to which you need access using the methods of this class.
49 * For example, invoking {@link #preferences()} gives you access to the (default) preferences.
50 *
51 * @author Michael Zangl
52 */
53public class JOSMTestRules implements TestRule {
54 private int timeout = isDebugMode() ? -1 : 10 * 1000;
55 private TemporaryFolder josmHome;
56 private boolean usePreferences = false;
57 private APIType useAPI = APIType.NONE;
58 private String i18n = null;
59 private boolean platform;
60 private boolean useProjection;
61 private boolean useProjectionNadGrids;
62 private boolean commands;
63 private boolean allowMemoryManagerLeaks;
64 private boolean useMapStyles;
65 private boolean usePresets;
66 private boolean useHttps;
67 private boolean territories;
68 private boolean rlTraffic;
69 private boolean main;
70
71 /**
72 * Disable the default timeout for this test. Use with care.
73 * @return this instance, for easy chaining
74 */
75 public JOSMTestRules noTimeout() {
76 timeout = -1;
77 return this;
78 }
79
80 /**
81 * Set a timeout for all tests in this class. Local method timeouts may only reduce this timeout.
82 * @param millis The timeout duration in milliseconds.
83 * @return this instance, for easy chaining
84 */
85 public JOSMTestRules timeout(int millis) {
86 timeout = isDebugMode() ? -1 : millis;
87 return this;
88 }
89
90 /**
91 * Enable the use of default preferences.
92 * @return this instance, for easy chaining
93 */
94 public JOSMTestRules preferences() {
95 josmHome();
96 usePreferences = true;
97 return this;
98 }
99
100 /**
101 * Set JOSM home to a valid, empty directory.
102 * @return this instance, for easy chaining
103 */
104 private JOSMTestRules josmHome() {
105 josmHome = new TemporaryFolder();
106 return this;
107 }
108
109 /**
110 * Enables the i18n module for this test in english.
111 * @return this instance, for easy chaining
112 */
113 public JOSMTestRules i18n() {
114 return i18n("en");
115 }
116
117 /**
118 * Enables the i18n module for this test.
119 * @param language The language to use.
120 * @return this instance, for easy chaining
121 */
122 public JOSMTestRules i18n(String language) {
123 i18n = language;
124 return this;
125 }
126
127 /**
128 * Enable {@link Main#platform} global variable.
129 * @return this instance, for easy chaining
130 */
131 public JOSMTestRules platform() {
132 platform = true;
133 return this;
134 }
135
136 /**
137 * Enable the dev.openstreetmap.org API for this test.
138 * @return this instance, for easy chaining
139 */
140 public JOSMTestRules devAPI() {
141 preferences();
142 useAPI = APIType.DEV;
143 return this;
144 }
145
146 /**
147 * Use the {@link FakeOsmApi} for testing.
148 * @return this instance, for easy chaining
149 */
150 public JOSMTestRules fakeAPI() {
151 useAPI = APIType.FAKE;
152 return this;
153 }
154
155 /**
156 * Set up default projection (Mercator)
157 * @return this instance, for easy chaining
158 */
159 public JOSMTestRules projection() {
160 useProjection = true;
161 return this;
162 }
163
164 /**
165 * Set up loading of NTV2 grit shift files to support projections that need them.
166 * @return this instance, for easy chaining
167 */
168 public JOSMTestRules projectionNadGrids() {
169 useProjectionNadGrids = true;
170 return this;
171 }
172
173 /**
174 * Set up HTTPS certificates
175 * @return this instance, for easy chaining
176 */
177 public JOSMTestRules https() {
178 useHttps = true;
179 platform = true;
180 return this;
181 }
182
183 /**
184 * Allow the execution of commands using {@link Main#undoRedo}
185 * @return this instance, for easy chaining
186 */
187 public JOSMTestRules commands() {
188 commands = true;
189 return this;
190 }
191
192 /**
193 * Allow the memory manager to contain items after execution of the test cases.
194 * @return this instance, for easy chaining
195 */
196 public JOSMTestRules memoryManagerLeaks() {
197 allowMemoryManagerLeaks = true;
198 return this;
199 }
200
201 /**
202 * Use map styles in this test.
203 * @return this instance, for easy chaining
204 * @since 11777
205 */
206 public JOSMTestRules mapStyles() {
207 preferences();
208 useMapStyles = true;
209 return this;
210 }
211
212 /**
213 * Use presets in this test.
214 * @return this instance, for easy chaining
215 * @since 12568
216 */
217 public JOSMTestRules presets() {
218 preferences();
219 usePresets = true;
220 return this;
221 }
222
223 /**
224 * Use boundaries dataset in this test.
225 * @return this instance, for easy chaining
226 * @since 12545
227 */
228 public JOSMTestRules territories() {
229 territories = true;
230 return this;
231 }
232
233 /**
234 * Use right and lefthand traffic dataset in this test.
235 * @return this instance, for easy chaining
236 * @since 12556
237 */
238 public JOSMTestRules rlTraffic() {
239 territories();
240 rlTraffic = true;
241 return this;
242 }
243
244 /**
245 * Use the {@link Main#main}, {@code Main.contentPanePrivate}, {@code Main.mainPanel},
246 * {@link Main#menu}, {@link Main#toolbar} global variables in this test.
247 * @return this instance, for easy chaining
248 * @since 12557
249 */
250 public JOSMTestRules main() {
251 platform();
252 main = true;
253 return this;
254 }
255
256 @Override
257 public Statement apply(Statement base, Description description) {
258 Statement statement = base;
259 if (timeout > 0) {
260 // TODO: new DisableOnDebug(timeout)
261 statement = new FailOnTimeoutStatement(statement, timeout);
262 }
263 statement = new CreateJosmEnvironment(statement);
264 if (josmHome != null) {
265 statement = josmHome.apply(statement, description);
266 }
267 return statement;
268 }
269
270 /**
271 * Set up before running a test
272 * @throws InitializationError If an error occured while creating the required environment.
273 */
274 protected void before() throws InitializationError {
275 // Tests are running headless by default.
276 System.setProperty("java.awt.headless", "true");
277
278 cleanUpFromJosmFixture();
279
280 Config.setPreferencesInstance(Main.pref);
281 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance());
282 // All tests use the same timezone.
283 TimeZone.setDefault(DateUtils.UTC);
284 // Set log level to info
285 Logging.setLogLevel(Logging.LEVEL_INFO);
286 // Assume anonymous user
287 UserIdentityManager.getInstance().setAnonymous();
288 User.clearUserMap();
289 // Setup callbacks
290 DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback);
291 OsmConnection.setOAuthAccessTokenFetcher(OAuthAuthorizationWizard::obtainAccessToken);
292
293 // Set up i18n
294 if (i18n != null) {
295 I18n.set(i18n);
296 }
297
298 // Add JOSM home
299 if (josmHome != null) {
300 try {
301 File home = josmHome.newFolder();
302 System.setProperty("josm.home", home.getAbsolutePath());
303 } catch (IOException e) {
304 throw new InitializationError(e);
305 }
306 }
307
308 // Add preferences
309 if (usePreferences) {
310 Main.pref.resetToInitialState();
311 Main.pref.enableSaveOnPut(false);
312 // No pref init -> that would only create the preferences file.
313 // We force the use of a wrong API server, just in case anyone attempts an upload
314 Config.getPref().put("osm-server.url", "http://invalid");
315 }
316
317 // Set Platform
318 if (platform) {
319 Main.determinePlatformHook();
320 }
321
322 if (useHttps) {
323 try {
324 CertificateAmendment.addMissingCertificates();
325 } catch (IOException | GeneralSecurityException ex) {
326 throw new JosmRuntimeException(ex);
327 }
328 }
329
330 if (useProjection) {
331 Main.setProjection(Projections.getProjectionByCode("EPSG:3857")); // Mercator
332 }
333
334 if (useProjectionNadGrids) {
335 MainApplication.setupNadGridSources();
336 }
337
338 // Set API
339 if (useAPI == APIType.DEV) {
340 Config.getPref().put("osm-server.url", "http://api06.dev.openstreetmap.org/api");
341 } else if (useAPI == APIType.FAKE) {
342 FakeOsmApi api = FakeOsmApi.getInstance();
343 Config.getPref().put("osm-server.url", api.getServerUrl());
344 }
345
346 // Initialize API
347 if (useAPI != APIType.NONE) {
348 try {
349 OsmApi.getOsmApi().initialize(null);
350 } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
351 throw new InitializationError(e);
352 }
353 }
354
355 if (useMapStyles) {
356 // Reset the map paint styles.
357 MapPaintStyles.readFromPreferences();
358 }
359
360 if (usePresets) {
361 // Reset the presets.
362 TaggingPresets.readFromPreferences();
363 }
364
365 if (territories) {
366 Territories.initialize();
367 }
368
369 if (rlTraffic) {
370 RightAndLefthandTraffic.initialize();
371 }
372
373 if (commands) {
374 // TODO: Implement a more selective version of this once Main is restructured.
375 JOSMFixture.createUnitTestFixture().init(true);
376 } else {
377 if (main) {
378 new MainApplication();
379 JOSMFixture.initContentPane();
380 JOSMFixture.initMainPanel(true);
381 JOSMFixture.initToolbar();
382 JOSMFixture.initMainMenu();
383 }
384 }
385 }
386
387 /**
388 * Clean up what test not using these test rules may have broken.
389 */
390 @SuppressFBWarnings("DM_GC")
391 private void cleanUpFromJosmFixture() {
392 MemoryManagerTest.resetState(true);
393 cleanLayerEnvironment();
394 Main.pref.resetToInitialState();
395 Main.platform = null;
396 System.gc();
397 }
398
399 /**
400 * Cleans the Layer manager and the SelectionEventManager.
401 * You don't need to call this during tests, the test environment will do it for you.
402 * @since 12070
403 */
404 public static void cleanLayerEnvironment() {
405 // Get the instance before cleaning - this ensures that it is initialized.
406 SelectionEventManager eventManager = SelectionEventManager.getInstance();
407 MainApplication.getLayerManager().resetState();
408 eventManager.resetState();
409 }
410
411 /**
412 * Clean up after running a test
413 */
414 @SuppressFBWarnings("DM_GC")
415 protected void after() {
416 // Sync AWT Thread
417 GuiHelper.runInEDTAndWait(new Runnable() {
418 @Override
419 public void run() {
420 }
421 });
422 // Remove all layers
423 cleanLayerEnvironment();
424 MemoryManagerTest.resetState(allowMemoryManagerLeaks);
425
426 // TODO: Remove global listeners and other global state.
427 Main.pref.resetToInitialState();
428 Main.platform = null;
429 // Parts of JOSM uses weak references - destroy them.
430 System.gc();
431 }
432
433 private final class CreateJosmEnvironment extends Statement {
434 private final Statement base;
435
436 private CreateJosmEnvironment(Statement base) {
437 this.base = base;
438 }
439
440 @Override
441 public void evaluate() throws Throwable {
442 before();
443 try {
444 base.evaluate();
445 } finally {
446 after();
447 }
448 }
449 }
450
451 enum APIType {
452 NONE, FAKE, DEV
453 }
454
455 /**
456 * The junit timeout statement has problems when switchting timezones. This one does not.
457 * @author Michael Zangl
458 */
459 private static class FailOnTimeoutStatement extends Statement {
460
461 private int timeout;
462 private Statement original;
463
464 FailOnTimeoutStatement(Statement original, int timeout) {
465 this.original = original;
466 this.timeout = timeout;
467 }
468
469 @Override
470 public void evaluate() throws Throwable {
471 TimeoutThread thread = new TimeoutThread(original);
472 thread.setDaemon(true);
473 thread.start();
474 thread.join(timeout);
475 thread.interrupt();
476 if (!thread.isDone) {
477 Throwable exception = thread.getExecutionException();
478 if (exception != null) {
479 throw exception;
480 } else {
481 throw new Exception(MessageFormat.format("Test timed out after {0}ms", timeout));
482 }
483 }
484 }
485 }
486
487 private static final class TimeoutThread extends Thread {
488 public boolean isDone;
489 private Statement original;
490 private Throwable exceptionCaught;
491
492 private TimeoutThread(Statement original) {
493 super("Timeout runner");
494 this.original = original;
495 }
496
497 public Throwable getExecutionException() {
498 return exceptionCaught;
499 }
500
501 @Override
502 public void run() {
503 try {
504 original.evaluate();
505 isDone = true;
506 } catch (Throwable e) {
507 exceptionCaught = e;
508 }
509 }
510 }
511
512 private boolean isDebugMode() {
513 return java.lang.management.ManagementFactory.getRuntimeMXBean().
514 getInputArguments().toString().indexOf("-agentlib:jdwp") > 0;
515 }
516}
Note: See TracBrowser for help on using the repository browser.