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

Last change on this file since 12556 was 12556, checked in by Don-vip, 3 months ago

speedup RightAndLefthandTrafficTest

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