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