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

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

see #15229 - see #15182 - fix unit tests

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