Ticket #15508: v1-0002-add-TileSourceRule-a-junit-rule-for-creating-mock-ti.patch

File v1-0002-add-TileSourceRule-a-junit-rule-for-creating-mock-ti.patch, 9.2 KB (added by ris, 8 years ago)
  • new file test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java

    From 943dd282b0e729054b22f0608c55de1213503949 Mon Sep 17 00:00:00 2001
    From: Robert Scott <code@humanleg.org.uk>
    Date: Sat, 21 Oct 2017 11:19:28 +0100
    Subject: [PATCH 2/4] add TileSourceRule: a junit rule for creating mock tile
     servers
    
    ---
     .../josm/testutils/TileSourceRule.java             | 216 +++++++++++++++++++++
     1 file changed, 216 insertions(+)
     create mode 100644 test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java
    
    diff --git a/test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java b/test/unit/org/openstreetmap/josm/testutils/TileSourceRule.java
    new file mode 100644
    index 000000000..3738a4b69
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.testutils;
     3
     4import java.io.ByteArrayOutputStream;
     5import java.io.IOException;
     6import java.util.Arrays;
     7import java.util.Collections;
     8import java.util.Objects;
     9import java.util.HashMap;
     10import java.util.List;
     11
     12import java.awt.Color;
     13import java.awt.Graphics2D;
     14import java.awt.image.BufferedImage;
     15
     16import javax.imageio.ImageIO;
     17
     18import org.openstreetmap.josm.data.imagery.ImageryInfo;
     19import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
     20import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
     21import org.openstreetmap.josm.tools.Logging;
     22
     23import static org.openstreetmap.josm.TestUtils.getPrivateStaticField;
     24
     25import org.junit.runner.Description;
     26import org.junit.runners.model.Statement;
     27import com.github.tomakehurst.wiremock.client.MappingBuilder;
     28import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
     29import com.github.tomakehurst.wiremock.client.WireMock;
     30import com.github.tomakehurst.wiremock.junit.WireMockRule;
     31
     32import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
     33
     34
     35public class TileSourceRule extends WireMockRule {
     36    private static class ByteArrayWrapper{
     37        // i don't believe you're making me do this, java
     38        public final byte[] byteArray;
     39
     40        public ByteArrayWrapper(byte[] ba) {
     41            this.byteArray = ba;
     42        }
     43    }
     44
     45    public static HashMap<ConstSource, ByteArrayWrapper> constPayloadCache = new HashMap<>();
     46
     47    public static abstract class ConstSource {
     48        public abstract byte[] generatePayloadBytes();
     49        public abstract MappingBuilder getMappingBuilder();
     50        public abstract String getLabel();
     51        public abstract ImageryInfo getImageryInfo(int port);
     52
     53        public byte[] getPayloadBytes() {
     54            ByteArrayWrapper payloadWrapper = constPayloadCache.get(this);
     55            if (payloadWrapper == null) {
     56                payloadWrapper = new ByteArrayWrapper(this.generatePayloadBytes());
     57                constPayloadCache.put(this, payloadWrapper);
     58            }
     59            return payloadWrapper.byteArray;
     60        }
     61
     62        public ResponseDefinitionBuilder getResponseDefinitionBuilder() {
     63            return WireMock.aResponse().withStatus(200).withHeader("Content-Type", "image/png").withBody(
     64                this.getPayloadBytes()
     65            );
     66        }
     67    }
     68
     69    public static class ColorSource extends ConstSource {
     70        protected final Color color;
     71        protected final String label;
     72        protected final int tileSize;
     73
     74        public ColorSource(Color color, String label, int tileSize) {
     75            this.color = color;
     76            this.label = label;
     77            this.tileSize = tileSize;
     78        }
     79
     80        @Override
     81        public int hashCode() {
     82            return Objects.hash(this.color, this.label, this.tileSize, this.getClass());
     83        }
     84
     85        @Override
     86        public byte[] generatePayloadBytes() {
     87            BufferedImage image = new BufferedImage(this.tileSize, this.tileSize, BufferedImage.TYPE_INT_RGB);
     88            Graphics2D g = image.createGraphics();
     89            g.setBackground(this.color);
     90            g.clearRect(0, 0, image.getWidth(), image.getHeight());
     91
     92            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
     93            try {
     94                ImageIO.write(image, "png", outputStream);
     95            } catch (IOException e) {
     96                // I don't see how this would be possible writing to a ByteArrayOutputStream
     97            }
     98            return outputStream.toByteArray();
     99        }
     100
     101        @Override
     102        public MappingBuilder getMappingBuilder() {
     103            return WireMock.get(WireMock.urlMatching(String.format("/%h/(\\d+)/(\\d+)/(\\d+)\\.png", this.hashCode())));
     104        }
     105
     106        @Override
     107        public ImageryInfo getImageryInfo(int port) {
     108            return new ImageryInfo(
     109                this.label,
     110                String.format("tms[20]:http://localhost:%d/%h/{z}/{x}/{y}.png", port, this.hashCode()),
     111                "tms",
     112                (String)null,
     113                (String)null
     114            );
     115        }
     116
     117        @Override
     118        public String getLabel() {
     119            return this.label;
     120        }
     121    }
     122
     123    public final List<ConstSource> sourcesList;
     124    public final boolean clearLayerList;
     125    public final boolean clearSlippyMapSources;
     126    public final boolean registerInLayerList;
     127
     128    public TileSourceRule(ConstSource... sources) {
     129        this(false, false, false, sources);
     130    }
     131
     132    public TileSourceRule(
     133        boolean clearLayerList,
     134        boolean clearSlippyMapSources,
     135        boolean registerInLayerList,
     136        ConstSource... sources
     137    ) {
     138        super(options().dynamicPort());
     139        this.clearLayerList = clearLayerList;
     140        this.clearSlippyMapSources = clearSlippyMapSources;
     141        this.registerInLayerList = registerInLayerList;
     142        this.sourcesList = Collections.unmodifiableList(Arrays.asList(sources));
     143        for (ConstSource source : this.sourcesList) {
     144            this.stubFor(source.getMappingBuilder().willReturn(source.getResponseDefinitionBuilder()));
     145        }
     146    }
     147
     148    /**
     149     * A junit-rule {@code apply} method exposed separately to allow a chaining rule to put this much earlier in
     150     * the test's initialization routine. The idea being to allow WireMock's web server to be starting up while other
     151     * necessary initialization is taking place.
     152     * See {@link org.junit.rules.TestRule#apply} for arguments.
     153     */
     154    public Statement applyRunServer(Statement base, Description description) {
     155        return super.apply(base, description);
     156    }
     157
     158    /**
     159     * A junit-rule {@code apply} method exposed separately, containing initialization steps which can only be performed
     160     * once more of josm's environment has been set up.
     161     * See {@link org.junit.rules.TestRule#apply} for arguments.
     162     */
     163    public Statement applyRegisterLayers(Statement base, Description description) {
     164        if (this.registerInLayerList || this.clearLayerList) {
     165            return new Statement() {
     166                @Override
     167                @SuppressWarnings("unchecked")
     168                public void evaluate() throws Throwable {
     169                    List<SlippyMapBBoxChooser.TileSourceProvider> slippyMapProviders = null;
     170                    SlippyMapBBoxChooser.TileSourceProvider slippyMapDefaultProvider = null;
     171                    if (TileSourceRule.this.clearSlippyMapSources) {
     172                        try {
     173                            slippyMapProviders = (List<SlippyMapBBoxChooser.TileSourceProvider>)getPrivateStaticField(
     174                                SlippyMapBBoxChooser.class,
     175                                "providers"
     176                            );
     177                            // pop this off the beginning of the list, keep for later
     178                            slippyMapDefaultProvider = slippyMapProviders.remove(0);
     179                        } catch (ReflectiveOperationException e) {
     180                            Logging.warn("Failed to remove default SlippyMapBBoxChooser TileSourceProvider");
     181                        }
     182                    }
     183
     184                    if (TileSourceRule.this.clearLayerList) {
     185                        ImageryLayerInfo.instance.clear();
     186                    }
     187                    if (TileSourceRule.this.registerInLayerList) {
     188                        for (ConstSource source : TileSourceRule.this.sourcesList){
     189                            ImageryLayerInfo.addLayer(source.getImageryInfo(TileSourceRule.this.port()));
     190                        }
     191                    }
     192
     193                    try {
     194                        base.evaluate();
     195                    } finally {
     196                        if (slippyMapDefaultProvider != null) {
     197                            // clean this up to its original state
     198                            slippyMapProviders.add(0, slippyMapDefaultProvider);
     199                        }
     200                    }
     201                }
     202            };
     203        } else {
     204            return base;
     205        }
     206    }
     207
     208    /**
     209     * A standard implementation of apply which simply calls both sub- {@code apply} methods, {@link #applyRunServer}
     210     * and {@link applyRegisterLayers}. Called when used as a standard junit rule.
     211     */
     212    @Override
     213    public Statement apply(Statement base, Description description) {
     214        return this.applyRunServer(this.applyRegisterLayers(base, description), description);
     215    }
     216}