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

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

see #15229 - see #15182 - fix unit tests

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