Ticket #15599: v2-0003-add-ImagePatternMatching-testutil-example-use-in-.patch

File v2-0003-add-ImagePatternMatching-testutil-example-use-in-.patch, 19.5 KB (added by ris, 6 years ago)
  • test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java

    From b53dc2e815c91fdefa9613da5911306f7df90c41 Mon Sep 17 00:00:00 2001
    From: Robert Scott <code@humanleg.org.uk>
    Date: Sat, 25 Nov 2017 23:55:46 +0000
    Subject: [PATCH v2 3/3] add ImagePatternMatching testutil, example use in
     MinimapDialogTest
    
    ---
     .../josm/gui/dialogs/MinimapDialogTest.java        | 126 ++++++++
     .../josm/testutils/ImagePatternMatching.java       | 316 +++++++++++++++++++++
     2 files changed, 442 insertions(+)
     create mode 100644 test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java
    
    diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java
    index b4ac23820..8aeef9b16 100644
    a b import static java.util.concurrent.TimeUnit.MILLISECONDS;  
    1111import java.awt.Color;
    1212import java.awt.Component;
    1313import java.awt.Graphics2D;
     14import java.awt.event.ComponentEvent;
    1415import java.awt.image.BufferedImage;
    1516
    1617import javax.swing.JMenuItem;
    1718import javax.swing.JPopupMenu;
    1819
     20import java.util.Arrays;
     21import java.util.Map;
    1922import java.util.concurrent.Callable;
     23import java.util.regex.Matcher;
    2024
    2125import org.junit.Rule;
    2226import org.junit.Test;
    2327import org.openstreetmap.josm.Main;
    2428import org.openstreetmap.josm.TestUtils;
     29import org.openstreetmap.josm.data.Bounds;
     30import org.openstreetmap.josm.data.projection.Projections;
    2531import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
    2632import org.openstreetmap.josm.gui.bbox.SourceButton;
     33import org.openstreetmap.josm.gui.MainApplication;
     34import org.openstreetmap.josm.gui.MapView;
    2735import org.openstreetmap.josm.gui.util.GuiHelper;
     36import org.openstreetmap.josm.testutils.ImagePatternMatching;
    2837import org.openstreetmap.josm.testutils.JOSMTestRules;
    2938
    3039import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    3140
    3241import org.awaitility.Awaitility;
    3342
     43import com.google.common.collect.ImmutableMap;
     44
    3445/**
    3546 * Unit tests of {@link MinimapDialog} class.
    3647 */
    public class MinimapDialogTest {  
    251262
    252263        assertEquals(0xffffffff, paintedSlippyMap.getRGB(0, 0));
    253264    }
     265
     266    /**
     267     * test viewport marker rectangle matches the mapView's aspect ratio
     268     * @throws Exception if any error occurs
     269     */
     270    @Test
     271    public void testViewportAspectRatio() throws Exception {
     272        Main.pref.put("slippy_map_chooser.mapstyle", "White Tiles");
     273        // ensure projection matches JMapViewer's
     274        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
     275
     276        MapView mapView = MainApplication.getMap().mapView;
     277        GuiHelper.runInEDTAndWaitWithException(() -> {
     278            mapView.setVisible(true);
     279            mapView.addNotify();
     280            mapView.doLayout();
     281            // ensure we have a square mapView viewport
     282            mapView.setBounds(0, 0, 350, 350);
     283        });
     284
     285        this.setUpMiniMap();
     286
     287        // attempt to set viewport to cover a non-square area
     288        mapView.zoomTo(new Bounds(26.27, -18.23, 26.275, -18.229));
     289
     290        // an initial paint operation is required to trigger the tile fetches
     291        this.paintSlippyMap();
     292
     293        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     294
     295        this.paintSlippyMap();
     296
     297        Map<Integer, String> paletteMap = ImmutableMap.<Integer, String>builder()
     298            .put(0xffffffff, "w")
     299            .put(0xff000000, "b")
     300            .put(0xfff0d1d1, "p")
     301            .build();
     302
     303        Matcher rowMatcher = ImagePatternMatching.rowMatch(
     304            paintedSlippyMap,
     305            paintedSlippyMap.getHeight()/2,
     306            paletteMap,
     307            "^(w+)b(p+)b(w+)$",
     308            true
     309        );
     310
     311        // (within a tolerance for numerical error) the number of pixels on the left of the viewport marker
     312        // should equal the number on the right
     313        assertTrue(
     314            "Viewport marker not horizontally centered",
     315            Math.abs(rowMatcher.group(1).length() - rowMatcher.group(3).length()) < 4
     316        );
     317
     318        Matcher colMatcher = ImagePatternMatching.columnMatch(
     319            paintedSlippyMap,
     320            paintedSlippyMap.getWidth()/2,
     321            paletteMap,
     322            "^(w+)b(p+)b(w+)$",
     323            true
     324        );
     325
     326        // (within a tolerance for numerical error) the number of pixels on the top of the viewport marker
     327        // should equal the number on the bottom
     328        assertTrue(
     329            "Viewport marker not vertically centered",
     330            Math.abs(colMatcher.group(1).length() - colMatcher.group(3).length()) < 4
     331        );
     332
     333        // (within a tolerance for numerical error) the viewport marker should be square
     334        assertTrue(
     335            "Viewport marker not square",
     336            Math.abs(colMatcher.group(2).length() - rowMatcher.group(2).length()) < 4
     337        );
     338
     339        // now change the mapView size
     340        GuiHelper.runInEDTAndWaitWithException(() -> {
     341            mapView.setBounds(0, 0, 150, 300);
     342            Arrays.stream(mapView.getComponentListeners()).forEach(
     343                cl -> cl.componentResized(new ComponentEvent(mapView, ComponentEvent.COMPONENT_RESIZED))
     344            );
     345        });
     346        // minimap doesn't (yet?) listen for component resize events to update its viewport marker, so
     347        // trigger a zoom change
     348        mapView.zoomTo(mapView.getCenter());
     349        this.paintSlippyMap();
     350
     351        rowMatcher = ImagePatternMatching.rowMatch(
     352            paintedSlippyMap,
     353            paintedSlippyMap.getHeight()/2,
     354            paletteMap,
     355            "^(w+)b(p+)b(w+)$",
     356            true
     357        );
     358        assertTrue(
     359            "Viewport marker not horizontally centered",
     360            Math.abs(rowMatcher.group(1).length() - rowMatcher.group(3).length()) < 4
     361        );
     362
     363        colMatcher = ImagePatternMatching.columnMatch(
     364            paintedSlippyMap,
     365            paintedSlippyMap.getWidth()/2,
     366            paletteMap,
     367            "^(w+)b(p+)b(w+)$",
     368            true
     369        );
     370        assertTrue(
     371            "Viewport marker not vertically centered",
     372            Math.abs(colMatcher.group(1).length() - colMatcher.group(3).length()) < 4
     373        );
     374
     375        assertTrue(
     376            "Viewport marker not 2:1 aspect ratio",
     377            Math.abs(colMatcher.group(2).length() - (rowMatcher.group(2).length()*2.0)) < 5
     378        );
     379    }
    254380}
  • new file test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java

    diff --git a/test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java b/test/unit/org/openstreetmap/josm/testutils/ImagePatternMatching.java
    new file mode 100644
    index 000000000..27536e16a
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.testutils;
     3
     4import static org.junit.Assert.fail;
     5
     6import java.awt.image.BufferedImage;
     7
     8import java.util.Arrays;
     9import java.util.HashMap;
     10import java.util.Map;
     11import java.util.Optional;
     12import java.util.function.IntFunction;
     13import java.util.regex.Matcher;
     14import java.util.regex.Pattern;
     15import java.util.stream.Collectors;
     16
     17/**
     18 * Utilities to aid in making assertions about images using regular expressions.
     19 */
     20public final class ImagePatternMatching {
     21    private ImagePatternMatching() {}
     22
     23    private static final Map<String, Pattern> patternCache = new HashMap<String, Pattern>();
     24
     25    private static Matcher imageStripPatternMatchInner(
     26        final BufferedImage image,
     27        final int columnOrRowIndex,
     28        IntFunction<String> paletteMapFn,
     29        final Map<Integer, String> paletteMap,
     30        Pattern pattern,
     31        final String patternString,
     32        final boolean isColumn,
     33        final boolean assertMatch
     34    ) {
     35        paletteMapFn = Optional.ofNullable(paletteMapFn)
     36            // using "#" as the default "unmapped" character as it can be used in regexes without escaping
     37            .orElse(i -> paletteMap.getOrDefault(i, "#"));
     38        pattern = Optional.ofNullable(pattern)
     39            .orElseGet(() -> patternCache.computeIfAbsent(patternString, k -> Pattern.compile(k)));
     40
     41        int[] columnOrRow = isColumn
     42            ? image.getRGB(columnOrRowIndex, 0, 1, image.getHeight(), null, 0, 1)
     43            : image.getRGB(0, columnOrRowIndex, image.getWidth(), 1, null, 0, image.getWidth());
     44
     45        String stringRepr = Arrays.stream(columnOrRow).mapToObj(paletteMapFn).collect(Collectors.joining());
     46        Matcher result = pattern.matcher(stringRepr);
     47
     48        if (assertMatch && !result.matches()) {
     49            System.err.println(String.format("Full strip failing to match pattern %s: %s", pattern, stringRepr));
     50            fail(String.format(
     51                "%s %d failed to match pattern %s",
     52                isColumn ? "Column" : "Row",
     53                columnOrRowIndex,
     54                pattern
     55            ));
     56        }
     57
     58        return result;
     59    }
     60
     61    /**
     62     * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMap}
     63     * against the regular expression described by {@code patternString}.
     64     *
     65     * @param image         image to take column from
     66     * @param colNumber     image column number for comparison
     67     * @param paletteMap    {@link Map} of {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     68     *                      is advised to only map colors to single characters. Colors with no corresponding entry in
     69     *                      the map are mapped to {@code #}.
     70     * @param patternString string representation of regular expression to match against. These are simply used to
     71     *                      construct a {@link Pattern} which is cached in case of re-use.
     72     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     73     * @return {@link Matcher} produced by matching attempt
     74     */
     75    public static Matcher columnMatch(
     76        final BufferedImage image,
     77        final int colNumber,
     78        final Map<Integer, String> paletteMap,
     79        final String patternString,
     80        final boolean assertMatch
     81    ) {
     82        return imageStripPatternMatchInner(
     83            image,
     84            colNumber,
     85            null,
     86            paletteMap,
     87            null,
     88            patternString,
     89            true,
     90            true
     91        );
     92    }
     93
     94    /**
     95     * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMapFn}
     96     * against the regular expression described by {@code patternString}.
     97     *
     98     * @param image         image to take column from
     99     * @param colNumber     image column number for comparison
     100     * @param paletteMapFn  function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     101     *                      is advised to only map colors to single characters.
     102     * @param patternString string representation of regular expression to match against. These are simply used to
     103     *                      construct a {@link Pattern} which is cached in case of re-use.
     104     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     105     * @return {@link Matcher} produced by matching attempt
     106     */
     107    public static Matcher columnMatch(
     108        final BufferedImage image,
     109        final int colNumber,
     110        final IntFunction<String> paletteMapFn,
     111        final String patternString,
     112        final boolean assertMatch
     113    ) {
     114        return imageStripPatternMatchInner(
     115            image,
     116            colNumber,
     117            paletteMapFn,
     118            null,
     119            null,
     120            patternString,
     121            true,
     122            true
     123        );
     124    }
     125
     126    /**
     127     * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMap}
     128     * against the regular expression {@code pattern}.
     129     *
     130     * @param image         image to take column from
     131     * @param colNumber     image column number for comparison
     132     * @param paletteMap    {@link Map} of {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     133     *                      is advised to only map colors to single characters. Colors with no corresponding entry in
     134     *                      the map are mapped to {@code #}.
     135     * @param pattern       regular expression to match against
     136     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     137     * @return {@link Matcher} produced by matching attempt
     138     */
     139    public static Matcher columnMatch(
     140        final BufferedImage image,
     141        final int colNumber,
     142        final Map<Integer, String> paletteMap,
     143        final Pattern pattern,
     144        final boolean assertMatch
     145    ) {
     146        return imageStripPatternMatchInner(
     147            image,
     148            colNumber,
     149            null,
     150            paletteMap,
     151            pattern,
     152            null,
     153            true,
     154            true
     155        );
     156    }
     157
     158    /**
     159     * Attempt to match column {@code colNumber}, once translated to characters according to {@code paletteMapFn}
     160     * against the regular expression {@code pattern}.
     161     *
     162     * @param image         image to take column from
     163     * @param colNumber     image column number for comparison
     164     * @param paletteMapFn  function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     165     *                      is advised to only map colors to single characters.
     166     * @param pattern       regular expression to match against
     167     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     168     * @return {@link Matcher} produced by matching attempt
     169     */
     170    public static Matcher columnMatch(
     171        final BufferedImage image,
     172        final int colNumber,
     173        final IntFunction<String> paletteMapFn,
     174        final Pattern pattern,
     175        final boolean assertMatch
     176    ) {
     177        return imageStripPatternMatchInner(
     178            image,
     179            colNumber,
     180            paletteMapFn,
     181            null,
     182            pattern,
     183            null,
     184            true,
     185            true
     186        );
     187    }
     188
     189    /**
     190     * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMap}
     191     * against the regular expression described by {@code patternString}.
     192     *
     193     * @param image         image to take row from
     194     * @param rowNumber     image row number for comparison
     195     * @param paletteMap    {@link Map} of {@code Integer}s (denoting the or in ARGB format) to {@link String}s. It
     196     *                      is advised to only map colors to single characters. Colors with no corresponding entry in
     197     *                      the map are mapped to {@code #}.
     198     * @param patternString string representation of regular expression to match against. These are simply used to
     199     *                      construct a {@link Pattern} which is cached in case of re-use.
     200     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     201     * @return {@link Matcher} produced by matching attempt
     202     */
     203    public static Matcher rowMatch(
     204        final BufferedImage image,
     205        final int rowNumber,
     206        final Map<Integer, String> paletteMap,
     207        final String patternString,
     208        final boolean assertMatch
     209    ) {
     210        return imageStripPatternMatchInner(
     211            image,
     212            rowNumber,
     213            null,
     214            paletteMap,
     215            null,
     216            patternString,
     217            false,
     218            true
     219        );
     220    }
     221
     222    /**
     223     * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMapFn}
     224     * against the regular expression described by {@code patternString}.
     225     *
     226     * @param image         image to take row from
     227     * @param rowNumber     image row number for comparison
     228     * @param paletteMapFn  function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     229     *                      is advised to only map colors to single characters.
     230     * @param patternString string representation of regular expression to match against. These are simply used to
     231     *                      construct a {@link Pattern} which is cached in case of re-use.
     232     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     233     * @return {@link Matcher} produced by matching attempt
     234     */
     235    public static Matcher rowMatch(
     236        final BufferedImage image,
     237        final int rowNumber,
     238        final IntFunction<String> paletteMapFn,
     239        final String patternString,
     240        final boolean assertMatch
     241    ) {
     242        return imageStripPatternMatchInner(
     243            image,
     244            rowNumber,
     245            paletteMapFn,
     246            null,
     247            null,
     248            patternString,
     249            false,
     250            true
     251        );
     252    }
     253
     254    /**
     255     * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMap}
     256     * against the regular expression {@code pattern}.
     257     *
     258     * @param image         image to take row from
     259     * @param rowNumber     image row number for comparison
     260     * @param paletteMap    {@link Map} of {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     261     *                      is advised to only map colors to single characters. Colors with no corresponding entry in
     262     *                      the map are mapped to {@code #}.
     263     * @param pattern       regular expression to match against
     264     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     265     * @return {@link Matcher} produced by matching attempt
     266     */
     267    public static Matcher rowMatch(
     268        final BufferedImage image,
     269        final int rowNumber,
     270        final Map<Integer, String> paletteMap,
     271        final Pattern pattern,
     272        final boolean assertMatch
     273    ) {
     274        return imageStripPatternMatchInner(
     275            image,
     276            rowNumber,
     277            null,
     278            paletteMap,
     279            pattern,
     280            null,
     281            false,
     282            true
     283        );
     284    }
     285
     286    /**
     287     * Attempt to match row {@code rowNumber}, once translated to characters according to {@code paletteMapFn}
     288     * against the regular expression {@code pattern}.
     289     *
     290     * @param image         image to take row from
     291     * @param rowNumber     image row number for comparison
     292     * @param paletteMapFn  function mapping {@code Integer}s (denoting the color in ARGB format) to {@link String}s. It
     293     *                      is advised to only map colors to single characters.
     294     * @param pattern       regular expression to match against
     295     * @param assertMatch   whether to raise an (informative) {@link AssertionFailedError} if no match is found.
     296     * @return {@link Matcher} produced by matching attempt
     297     */
     298    public static Matcher rowMatch(
     299        final BufferedImage image,
     300        final int rowNumber,
     301        final IntFunction<String> paletteMapFn,
     302        final Pattern pattern,
     303        final boolean assertMatch
     304    ) {
     305        return imageStripPatternMatchInner(
     306            image,
     307            rowNumber,
     308            paletteMapFn,
     309            null,
     310            pattern,
     311            null,
     312            false,
     313            true
     314        );
     315    }
     316}