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