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

Last change on this file since 13322 was 13322, checked in by Don-vip, 6 years ago

see #15767 - clear projection change listeners at the end of unit tests

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