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

Last change on this file since 17360 was 16985, checked in by simon04, 4 years ago

fix #19736 - MapStatus displays non-localized SystemOfMeasurement name

File size: 28.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.testutils;
3
4import java.awt.Color;
5import java.awt.Window;
6import java.awt.event.WindowEvent;
7import java.io.ByteArrayInputStream;
8import java.io.File;
9import java.io.IOException;
10import java.io.PrintWriter;
11import java.io.StringWriter;
12import java.lang.annotation.Documented;
13import java.lang.annotation.ElementType;
14import java.lang.annotation.Retention;
15import java.lang.annotation.RetentionPolicy;
16import java.lang.annotation.Target;
17import java.nio.charset.StandardCharsets;
18import java.security.GeneralSecurityException;
19import java.text.MessageFormat;
20import java.util.Arrays;
21import java.util.Map;
22import java.util.TimeZone;
23import java.util.logging.Handler;
24
25import org.awaitility.Awaitility;
26import org.junit.jupiter.api.extension.AfterAllCallback;
27import org.junit.jupiter.api.extension.AfterEachCallback;
28import org.junit.jupiter.api.extension.BeforeAllCallback;
29import org.junit.jupiter.api.extension.BeforeEachCallback;
30import org.junit.jupiter.api.extension.ExtensionContext;
31import org.junit.rules.TemporaryFolder;
32import org.junit.rules.TestRule;
33import org.junit.runner.Description;
34import org.junit.runners.model.InitializationError;
35import org.junit.runners.model.Statement;
36import org.openstreetmap.josm.JOSMFixture;
37import org.openstreetmap.josm.TestUtils;
38import org.openstreetmap.josm.actions.DeleteAction;
39import org.openstreetmap.josm.command.DeleteCommand;
40import org.openstreetmap.josm.data.Preferences;
41import org.openstreetmap.josm.data.SystemOfMeasurement;
42import org.openstreetmap.josm.data.UserIdentityManager;
43import org.openstreetmap.josm.data.Version;
44import org.openstreetmap.josm.data.osm.User;
45import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
46import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
47import org.openstreetmap.josm.data.preferences.JosmUrls;
48import org.openstreetmap.josm.data.projection.ProjectionRegistry;
49import org.openstreetmap.josm.data.projection.Projections;
50import org.openstreetmap.josm.gui.MainApplication;
51import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
52import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
53import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreferenceTestIT;
54import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
55import org.openstreetmap.josm.gui.util.GuiHelper;
56import org.openstreetmap.josm.io.CertificateAmendment;
57import org.openstreetmap.josm.io.OsmApi;
58import org.openstreetmap.josm.io.OsmApiInitializationException;
59import org.openstreetmap.josm.io.OsmConnection;
60import org.openstreetmap.josm.io.OsmTransferCanceledException;
61import org.openstreetmap.josm.spi.preferences.Config;
62import org.openstreetmap.josm.spi.preferences.Setting;
63import org.openstreetmap.josm.testutils.mockers.EDTAssertionMocker;
64import org.openstreetmap.josm.testutils.mockers.WindowlessMapViewStateMocker;
65import org.openstreetmap.josm.testutils.mockers.WindowlessNavigatableComponentMocker;
66import org.openstreetmap.josm.tools.Http1Client;
67import org.openstreetmap.josm.tools.HttpClient;
68import org.openstreetmap.josm.tools.I18n;
69import org.openstreetmap.josm.tools.JosmRuntimeException;
70import org.openstreetmap.josm.tools.Logging;
71import org.openstreetmap.josm.tools.MemoryManagerTest;
72import org.openstreetmap.josm.tools.Territories;
73import org.openstreetmap.josm.tools.bugreport.ReportedException;
74import org.openstreetmap.josm.tools.date.DateUtils;
75
76import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
77
78/**
79 * This class runs a test in an environment that resembles the one used by the JOSM main application.
80 * <p>
81 * The environment is reset before every test. You can specify the components to which you need access using the methods of this class.
82 * For example, invoking {@link #preferences()} gives you access to the (default) preferences.
83 *
84 * @author Michael Zangl
85 */
86public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback {
87 private int timeout = isDebugMode() ? -1 : 10 * 1000;
88 private TemporaryFolder josmHome;
89 private boolean usePreferences = false;
90 private APIType useAPI = APIType.NONE;
91 private String i18n = null;
92 private TileSourceRule tileSourceRule;
93 private String assumeRevisionString;
94 private Version originalVersion;
95 private Runnable mapViewStateMockingRunnable;
96 private Runnable navigableComponentMockingRunnable;
97 private Runnable edtAssertionMockingRunnable;
98 private boolean useProjection;
99 private boolean useProjectionNadGrids;
100 private boolean commands;
101 private boolean allowMemoryManagerLeaks;
102 private boolean useMapStyles;
103 private boolean usePresets;
104 private boolean useHttps;
105 private boolean territories;
106 private boolean metric;
107 private boolean main;
108 /**
109 * This boolean is only used to indicate if JUnit5 is used in a test. If it is,
110 * we must not call after in {@link JOSMTestRules.CreateJosmEnvironment#evaluate}.
111 * TODO: Remove JUnit4 as a whole sometime after 2021-01-01 (~6 month lead time for plugins)
112 */
113 private boolean junit5;
114
115 /**
116 * Disable the default timeout for this test. Use with care.
117 * @return this instance, for easy chaining
118 */
119 public JOSMTestRules noTimeout() {
120 timeout = -1;
121 return this;
122 }
123
124 /**
125 * Set a timeout for all tests in this class. Local method timeouts may only reduce this timeout.
126 * @param millis The timeout duration in milliseconds.
127 * @return this instance, for easy chaining
128 */
129 public JOSMTestRules timeout(int millis) {
130 timeout = isDebugMode() ? -1 : millis;
131 return this;
132 }
133
134 /**
135 * Enable the use of default preferences.
136 * @return this instance, for easy chaining
137 */
138 public JOSMTestRules preferences() {
139 josmHome();
140 usePreferences = true;
141 return this;
142 }
143
144 /**
145 * Set JOSM home to a valid, empty directory.
146 * @return this instance, for easy chaining
147 */
148 private JOSMTestRules josmHome() {
149 josmHome = new TemporaryFolder();
150 return this;
151 }
152
153 /**
154 * Enables the i18n module for this test in english.
155 * @return this instance, for easy chaining
156 */
157 public JOSMTestRules i18n() {
158 return i18n("en");
159 }
160
161 /**
162 * Enables the i18n module for this test.
163 * @param language The language to use.
164 * @return this instance, for easy chaining
165 */
166 public JOSMTestRules i18n(String language) {
167 i18n = language;
168 return this;
169 }
170
171 /**
172 * Mock this test's assumed JOSM version (as reported by {@link Version}).
173 * @param revisionProperties mock contents of JOSM's {@code REVISION} properties file
174 * @return this instance, for easy chaining
175 */
176 public JOSMTestRules assumeRevision(final String revisionProperties) {
177 this.assumeRevisionString = revisionProperties;
178 return this;
179 }
180
181 /**
182 * Enable the dev.openstreetmap.org API for this test.
183 * @return this instance, for easy chaining
184 */
185 public JOSMTestRules devAPI() {
186 preferences();
187 useAPI = APIType.DEV;
188 return this;
189 }
190
191 /**
192 * Use the {@link FakeOsmApi} for testing.
193 * @return this instance, for easy chaining
194 */
195 public JOSMTestRules fakeAPI() {
196 useAPI = APIType.FAKE;
197 return this;
198 }
199
200 /**
201 * Set up default projection (Mercator)
202 * @return this instance, for easy chaining
203 */
204 public JOSMTestRules projection() {
205 useProjection = true;
206 return this;
207 }
208
209 /**
210 * Set up loading of NTV2 grit shift files to support projections that need them.
211 * @return this instance, for easy chaining
212 */
213 public JOSMTestRules projectionNadGrids() {
214 useProjectionNadGrids = true;
215 return this;
216 }
217
218 /**
219 * Set up HTTPS certificates
220 * @return this instance, for easy chaining
221 */
222 public JOSMTestRules https() {
223 useHttps = true;
224 return this;
225 }
226
227 /**
228 * Allow the execution of commands using {@code UndoRedoHandler}
229 * @return this instance, for easy chaining
230 */
231 public JOSMTestRules commands() {
232 commands = true;
233 return this;
234 }
235
236 /**
237 * Allow the memory manager to contain items after execution of the test cases.
238 * @return this instance, for easy chaining
239 */
240 public JOSMTestRules memoryManagerLeaks() {
241 allowMemoryManagerLeaks = true;
242 return this;
243 }
244
245 /**
246 * Use map styles in this test.
247 * @return this instance, for easy chaining
248 * @since 11777
249 */
250 public JOSMTestRules mapStyles() {
251 preferences();
252 useMapStyles = true;
253 return this;
254 }
255
256 /**
257 * Use presets in this test.
258 * @return this instance, for easy chaining
259 * @since 12568
260 */
261 public JOSMTestRules presets() {
262 preferences();
263 usePresets = true;
264 return this;
265 }
266
267 /**
268 * Use boundaries dataset in this test.
269 * @return this instance, for easy chaining
270 * @since 12545
271 */
272 public JOSMTestRules territories() {
273 territories = true;
274 return this;
275 }
276
277 /**
278 * Use right and lefthand traffic dataset in this test.
279 * @return this instance, for easy chaining
280 * @since 12556
281 * @deprecated Use {@link #territories}
282 */
283 @Deprecated
284 public JOSMTestRules rlTraffic() {
285 territories();
286 return this;
287 }
288
289 /**
290 * Force metric measurement system.
291 * @return this instance, for easy chaining
292 * @since 15400
293 */
294 public JOSMTestRules metricSystem() {
295 metric = true;
296 return this;
297 }
298
299 /**
300 * Re-raise AssertionErrors thrown in the EDT where they would have normally been swallowed.
301 * @return this instance, for easy chaining
302 */
303 public JOSMTestRules assertionsInEDT() {
304 return this.assertionsInEDT(EDTAssertionMocker::new);
305 }
306
307 /**
308 * Re-raise AssertionErrors thrown in the EDT where they would have normally been swallowed.
309 * @param edtAssertionMockingRunnable Runnable for initializing this functionality
310 *
311 * @return this instance, for easy chaining
312 */
313 public JOSMTestRules assertionsInEDT(final Runnable edtAssertionMockingRunnable) {
314 this.edtAssertionMockingRunnable = edtAssertionMockingRunnable;
315 return this;
316 }
317
318 /**
319 * Replace imagery sources with a default set of mock tile sources
320 *
321 * @return this instance, for easy chaining
322 */
323 public JOSMTestRules fakeImagery() {
324 return this.fakeImagery(
325 new TileSourceRule(
326 true,
327 true,
328 true,
329 new TileSourceRule.ColorSource(Color.WHITE, "White Tiles", 256),
330 new TileSourceRule.ColorSource(Color.BLACK, "Black Tiles", 256),
331 new TileSourceRule.ColorSource(Color.MAGENTA, "Magenta Tiles", 256),
332 new TileSourceRule.ColorSource(Color.GREEN, "Green Tiles", 256)
333 )
334 );
335 }
336
337 /**
338 * Replace imagery sources with those from specific mock tile server setup
339 * @param tileSourceRule Tile source rule
340 *
341 * @return this instance, for easy chaining
342 */
343 public JOSMTestRules fakeImagery(TileSourceRule tileSourceRule) {
344 this.preferences();
345 this.tileSourceRule = tileSourceRule;
346 return this;
347 }
348
349 /**
350 * Use the {@code Main#main}, {@code Main.contentPanePrivate}, {@code Main.mainPanel},
351 * global variables in this test.
352 * @return this instance, for easy chaining
353 * @since 12557
354 */
355 public JOSMTestRules main() {
356 return this.main(
357 WindowlessMapViewStateMocker::new,
358 WindowlessNavigatableComponentMocker::new
359 );
360 }
361
362 /**
363 * Use the {@code Main#main}, {@code Main.contentPanePrivate}, {@code Main.mainPanel},
364 * global variables in this test.
365 * @param mapViewStateMockingRunnable Runnable to use for mocking out any required parts of
366 * {@link org.openstreetmap.josm.gui.MapViewState}, null to skip.
367 * @param navigableComponentMockingRunnable Runnable to use for mocking out any required parts
368 * of {@link org.openstreetmap.josm.gui.NavigatableComponent}, null to skip.
369 *
370 * @return this instance, for easy chaining
371 */
372 public JOSMTestRules main(
373 final Runnable mapViewStateMockingRunnable,
374 final Runnable navigableComponentMockingRunnable
375 ) {
376 this.main = true;
377 this.mapViewStateMockingRunnable = mapViewStateMockingRunnable;
378 this.navigableComponentMockingRunnable = navigableComponentMockingRunnable;
379 return this;
380 }
381
382 /**
383 * Must be called if test run with Junit parameters
384 * @return this instance, for easy chaining
385 */
386 public JOSMTestRules parameters() {
387 try {
388 apply(new Statement() {
389 @Override
390 public void evaluate() throws Throwable {
391 // Do nothing. Hack needed because @Parameters are computed before anything else
392 }
393 }, Description.createSuiteDescription(ImageryPreferenceTestIT.class)).evaluate();
394 } catch (Throwable e) {
395 Logging.error(e);
396 }
397 return this;
398 }
399
400 private static class MockVersion extends Version {
401 MockVersion(final String propertiesString) {
402 super.initFromRevisionInfo(
403 new ByteArrayInputStream(propertiesString.getBytes(StandardCharsets.UTF_8))
404 );
405 }
406 }
407
408 @Override
409 public Statement apply(Statement base, Description description) {
410 // First process any Override* annotations for per-test overrides.
411 // The following only work because "option" methods modify JOSMTestRules in-place
412 final OverrideAssumeRevision overrideAssumeRevision = description.getAnnotation(OverrideAssumeRevision.class);
413 if (overrideAssumeRevision != null) {
414 this.assumeRevision(overrideAssumeRevision.value());
415 }
416 final OverrideTimeout overrideTimeout = description.getAnnotation(OverrideTimeout.class);
417 if (overrideTimeout != null) {
418 this.timeout(overrideTimeout.value());
419 }
420 Statement statement = base;
421 // counter-intuitively, Statements which need to have their setup routines performed *after* another one need to
422 // be added into the chain *before* that one, so that it ends up on the "inside".
423 if (timeout > 0) {
424 // TODO: new DisableOnDebug(timeout)
425 statement = new FailOnTimeoutStatement(statement, timeout);
426 }
427
428 // this half of TileSourceRule's initialization must happen after josm is set up
429 if (this.tileSourceRule != null) {
430 statement = this.tileSourceRule.applyRegisterLayers(statement, description);
431 }
432
433 statement = new CreateJosmEnvironment(statement);
434 if (josmHome != null) {
435 statement = josmHome.apply(statement, description);
436 }
437
438 // run mock tile server as the outermost Statement (started first) so it can hopefully be initializing in
439 // parallel with other setup
440 if (this.tileSourceRule != null) {
441 statement = this.tileSourceRule.applyRunServer(statement, description);
442 }
443 return statement;
444 }
445
446 @Override
447 public void beforeEach(ExtensionContext context) throws Exception {
448 this.junit5 = true;
449 Statement temporaryStatement = new Statement() {
450 @Override
451 public void evaluate() throws Throwable {
452 // do nothing
453 }
454 };
455 try {
456 this.apply(temporaryStatement,
457 Description.createTestDescription(this.getClass(), "JOSMTestRules JUnit5 Compatibility"))
458 .evaluate();
459 } catch (Throwable e) {
460 throw new Exception(e);
461 }
462 }
463
464 @Override
465 public void afterEach(ExtensionContext context) throws Exception {
466 after();
467 }
468
469 @Override
470 public void beforeAll(ExtensionContext context) throws Exception {
471 beforeEach(context);
472 }
473
474 @Override
475 public void afterAll(ExtensionContext context) throws Exception {
476 afterEach(context);
477 }
478
479 /**
480 * Set up before running a test
481 * @throws InitializationError If an error occurred while creating the required environment.
482 * @throws ReflectiveOperationException if a reflective access error occurs
483 */
484 protected void before() throws InitializationError, ReflectiveOperationException {
485 cleanUpFromJosmFixture();
486
487 if (this.assumeRevisionString != null) {
488 this.originalVersion = Version.getInstance();
489 final Version replacementVersion = new MockVersion(this.assumeRevisionString);
490 TestUtils.setPrivateStaticField(Version.class, "instance", replacementVersion);
491 }
492
493 // Add JOSM home
494 if (josmHome != null) {
495 try {
496 File home = josmHome.newFolder();
497 System.setProperty("josm.home", home.getAbsolutePath());
498 JosmBaseDirectories.getInstance().clearMemos();
499 } catch (IOException e) {
500 throw new InitializationError(e);
501 }
502 }
503
504 Preferences pref = Preferences.main();
505 Config.setPreferencesInstance(pref);
506 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance());
507 Config.setUrlsProvider(JosmUrls.getInstance());
508 // All tests use the same timezone.
509 TimeZone.setDefault(DateUtils.UTC);
510
511 // Force log handlers to reacquire reference to (junit's fake) stdout/stderr
512 for (Handler handler : Logging.getLogger().getHandlers()) {
513 if (handler instanceof Logging.ReacquiringConsoleHandler) {
514 handler.flush();
515 ((Logging.ReacquiringConsoleHandler) handler).reacquireOutputStream();
516 }
517 }
518 // Set log level to info
519 Logging.setLogLevel(Logging.LEVEL_INFO);
520
521 // Assume anonymous user
522 UserIdentityManager.getInstance().setAnonymous();
523 User.clearUserMap();
524 // Setup callbacks
525 DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback);
526 OsmConnection.setOAuthAccessTokenFetcher(OAuthAuthorizationWizard::obtainAccessToken);
527 HttpClient.setFactory(Http1Client::new);
528
529 // Set up i18n
530 if (i18n != null) {
531 I18n.set(i18n);
532 }
533
534 // Add preferences
535 if (usePreferences) {
536 @SuppressWarnings("unchecked")
537 final Map<String, Setting<?>> defaultsMap = (Map<String, Setting<?>>) TestUtils.getPrivateField(pref, "defaultsMap");
538 defaultsMap.clear();
539 pref.resetToInitialState();
540 pref.enableSaveOnPut(false);
541 // No pref init -> that would only create the preferences file.
542 // We force the use of a wrong API server, just in case anyone attempts an upload
543 Config.getPref().put("osm-server.url", "http://invalid");
544 }
545
546 // Make sure we're using the metric system
547 if (metric) {
548 SystemOfMeasurement.setSystemOfMeasurement(SystemOfMeasurement.METRIC);
549 }
550
551 if (useHttps) {
552 try {
553 CertificateAmendment.addMissingCertificates();
554 } catch (IOException | GeneralSecurityException ex) {
555 throw new JosmRuntimeException(ex);
556 }
557 }
558
559 if (useProjection) {
560 ProjectionRegistry.setProjection(Projections.getProjectionByCode("EPSG:3857")); // Mercator
561 }
562
563 if (useProjectionNadGrids) {
564 MainApplication.setupNadGridSources();
565 }
566
567 // Set API
568 if (useAPI == APIType.DEV) {
569 Config.getPref().put("osm-server.url", "https://api06.dev.openstreetmap.org/api");
570 } else if (useAPI == APIType.FAKE) {
571 FakeOsmApi api = FakeOsmApi.getInstance();
572 Config.getPref().put("osm-server.url", api.getServerUrl());
573 }
574
575 // Initialize API
576 if (useAPI != APIType.NONE) {
577 try {
578 OsmApi.getOsmApi().initialize(null);
579 } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
580 throw new InitializationError(e);
581 }
582 }
583
584 if (useMapStyles) {
585 // Reset the map paint styles.
586 MapPaintStyles.readFromPreferences();
587 }
588
589 if (usePresets) {
590 // Reset the presets.
591 TaggingPresets.readFromPreferences();
592 }
593
594 if (territories) {
595 Territories.initializeInternalData();
596 }
597
598 if (this.edtAssertionMockingRunnable != null) {
599 this.edtAssertionMockingRunnable.run();
600 }
601
602 if (commands) {
603 // TODO: Implement a more selective version of this once Main is restructured.
604 JOSMFixture.createUnitTestFixture().init(true);
605 } else {
606 if (main) {
607 // apply mockers to MapViewState and NavigableComponent whether we're headless or not
608 // as we generally don't create the josm main window even in non-headless mode.
609 if (this.mapViewStateMockingRunnable != null) {
610 this.mapViewStateMockingRunnable.run();
611 }
612 if (this.navigableComponentMockingRunnable != null) {
613 this.navigableComponentMockingRunnable.run();
614 }
615
616 new MainApplication();
617 JOSMFixture.initContentPane();
618 JOSMFixture.initMainPanel(true);
619 JOSMFixture.initToolbar();
620 JOSMFixture.initMainMenu();
621 }
622 }
623 }
624
625 /**
626 * Clean up what test not using these test rules may have broken.
627 */
628 @SuppressFBWarnings("DM_GC")
629 private void cleanUpFromJosmFixture() {
630 MemoryManagerTest.resetState(true);
631 cleanLayerEnvironment();
632 Preferences.main().resetToInitialState();
633 System.gc();
634 }
635
636 /**
637 * Cleans the Layer manager and the SelectionEventManager.
638 * You don't need to call this during tests, the test environment will do it for you.
639 * @since 12070
640 */
641 public static void cleanLayerEnvironment() {
642 // Get the instance before cleaning - this ensures that it is initialized.
643 SelectionEventManager eventManager = SelectionEventManager.getInstance();
644 MainApplication.getLayerManager().resetState();
645 eventManager.resetState();
646 }
647
648 /**
649 * @return TileSourceRule which is automatically started by this rule
650 */
651 public TileSourceRule getTileSourceRule() {
652 return this.tileSourceRule;
653 }
654
655 /**
656 * Clean up after running a test
657 * @throws ReflectiveOperationException if a reflective access error occurs
658 */
659 @SuppressFBWarnings("DM_GC")
660 protected void after() throws ReflectiveOperationException {
661 // Sync AWT Thread
662 GuiHelper.runInEDTAndWait(() -> { });
663 // Sync worker thread
664 final boolean[] queueEmpty = {false};
665 MainApplication.worker.submit(() -> queueEmpty[0] = true);
666 Awaitility.await().forever().until(() -> queueEmpty[0]);
667 // Remove all layers
668 cleanLayerEnvironment();
669 MemoryManagerTest.resetState(allowMemoryManagerLeaks);
670
671 // TODO: Remove global listeners and other global state.
672 ProjectionRegistry.clearProjectionChangeListeners();
673 Preferences.main().resetToInitialState();
674
675 if (this.assumeRevisionString != null && this.originalVersion != null) {
676 TestUtils.setPrivateStaticField(Version.class, "instance", this.originalVersion);
677 }
678
679 Window[] windows = Window.getWindows();
680 if (windows.length != 0) {
681 Logging.info(
682 "Attempting to close {0} windows left open by tests: {1}",
683 windows.length,
684 Arrays.toString(windows)
685 );
686 }
687 GuiHelper.runInEDTAndWait(() -> {
688 for (Window window : windows) {
689 window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING));
690 window.dispose();
691 }
692 });
693
694 // Parts of JOSM uses weak references - destroy them.
695 System.gc();
696 }
697
698 private final class CreateJosmEnvironment extends Statement {
699 private final Statement base;
700
701 private CreateJosmEnvironment(Statement base) {
702 this.base = base;
703 }
704
705 @Override
706 public void evaluate() throws Throwable {
707 before();
708 try {
709 base.evaluate();
710 } finally {
711 if (!junit5) {
712 after();
713 }
714 }
715 }
716 }
717
718 enum APIType {
719 NONE, FAKE, DEV
720 }
721
722 /**
723 * The junit timeout statement has problems when switching timezones. This one does not.
724 * @author Michael Zangl
725 */
726 private static class FailOnTimeoutStatement extends Statement {
727
728 private final int timeout;
729 private final Statement original;
730
731 FailOnTimeoutStatement(Statement original, int timeout) {
732 this.original = original;
733 this.timeout = timeout;
734 }
735
736 @Override
737 public void evaluate() throws Throwable {
738 TimeoutThread thread = new TimeoutThread(original);
739 thread.setDaemon(true);
740 thread.start();
741 thread.join(timeout);
742 thread.interrupt();
743 if (!thread.isDone) {
744 Throwable exception = thread.getExecutionException();
745 if (exception != null) {
746 throw exception;
747 } else {
748 if (Logging.isLoggingEnabled(Logging.LEVEL_DEBUG)) {
749 // i.e. skip expensive formatting of stack trace if it won't be shown
750 final StringWriter sw = new StringWriter();
751 new ReportedException(exception).printReportThreadsTo(new PrintWriter(sw));
752 Logging.debug("Thread state at timeout: {0}", sw);
753 }
754 throw new Exception(MessageFormat.format("Test timed out after {0}ms", timeout));
755 }
756 }
757 }
758 }
759
760 private static final class TimeoutThread extends Thread {
761 public boolean isDone;
762 private final Statement original;
763 private Throwable exceptionCaught;
764
765 private TimeoutThread(Statement original) {
766 super("Timeout runner");
767 this.original = original;
768 }
769
770 public Throwable getExecutionException() {
771 return exceptionCaught;
772 }
773
774 @Override
775 public void run() {
776 try {
777 original.evaluate();
778 isDone = true;
779 } catch (Throwable e) {
780 exceptionCaught = e;
781 }
782 }
783 }
784
785 private boolean isDebugMode() {
786 return java.lang.management.ManagementFactory.getRuntimeMXBean().
787 getInputArguments().toString().indexOf("-agentlib:jdwp") > 0;
788 }
789
790 /**
791 * Override this test's assumed JOSM version (as reported by {@link Version}).
792 * @see JOSMTestRules#assumeRevision(String)
793 */
794 @Documented
795 @Retention(RetentionPolicy.RUNTIME)
796 @Target(ElementType.METHOD)
797 public @interface OverrideAssumeRevision {
798 /**
799 * Returns overridden assumed JOSM version.
800 * @return overridden assumed JOSM version
801 */
802 String value();
803 }
804
805 /**
806 * Override this test's timeout.
807 * @see JOSMTestRules#timeout(int)
808 */
809 @Documented
810 @Retention(RetentionPolicy.RUNTIME)
811 @Target(ElementType.METHOD)
812 public @interface OverrideTimeout {
813 /**
814 * Returns overridden timeout value.
815 * @return overridden timeout value
816 */
817 int value();
818 }
819}
Note: See TracBrowser for help on using the repository browser.