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, 15 months 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.