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

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

see #15229 - use Config in tests

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