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

Last change on this file since 12557 was 12557, checked in by Don-vip, 2 weeks ago

see #15102 - first batch of HTTP unit tests mocking, using WireMock 2.7.1

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