Ticket #16567: 16567.separate_all_each.1.patch

File 16567.separate_all_each.1.patch, 21.5 KB (added by taylor.smock, 5 years ago)

Use @ExtendWith for Override annotations

  • test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.testutils;
    33
     4import static org.junit.jupiter.api.Assertions.fail;
     5
    46import java.awt.Color;
    57import java.awt.Window;
    68import java.awt.event.WindowEvent;
    79import java.io.ByteArrayInputStream;
    8 import java.io.File;
    910import java.io.IOException;
    1011import java.io.PrintWriter;
    1112import java.io.StringWriter;
     13import java.lang.annotation.Annotation;
    1214import java.lang.annotation.Documented;
    1315import java.lang.annotation.ElementType;
    1416import java.lang.annotation.Retention;
    1517import java.lang.annotation.RetentionPolicy;
    1618import java.lang.annotation.Target;
     19import java.lang.reflect.AnnotatedElement;
    1720import java.nio.charset.StandardCharsets;
     21import java.nio.file.Files;
     22import java.nio.file.Path;
    1823import java.security.GeneralSecurityException;
    1924import java.text.MessageFormat;
    2025import java.util.Arrays;
    2126import java.util.Map;
    2227import java.util.TimeZone;
     28import java.util.UUID;
    2329import java.util.logging.Handler;
    2430
    2531import org.awaitility.Awaitility;
     
    2733import org.junit.jupiter.api.extension.AfterEachCallback;
    2834import org.junit.jupiter.api.extension.BeforeAllCallback;
    2935import org.junit.jupiter.api.extension.BeforeEachCallback;
     36import org.junit.jupiter.api.extension.ExtendWith;
    3037import org.junit.jupiter.api.extension.ExtensionContext;
     38import org.junit.jupiter.api.extension.ExtensionContext.Store;
    3139import org.junit.rules.TemporaryFolder;
    3240import org.junit.rules.TestRule;
    3341import org.junit.runner.Description;
     
    8593 */
    8694public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback {
    8795    private int timeout = isDebugMode() ? -1 : 10 * 1000;
     96    private final Path josmHomeTemp;
    8897    private TemporaryFolder josmHome;
    8998    private boolean usePreferences = false;
    9099    private APIType useAPI = APIType.NONE;
     
    105114    private boolean territories;
    106115    private boolean metric;
    107116    private boolean main;
     117
    108118    /**
    109119     * This boolean is only used to indicate if JUnit5 is used in a test. If it is,
    110120     * we must not call after in {@link JOSMTestRules.CreateJosmEnvironment#evaluate}.
     
    112122     */
    113123    private boolean junit5;
    114124
     125    private FailOnTimeoutStatement failOnTimeout;
     126
    115127    /**
     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    /**
    116141     * Disable the default timeout for this test. Use with care.
    117142     * @return this instance, for easy chaining
    118143     */
     
    446471    @Override
    447472    public void beforeEach(ExtensionContext context) throws Exception {
    448473        this.junit5 = true;
     474
     475        // Temporary statement is necessary until we are able to remove JUnit4.
    449476        Statement temporaryStatement = new Statement() {
    450477            @Override
    451478            public void evaluate() throws Throwable {
     
    452479                // do nothing
    453480            }
    454481        };
     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        }
    455494        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            }
    459501        } catch (Throwable e) {
    460502            throw new Exception(e);
    461503        }
     
    463505
    464506    @Override
    465507    public void afterEach(ExtensionContext context) throws Exception {
     508        if (this.failOnTimeout != null) {
     509            this.failOnTimeout.afterEach(context);
     510        }
    466511        after();
    467512    }
    468513
    469514    @Override
    470515    public void beforeAll(ExtensionContext context) throws Exception {
    471         beforeEach(context);
     516        if (this.tileSourceRule != null) {
     517            this.tileSourceRule.beforeAll(context);
     518        }
     519        this.beforeAllTests();
    472520    }
    473521
    474522    @Override
    475523    public void afterAll(ExtensionContext context) throws Exception {
    476524        afterEach(context);
     525        if (this.tileSourceRule != null) {
     526            this.tileSourceRule.afterAll(context);
     527        }
    477528    }
    478529
    479530    /**
     
    481532     * @throws InitializationError If an error occurred while creating the required environment.
    482533     * @throws ReflectiveOperationException if a reflective access error occurs
    483534     */
    484     protected void before() throws InitializationError, ReflectiveOperationException {
    485         cleanUpFromJosmFixture();
    486 
     535    protected void beforeEachTest() throws InitializationError, ReflectiveOperationException {
    487536        if (this.assumeRevisionString != null) {
    488537            this.originalVersion = Version.getInstance();
    489538            final Version replacementVersion = new MockVersion(this.assumeRevisionString);
     
    490539            TestUtils.setPrivateStaticField(Version.class, "instance", replacementVersion);
    491540        }
    492541
    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) {
    495545            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());
    498550                JosmBaseDirectories.getInstance().clearMemos();
    499551            } catch (IOException e) {
    500552                throw new InitializationError(e);
     
    622674        }
    623675    }
    624676
     677    protected void beforeAllTests() {
     678        cleanUpFromJosmFixture();
     679    }
     680
    625681    /**
    626682     * Clean up what test not using these test rules may have broken.
    627683     */
     
    704760
    705761        @Override
    706762        public void evaluate() throws Throwable {
    707             before();
     763            beforeAllTests();
     764            beforeEachTest();
    708765            try {
    709766                base.evaluate();
    710767            } finally {
     
    723780     * The junit timeout statement has problems when switching timezones. This one does not.
    724781     * @author Michael Zangl
    725782     */
    726     private static class FailOnTimeoutStatement extends Statement {
     783    private static class FailOnTimeoutStatement extends Statement implements BeforeEachCallback, AfterEachCallback {
    727784
    728785        private final int timeout;
    729786        private final Statement original;
     787        private TimeoutThread thread;
    730788
    731789        FailOnTimeoutStatement(Statement original, int timeout) {
    732790            this.original = original;
     
    735793
    736794        @Override
    737795        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 {
    741802            thread.join(timeout);
    742803            thread.interrupt();
    743804            if (!thread.isDone) {
    744805                Throwable exception = thread.getExecutionException();
    745806                if (exception != null) {
    746                     throw exception;
     807                    if (exception instanceof Exception) {
     808                        throw (Exception) exception;
     809                    }
     810                    throw new Exception(exception);
    747811                } else {
    748812                    if (Logging.isLoggingEnabled(Logging.LEVEL_DEBUG)) {
    749813                        // i.e. skip expensive formatting of stack trace if it won't be shown
     
    754818                    throw new Exception(MessageFormat.format("Test timed out after {0}ms", timeout));
    755819                }
    756820            }
     821
     822
    757823        }
     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        }
    758831    }
    759832
    760833    private static final class TimeoutThread extends Thread {
     
    787860                getInputArguments().toString().indexOf("-agentlib:jdwp") > 0;
    788861    }
    789862
     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
    790889    /**
    791890     * Override this test's assumed JOSM version (as reported by {@link Version}).
    792891     * @see JOSMTestRules#assumeRevision(String)
    793892     */
     893    @ExtendWith(OverrideAssumeRevisionExtension.class)
    794894    @Documented
    795895    @Retention(RetentionPolicy.RUNTIME)
    796896    @Target(ElementType.METHOD)
     
    802902        String value();
    803903    }
    804904
     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
    805916    /**
    806917     * Override this test's timeout.
    807918     * @see JOSMTestRules#timeout(int)
    808919     */
     920    @ExtendWith(OverrideTimeoutExtension.class)
    809921    @Documented
    810922    @Retention(RetentionPolicy.RUNTIME)
    811923    @Target(ElementType.METHOD)
  • test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java

     
    22package org.openstreetmap.josm.testutils;
    33
    44import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
     5import static org.junit.jupiter.api.Assertions.fail;
    56import static org.openstreetmap.josm.TestUtils.getPrivateStaticField;
    67
    78import java.awt.Color;
     
    1011import java.io.ByteArrayOutputStream;
    1112import java.io.IOException;
    1213import java.util.Arrays;
     14import java.util.Collection;
    1315import java.util.Collections;
    1416import java.util.HashMap;
    1517import java.util.List;
    1618import java.util.Objects;
     19import java.util.stream.Collectors;
    1720
    1821import javax.imageio.ImageIO;
    1922
     23import org.junit.jupiter.api.extension.AfterAllCallback;
     24import org.junit.jupiter.api.extension.BeforeAllCallback;
     25import org.junit.jupiter.api.extension.ExtensionContext;
    2026import org.junit.runner.Description;
    2127import org.junit.runners.model.Statement;
    2228import org.openstreetmap.josm.data.imagery.ImageryInfo;
    2329import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
     30import org.openstreetmap.josm.gui.bbox.JosmMapViewer.TileSourceProvider;
    2431import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
    2532import org.openstreetmap.josm.tools.Logging;
    2633
     
    2835import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
    2936import com.github.tomakehurst.wiremock.client.WireMock;
    3037import com.github.tomakehurst.wiremock.junit.WireMockRule;
     38import com.github.tomakehurst.wiremock.verification.LoggedRequest;
    3139
    3240/**
    3341 * A JUnit rule, based on {@link WireMockRule} to provide a test with a simple mock tile server serving multiple tile
    3442 * sources.
    3543 */
    36 public class TileSourceRule extends WireMockRule {
     44public class TileSourceRule extends WireMockRule implements BeforeAllCallback, AfterAllCallback {
    3745    private static class ByteArrayWrapper {
    3846        public final byte[] byteArray;
    3947
     
    165173    protected final boolean clearLayerList;
    166174    protected final boolean clearSlippyMapSources;
    167175    protected final boolean registerInLayerList;
     176    private List<TileSourceProvider> slippyMapProviders;
     177    private TileSourceProvider slippyMapDefaultProvider;
     178    private List<ImageryInfo> originalImageryInfoList;
    168179
    169180    /**
    170181     * Construct a TileSourceRule for use with a JUnit test.
     
    238249        return super.apply(new Statement() {
    239250            @Override
    240251            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();
    249253                base.evaluate();
    250254            }
    251255        }, description);
    252256    }
    253257
     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
    254269    /**
    255270     * A junit-rule {@code apply} method exposed separately, containing initialization steps which can only be performed
    256271     * once more of josm's environment has been set up.
     
    264279        if (this.registerInLayerList || this.clearLayerList) {
    265280            return new Statement() {
    266281                @Override
    267                 @SuppressWarnings("unchecked")
    268282                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();
    295284                    try {
    296285                        base.evaluate();
    297286                    } 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();
    306288                    }
    307289                }
    308290            };
     
    311293        }
    312294    }
    313295
     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
    314336    /**
    315337     * A standard implementation of apply which simply calls both sub- {@code apply} methods, {@link #applyRunServer}
    316338     * and {@link #applyRegisterLayers}. Called when used as a standard junit rule.
     
    319341    public Statement apply(Statement base, Description description) {
    320342        return applyRunServer(applyRegisterLayers(base, description), description);
    321343    }
     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    }
    322365}