Ticket #16567: 16567.separate_all_each.patch

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

Initial work on using {After,Before}{Each,All}Callbacks properly. Some tests are failing in Eclipse.

  • 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.Method;
    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;
     27import java.util.Optional;
    2228import java.util.TimeZone;
     29import java.util.UUID;
    2330import java.util.logging.Handler;
    2431
    2532import org.awaitility.Awaitility;
     
    8592 */
    8693public class JOSMTestRules implements TestRule, AfterEachCallback, BeforeEachCallback, AfterAllCallback, BeforeAllCallback {
    8794    private int timeout = isDebugMode() ? -1 : 10 * 1000;
     95    private final Path josmHomeTemp;
    8896    private TemporaryFolder josmHome;
    8997    private boolean usePreferences = false;
    9098    private APIType useAPI = APIType.NONE;
     
    105113    private boolean territories;
    106114    private boolean metric;
    107115    private boolean main;
     116
    108117    /**
    109118     * This boolean is only used to indicate if JUnit5 is used in a test. If it is,
    110119     * we must not call after in {@link JOSMTestRules.CreateJosmEnvironment#evaluate}.
     
    112121     */
    113122    private boolean junit5;
    114123
     124    private FailOnTimeoutStatement failOnTimeout;
     125
    115126    /**
     127     * Create a new JOSMTestRules object
     128     */
     129    public JOSMTestRules() {
     130        Path tempHome = null;
     131        try {
     132            tempHome = Files.createTempDirectory("josm-test-rules-home");
     133        } catch (IOException e) {
     134            fail(e);
     135        }
     136        this.josmHomeTemp = tempHome;
     137    }
     138
     139    /**
    116140     * Disable the default timeout for this test. Use with care.
    117141     * @return this instance, for easy chaining
    118142     */
     
    443467        return statement;
    444468    }
    445469
     470    /**
     471     * Get an annotation for a context
     472     * @param <T> The annotation type
     473     * @param context The context to check
     474     * @param annotation The annotation to get
     475     * @return The annotation, or null
     476     */
     477    private static <T extends Annotation> T getAnnotation(ExtensionContext context, Class<T> annotation) {
     478        Optional<Method> method = context.getTestMethod();
     479        if (method.isPresent() && method.get().getAnnotation(annotation) != null) {
     480            return method.get().getAnnotation(annotation);
     481        }
     482        Optional<Class<?>> clazz = context.getTestClass();
     483        if (clazz.isPresent() && clazz.get().getAnnotation(annotation) != null) {
     484            return clazz.get().getAnnotation(annotation);
     485        }
     486        return null;
     487    }
     488
    446489    @Override
    447490    public void beforeEach(ExtensionContext context) throws Exception {
    448491        this.junit5 = true;
     492
     493        // Temporary statement is necessary until we are able to remove JUnit4.
    449494        Statement temporaryStatement = new Statement() {
    450495            @Override
    451496            public void evaluate() throws Throwable {
     
    452497                // do nothing
    453498            }
    454499        };
     500        // First process any Override* annotations for per-test overrides.
     501        // The following only work because "option" methods modify JOSMTestRules in-place
     502        final OverrideAssumeRevision overrideAssumeRevision = getAnnotation(context, OverrideAssumeRevision.class);
     503        if (overrideAssumeRevision != null) {
     504            this.assumeRevision(overrideAssumeRevision.value());
     505        }
     506        final OverrideTimeout overrideTimeout = getAnnotation(context, OverrideTimeout.class);
     507        if (overrideTimeout != null) {
     508            this.timeout(overrideTimeout.value());
     509        }
    455510        try {
    456             this.apply(temporaryStatement,
    457                     Description.createTestDescription(this.getClass(), "JOSMTestRules JUnit5 Compatibility"))
    458                     .evaluate();
     511            beforeEachTest();
     512
     513            if (this.timeout > 0) {
     514                this.failOnTimeout = new FailOnTimeoutStatement(temporaryStatement, this.timeout);
     515                this.failOnTimeout.beforeEach(context);
     516            }
    459517        } catch (Throwable e) {
    460518            throw new Exception(e);
    461519        }
     
    463521
    464522    @Override
    465523    public void afterEach(ExtensionContext context) throws Exception {
     524        if (this.failOnTimeout != null) {
     525            this.failOnTimeout.afterEach(context);
     526        }
    466527        after();
    467528    }
    468529
    469530    @Override
    470531    public void beforeAll(ExtensionContext context) throws Exception {
    471         beforeEach(context);
     532        if (this.tileSourceRule != null) {
     533            this.tileSourceRule.beforeAll(context);
     534        }
     535        this.beforeAllTests();
    472536    }
    473537
    474538    @Override
    475539    public void afterAll(ExtensionContext context) throws Exception {
    476540        afterEach(context);
     541        if (this.tileSourceRule != null) {
     542            this.tileSourceRule.afterAll(context);
     543        }
    477544    }
    478545
    479546    /**
     
    481548     * @throws InitializationError If an error occurred while creating the required environment.
    482549     * @throws ReflectiveOperationException if a reflective access error occurs
    483550     */
    484     protected void before() throws InitializationError, ReflectiveOperationException {
    485         cleanUpFromJosmFixture();
    486 
     551    protected void beforeEachTest() throws InitializationError, ReflectiveOperationException {
    487552        if (this.assumeRevisionString != null) {
    488553            this.originalVersion = Version.getInstance();
    489554            final Version replacementVersion = new MockVersion(this.assumeRevisionString);
     
    490555            TestUtils.setPrivateStaticField(Version.class, "instance", replacementVersion);
    491556        }
    492557
    493         // Add JOSM home
    494         if (josmHome != null) {
     558        // Add JOSM home. Take advantage of the fact that josmHome is only initialized with preferences.
     559        // And is always initialized with them.
     560        if (this.josmHome != null && this.josmHomeTemp != null) {
    495561            try {
    496                 File home = josmHome.newFolder();
    497                 System.setProperty("josm.home", home.getAbsolutePath());
     562                final Path home = Files.createTempDirectory(josmHomeTemp, UUID.randomUUID().toString());
     563                System.setProperty("josm.home", home.toAbsolutePath().toString());
    498564                JosmBaseDirectories.getInstance().clearMemos();
    499565            } catch (IOException e) {
    500566                throw new InitializationError(e);
     
    622688        }
    623689    }
    624690
     691    protected void beforeAllTests() {
     692        cleanUpFromJosmFixture();
     693    }
     694
    625695    /**
    626696     * Clean up what test not using these test rules may have broken.
    627697     */
     
    704774
    705775        @Override
    706776        public void evaluate() throws Throwable {
    707             before();
     777            beforeAllTests();
     778            beforeEachTest();
    708779            try {
    709780                base.evaluate();
    710781            } finally {
     
    723794     * The junit timeout statement has problems when switching timezones. This one does not.
    724795     * @author Michael Zangl
    725796     */
    726     private static class FailOnTimeoutStatement extends Statement {
     797    private static class FailOnTimeoutStatement extends Statement implements BeforeEachCallback, AfterEachCallback {
    727798
    728799        private final int timeout;
    729800        private final Statement original;
     801        private TimeoutThread thread;
    730802
    731803        FailOnTimeoutStatement(Statement original, int timeout) {
    732804            this.original = original;
     
    735807
    736808        @Override
    737809        public void evaluate() throws Throwable {
    738             TimeoutThread thread = new TimeoutThread(original);
    739             thread.setDaemon(true);
    740             thread.start();
     810            beforeEach(null);
     811            afterEach(null);
     812        }
     813
     814        @Override
     815        public void afterEach(ExtensionContext context) throws Exception {
    741816            thread.join(timeout);
    742817            thread.interrupt();
    743818            if (!thread.isDone) {
    744819                Throwable exception = thread.getExecutionException();
    745820                if (exception != null) {
    746                     throw exception;
     821                    if (exception instanceof Exception) {
     822                        throw (Exception) exception;
     823                    }
     824                    throw new Exception(exception);
    747825                } else {
    748826                    if (Logging.isLoggingEnabled(Logging.LEVEL_DEBUG)) {
    749827                        // i.e. skip expensive formatting of stack trace if it won't be shown
     
    754832                    throw new Exception(MessageFormat.format("Test timed out after {0}ms", timeout));
    755833                }
    756834            }
     835
     836
    757837        }
     838
     839        @Override
     840        public void beforeEach(ExtensionContext context) throws Exception {
     841            this.thread = new TimeoutThread(original);
     842            this.thread.setDaemon(true);
     843            this.thread.start();
     844        }
    758845    }
    759846
    760847    private static final class TimeoutThread extends Thread {
  • 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}