Ticket #16567: 16567.separate_all_each.1.patch
| File 16567.separate_all_each.1.patch, 21.5 KB (added by , 5 years ago) |
|---|
-
test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.testutils; 3 3 4 import static org.junit.jupiter.api.Assertions.fail; 5 4 6 import java.awt.Color; 5 7 import java.awt.Window; 6 8 import java.awt.event.WindowEvent; 7 9 import java.io.ByteArrayInputStream; 8 import java.io.File;9 10 import java.io.IOException; 10 11 import java.io.PrintWriter; 11 12 import java.io.StringWriter; 13 import java.lang.annotation.Annotation; 12 14 import java.lang.annotation.Documented; 13 15 import java.lang.annotation.ElementType; 14 16 import java.lang.annotation.Retention; 15 17 import java.lang.annotation.RetentionPolicy; 16 18 import java.lang.annotation.Target; 19 import java.lang.reflect.AnnotatedElement; 17 20 import java.nio.charset.StandardCharsets; 21 import java.nio.file.Files; 22 import java.nio.file.Path; 18 23 import java.security.GeneralSecurityException; 19 24 import java.text.MessageFormat; 20 25 import java.util.Arrays; 21 26 import java.util.Map; 22 27 import java.util.TimeZone; 28 import java.util.UUID; 23 29 import java.util.logging.Handler; 24 30 25 31 import org.awaitility.Awaitility; … … 27 33 import org.junit.jupiter.api.extension.AfterEachCallback; 28 34 import org.junit.jupiter.api.extension.BeforeAllCallback; 29 35 import org.junit.jupiter.api.extension.BeforeEachCallback; 36 import org.junit.jupiter.api.extension.ExtendWith; 30 37 import org.junit.jupiter.api.extension.ExtensionContext; 38 import org.junit.jupiter.api.extension.ExtensionContext.Store; 31 39 import org.junit.rules.TemporaryFolder; 32 40 import org.junit.rules.TestRule; 33 41 import org.junit.runner.Description; … … 85 93 */ 86 94 public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback { 87 95 private int timeout = isDebugMode() ? -1 : 10 * 1000; 96 private final Path josmHomeTemp; 88 97 private TemporaryFolder josmHome; 89 98 private boolean usePreferences = false; 90 99 private APIType useAPI = APIType.NONE; … … 105 114 private boolean territories; 106 115 private boolean metric; 107 116 private boolean main; 117 108 118 /** 109 119 * This boolean is only used to indicate if JUnit5 is used in a test. If it is, 110 120 * we must not call after in {@link JOSMTestRules.CreateJosmEnvironment#evaluate}. … … 112 122 */ 113 123 private boolean junit5; 114 124 125 private FailOnTimeoutStatement failOnTimeout; 126 115 127 /** 128 * Create a new JOSMTestRules object 129 */ 130 public JOSMTestRules() { 131 Path tempHome = null; 132 try { 133 tempHome = Files.createTempDirectory("josm-test-rules-home"); 134 } catch (IOException e) { 135 fail(e); 136 } 137 this.josmHomeTemp = tempHome; 138 } 139 140 /** 116 141 * Disable the default timeout for this test. Use with care. 117 142 * @return this instance, for easy chaining 118 143 */ … … 446 471 @Override 447 472 public void beforeEach(ExtensionContext context) throws Exception { 448 473 this.junit5 = true; 474 475 // Temporary statement is necessary until we are able to remove JUnit4. 449 476 Statement temporaryStatement = new Statement() { 450 477 @Override 451 478 public void evaluate() throws Throwable { … … 452 479 // do nothing 453 480 } 454 481 }; 482 final Store store = context.getStore(ExtensionContext.Namespace.create(JOSMTestRules.class)); 483 // First process any Override* annotations for per-test overrides. 484 // The following only work because "option" methods modify JOSMTestRules in-place 485 final String tempRevision = store.getOrDefault(OverrideAssumeRevisionExtension.KEY, String.class, null); 486 if (tempRevision != null) { 487 this.assumeRevision(tempRevision); 488 } 489 490 final Integer tempTimeout = store.getOrDefault(OverrideTimeoutExtension.KEY, Integer.class, null); 491 if (tempTimeout != null) { 492 this.timeout(tempTimeout); 493 } 455 494 try { 456 this.apply(temporaryStatement, 457 Description.createTestDescription(this.getClass(), "JOSMTestRules JUnit5 Compatibility")) 458 .evaluate(); 495 beforeEachTest(); 496 497 if (this.timeout > 0) { 498 this.failOnTimeout = new FailOnTimeoutStatement(temporaryStatement, this.timeout); 499 this.failOnTimeout.beforeEach(context); 500 } 459 501 } catch (Throwable e) { 460 502 throw new Exception(e); 461 503 } … … 463 505 464 506 @Override 465 507 public void afterEach(ExtensionContext context) throws Exception { 508 if (this.failOnTimeout != null) { 509 this.failOnTimeout.afterEach(context); 510 } 466 511 after(); 467 512 } 468 513 469 514 @Override 470 515 public void beforeAll(ExtensionContext context) throws Exception { 471 beforeEach(context); 516 if (this.tileSourceRule != null) { 517 this.tileSourceRule.beforeAll(context); 518 } 519 this.beforeAllTests(); 472 520 } 473 521 474 522 @Override 475 523 public void afterAll(ExtensionContext context) throws Exception { 476 524 afterEach(context); 525 if (this.tileSourceRule != null) { 526 this.tileSourceRule.afterAll(context); 527 } 477 528 } 478 529 479 530 /** … … 481 532 * @throws InitializationError If an error occurred while creating the required environment. 482 533 * @throws ReflectiveOperationException if a reflective access error occurs 483 534 */ 484 protected void before() throws InitializationError, ReflectiveOperationException { 485 cleanUpFromJosmFixture(); 486 535 protected void beforeEachTest() throws InitializationError, ReflectiveOperationException { 487 536 if (this.assumeRevisionString != null) { 488 537 this.originalVersion = Version.getInstance(); 489 538 final Version replacementVersion = new MockVersion(this.assumeRevisionString); … … 490 539 TestUtils.setPrivateStaticField(Version.class, "instance", replacementVersion); 491 540 } 492 541 493 // Add JOSM home 494 if (josmHome != null) { 542 // Add JOSM home. Take advantage of the fact that josmHome is only initialized with preferences. 543 // And is always initialized with them. 544 if (this.josmHome != null && this.josmHomeTemp != null) { 495 545 try { 496 File home = josmHome.newFolder(); 497 System.setProperty("josm.home", home.getAbsolutePath()); 546 // TODO When JUnit4 support is dropped, use {@link ExtensionContext#getUniqueId} 547 // Currently, assume that the temporary directory is automatically cleared on boot. 548 final Path home = Files.createTempDirectory(josmHomeTemp, UUID.randomUUID().toString()); 549 System.setProperty("josm.home", home.toAbsolutePath().toString()); 498 550 JosmBaseDirectories.getInstance().clearMemos(); 499 551 } catch (IOException e) { 500 552 throw new InitializationError(e); … … 622 674 } 623 675 } 624 676 677 protected void beforeAllTests() { 678 cleanUpFromJosmFixture(); 679 } 680 625 681 /** 626 682 * Clean up what test not using these test rules may have broken. 627 683 */ … … 704 760 705 761 @Override 706 762 public void evaluate() throws Throwable { 707 before(); 763 beforeAllTests(); 764 beforeEachTest(); 708 765 try { 709 766 base.evaluate(); 710 767 } finally { … … 723 780 * The junit timeout statement has problems when switching timezones. This one does not. 724 781 * @author Michael Zangl 725 782 */ 726 private static class FailOnTimeoutStatement extends Statement {783 private static class FailOnTimeoutStatement extends Statement implements BeforeEachCallback, AfterEachCallback { 727 784 728 785 private final int timeout; 729 786 private final Statement original; 787 private TimeoutThread thread; 730 788 731 789 FailOnTimeoutStatement(Statement original, int timeout) { 732 790 this.original = original; … … 735 793 736 794 @Override 737 795 public void evaluate() throws Throwable { 738 TimeoutThread thread = new TimeoutThread(original); 739 thread.setDaemon(true); 740 thread.start(); 796 beforeEach(null); 797 afterEach(null); 798 } 799 800 @Override 801 public void afterEach(ExtensionContext context) throws Exception { 741 802 thread.join(timeout); 742 803 thread.interrupt(); 743 804 if (!thread.isDone) { 744 805 Throwable exception = thread.getExecutionException(); 745 806 if (exception != null) { 746 throw exception; 807 if (exception instanceof Exception) { 808 throw (Exception) exception; 809 } 810 throw new Exception(exception); 747 811 } else { 748 812 if (Logging.isLoggingEnabled(Logging.LEVEL_DEBUG)) { 749 813 // i.e. skip expensive formatting of stack trace if it won't be shown … … 754 818 throw new Exception(MessageFormat.format("Test timed out after {0}ms", timeout)); 755 819 } 756 820 } 821 822 757 823 } 824 825 @Override 826 public void beforeEach(ExtensionContext context) throws Exception { 827 this.thread = new TimeoutThread(original); 828 this.thread.setDaemon(true); 829 this.thread.start(); 830 } 758 831 } 759 832 760 833 private static final class TimeoutThread extends Thread { … … 787 860 getInputArguments().toString().indexOf("-agentlib:jdwp") > 0; 788 861 } 789 862 863 private static <T extends Annotation> T handleAnnotation(ExtensionContext context, Class<T> annotation) { 864 AnnotatedElement element = context.getElement().orElse(null); 865 ExtensionContext currentContext = context.getParent().orElse(null); 866 while (element == null && currentContext != null) { 867 element = currentContext.getElement().orElse(null); 868 currentContext = currentContext.getParent().orElse(null); 869 } 870 871 if (element != null) { 872 return element.getAnnotation(annotation); 873 } 874 return null; 875 } 876 877 class OverrideAssumeRevisionExtension implements BeforeEachCallback { 878 static final String KEY = "revision"; 879 @Override 880 public void beforeEach(ExtensionContext context) throws Exception { 881 OverrideAssumeRevision annotation = handleAnnotation(context, OverrideAssumeRevision.class); 882 if (annotation != null) { 883 context.getStore(ExtensionContext.Namespace.create(JOSMTestRules.class)).put(KEY, annotation.value()); 884 } 885 } 886 887 } 888 790 889 /** 791 890 * Override this test's assumed JOSM version (as reported by {@link Version}). 792 891 * @see JOSMTestRules#assumeRevision(String) 793 892 */ 893 @ExtendWith(OverrideAssumeRevisionExtension.class) 794 894 @Documented 795 895 @Retention(RetentionPolicy.RUNTIME) 796 896 @Target(ElementType.METHOD) … … 802 902 String value(); 803 903 } 804 904 905 class OverrideTimeoutExtension implements BeforeEachCallback { 906 static final String KEY = "timeout"; 907 @Override 908 public void beforeEach(ExtensionContext context) throws Exception { 909 OverrideTimeout annotation = handleAnnotation(context, OverrideTimeout.class); 910 if (annotation != null) { 911 context.getStore(ExtensionContext.Namespace.create(JOSMTestRules.class)).put(KEY, annotation.value()); 912 } 913 } 914 } 915 805 916 /** 806 917 * Override this test's timeout. 807 918 * @see JOSMTestRules#timeout(int) 808 919 */ 920 @ExtendWith(OverrideTimeoutExtension.class) 809 921 @Documented 810 922 @Retention(RetentionPolicy.RUNTIME) 811 923 @Target(ElementType.METHOD) -
test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java
2 2 package org.openstreetmap.josm.testutils; 3 3 4 4 import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; 5 import static org.junit.jupiter.api.Assertions.fail; 5 6 import static org.openstreetmap.josm.TestUtils.getPrivateStaticField; 6 7 7 8 import java.awt.Color; … … 10 11 import java.io.ByteArrayOutputStream; 11 12 import java.io.IOException; 12 13 import java.util.Arrays; 14 import java.util.Collection; 13 15 import java.util.Collections; 14 16 import java.util.HashMap; 15 17 import java.util.List; 16 18 import java.util.Objects; 19 import java.util.stream.Collectors; 17 20 18 21 import javax.imageio.ImageIO; 19 22 23 import org.junit.jupiter.api.extension.AfterAllCallback; 24 import org.junit.jupiter.api.extension.BeforeAllCallback; 25 import org.junit.jupiter.api.extension.ExtensionContext; 20 26 import org.junit.runner.Description; 21 27 import org.junit.runners.model.Statement; 22 28 import org.openstreetmap.josm.data.imagery.ImageryInfo; 23 29 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; 30 import org.openstreetmap.josm.gui.bbox.JosmMapViewer.TileSourceProvider; 24 31 import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser; 25 32 import org.openstreetmap.josm.tools.Logging; 26 33 … … 28 35 import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; 29 36 import com.github.tomakehurst.wiremock.client.WireMock; 30 37 import com.github.tomakehurst.wiremock.junit.WireMockRule; 38 import com.github.tomakehurst.wiremock.verification.LoggedRequest; 31 39 32 40 /** 33 41 * A JUnit rule, based on {@link WireMockRule} to provide a test with a simple mock tile server serving multiple tile 34 42 * sources. 35 43 */ 36 public class TileSourceRule extends WireMockRule {44 public class TileSourceRule extends WireMockRule implements BeforeAllCallback, AfterAllCallback { 37 45 private static class ByteArrayWrapper { 38 46 public final byte[] byteArray; 39 47 … … 165 173 protected final boolean clearLayerList; 166 174 protected final boolean clearSlippyMapSources; 167 175 protected final boolean registerInLayerList; 176 private List<TileSourceProvider> slippyMapProviders; 177 private TileSourceProvider slippyMapDefaultProvider; 178 private List<ImageryInfo> originalImageryInfoList; 168 179 169 180 /** 170 181 * Construct a TileSourceRule for use with a JUnit test. … … 238 249 return super.apply(new Statement() { 239 250 @Override 240 251 public void evaluate() throws Throwable { 241 try { 242 // a hack to circumvent a WireMock bug concerning delayed server startup. sending an early request 243 // to the mock server seems to prompt it to start earlier (though this request itself is not 244 // expected to succeed). see https://github.com/tomakehurst/wiremock/issues/97 245 new java.net.URL(TileSourceRule.this.url("/_poke")).getContent(); 246 } catch (IOException e) { 247 Logging.trace(e); 248 } 252 applyRunServerEarlyStart(); 249 253 base.evaluate(); 250 254 } 251 255 }, description); 252 256 } 253 257 258 private void applyRunServerEarlyStart() { 259 try { 260 // a hack to circumvent a WireMock bug concerning delayed server startup. sending an early request 261 // to the mock server seems to prompt it to start earlier (though this request itself is not 262 // expected to succeed). see https://github.com/tomakehurst/wiremock/issues/97 263 new java.net.URL(TileSourceRule.this.url("/_poke")).getContent(); 264 } catch (IOException e) { 265 Logging.trace(e); 266 } 267 } 268 254 269 /** 255 270 * A junit-rule {@code apply} method exposed separately, containing initialization steps which can only be performed 256 271 * once more of josm's environment has been set up. … … 264 279 if (this.registerInLayerList || this.clearLayerList) { 265 280 return new Statement() { 266 281 @Override 267 @SuppressWarnings("unchecked")268 282 public void evaluate() throws Throwable { 269 List<SlippyMapBBoxChooser.TileSourceProvider> slippyMapProviders = null; 270 SlippyMapBBoxChooser.TileSourceProvider slippyMapDefaultProvider = null; 271 List<ImageryInfo> originalImageryInfoList = null; 272 if (TileSourceRule.this.clearSlippyMapSources) { 273 try { 274 slippyMapProviders = (List<SlippyMapBBoxChooser.TileSourceProvider>) getPrivateStaticField( 275 SlippyMapBBoxChooser.class, 276 "providers" 277 ); 278 // pop this off the beginning of the list, keep for later 279 slippyMapDefaultProvider = slippyMapProviders.remove(0); 280 } catch (ReflectiveOperationException e) { 281 Logging.warn("Failed to remove default SlippyMapBBoxChooser TileSourceProvider"); 282 } 283 } 284 285 if (TileSourceRule.this.clearLayerList) { 286 originalImageryInfoList = ImageryLayerInfo.instance.getLayers(); 287 ImageryLayerInfo.instance.clear(); 288 } 289 if (TileSourceRule.this.registerInLayerList) { 290 for (ConstSource source : TileSourceRule.this.sourcesList) { 291 ImageryLayerInfo.addLayer(source.getImageryInfo(TileSourceRule.this.port())); 292 } 293 } 294 283 realBeforeRegisterLayers(); 295 284 try { 296 285 base.evaluate(); 297 286 } finally { 298 // clean up to original state 299 if (slippyMapDefaultProvider != null && slippyMapProviders != null) { 300 slippyMapProviders.add(0, slippyMapDefaultProvider); 301 } 302 if (originalImageryInfoList != null) { 303 ImageryLayerInfo.instance.clear(); 304 ImageryLayerInfo.addLayers(originalImageryInfoList); 305 } 287 realAfterRegisterLayers(); 306 288 } 307 289 } 308 290 }; … … 311 293 } 312 294 } 313 295 296 @SuppressWarnings("unchecked") 297 private void realBeforeRegisterLayers() { 298 if (this.registerInLayerList || this.clearLayerList) { 299 List<SlippyMapBBoxChooser.TileSourceProvider> slippyMapProviders = null; 300 if (TileSourceRule.this.clearSlippyMapSources) { 301 try { 302 slippyMapProviders = (List<SlippyMapBBoxChooser.TileSourceProvider>) getPrivateStaticField( 303 SlippyMapBBoxChooser.class, 304 "providers" 305 ); 306 // pop this off the beginning of the list, keep for later 307 this.slippyMapDefaultProvider = slippyMapProviders.remove(0); 308 } catch (ReflectiveOperationException e) { 309 Logging.warn("Failed to remove default SlippyMapBBoxChooser TileSourceProvider"); 310 } 311 } 312 313 if (TileSourceRule.this.clearLayerList) { 314 originalImageryInfoList = ImageryLayerInfo.instance.getLayers(); 315 ImageryLayerInfo.instance.clear(); 316 } 317 if (TileSourceRule.this.registerInLayerList) { 318 for (ConstSource source : TileSourceRule.this.sourcesList) { 319 ImageryLayerInfo.addLayer(source.getImageryInfo(TileSourceRule.this.port())); 320 } 321 } 322 } 323 } 324 325 private void realAfterRegisterLayers() { 326 // clean up to original state 327 if (slippyMapDefaultProvider != null && slippyMapProviders != null) { 328 slippyMapProviders.add(0, slippyMapDefaultProvider); 329 } 330 if (originalImageryInfoList != null) { 331 ImageryLayerInfo.instance.clear(); 332 ImageryLayerInfo.addLayers(originalImageryInfoList); 333 } 334 } 335 314 336 /** 315 337 * A standard implementation of apply which simply calls both sub- {@code apply} methods, {@link #applyRunServer} 316 338 * and {@link #applyRegisterLayers}. Called when used as a standard junit rule. … … 319 341 public Statement apply(Statement base, Description description) { 320 342 return applyRunServer(applyRegisterLayers(base, description), description); 321 343 } 344 345 @Override 346 public void afterAll(ExtensionContext context) throws Exception { 347 after(); 348 Collection<LoggedRequest> missedRequests = super.findAllUnmatchedRequests(); 349 if (!missedRequests.isEmpty()) { 350 String message = missedRequests.stream().map(m -> m.getUrl()).collect(Collectors.joining("\n\n")); 351 fail(message); 352 } 353 stop(); 354 realAfterRegisterLayers(); 355 } 356 357 @Override 358 public void beforeAll(ExtensionContext context) throws Exception { 359 super.start(); 360 WireMock.configureFor("localhost", port()); 361 before(); 362 applyRunServerEarlyStart(); 363 realBeforeRegisterLayers(); 364 } 322 365 }
