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

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

    From 9d73b77030440debfbe6b30c3e81334e7db50c05 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 v3 3/3] add ImagePatternMatching testutil, example use in
     MinimapDialogTest
    
    ---
     .../josm/gui/dialogs/MinimapDialogTest.java        | 130 +++++++++
     .../josm/testutils/ImagePatternMatching.java       | 316 +++++++++++++++++++++
     2 files changed, 446 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..50197ac79 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;
     31import org.openstreetmap.josm.gui.MainApplication;
     32import org.openstreetmap.josm.gui.MapView;
    2533import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
    2634import org.openstreetmap.josm.gui.bbox.SourceButton;
     35import org.openstreetmap.josm.gui.layer.LayerManagerTest.TestLayer;
    2736import org.openstreetmap.josm.gui.util.GuiHelper;
     37import org.openstreetmap.josm.testutils.ImagePatternMatching;
    2838import org.openstreetmap.josm.testutils.JOSMTestRules;
    2939
    3040import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    3141
    3242import org.awaitility.Awaitility;
    3343
     44import com.google.common.collect.ImmutableMap;
     45
    3446/**
    3547 * Unit tests of {@link MinimapDialog} class.
    3648 */
    public class MinimapDialogTest {  
    251263
    252264        assertEquals(0xffffffff, paintedSlippyMap.getRGB(0, 0));
    253265    }
     266
     267    /**
     268     * test viewport marker rectangle matches the mapView's aspect ratio
     269     * @throws Exception if any error occurs
     270     */
     271    @Test
     272    public void testViewportAspectRatio() throws Exception {
     273        // Add a test layer to the layer manager to get the MapFrame & MapView
     274        MainApplication.getLayerManager().addLayer(new TestLayer());
     275
     276        Main.pref.put("slippy_map_chooser.mapstyle", "White Tiles");
     277        // ensure projection matches JMapViewer's
     278        Main.setProjection(Projections.getProjectionByCode("EPSG:3857"));
     279
     280        MapView mapView = MainApplication.getMap().mapView;
     281        GuiHelper.runInEDTAndWaitWithException(() -> {
     282            mapView.setVisible(true);
     283            mapView.addNotify();
     284            mapView.doLayout();
     285            // ensure we have a square mapView viewport
     286            mapView.setBounds(0, 0, 350, 350);
     287        });
     288
     289        this.setUpMiniMap();
     290
     291        // attempt to set viewport to cover a non-square area
     292        mapView.zoomTo(new Bounds(26.27, -18.23, 26.275, -18.229));
     293
     294        // an initial paint operation is required to trigger the tile fetches
     295        this.paintSlippyMap();
     296
     297        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     298
     299        this.paintSlippyMap();
     300
     301        Map<Integer, String> paletteMap = ImmutableMap.<Integer, String>builder()
     302            .put(0xffffffff, "w")
     303            .put(0xff000000, "b")
     304            .put(0xfff0d1d1, "p")
     305            .build();
     306
     307        Matcher rowMatcher = ImagePatternMatching.rowMatch(
     308            paintedSlippyMap,
     309            paintedSlippyMap.getHeight()/2,
     310            paletteMap,
     311            "^(w+)b(p+)b(w+)$",
     312            true
     313        );
     314
     315        // (within a tolerance for numerical error) the number of pixels on the left of the viewport marker
     316        // should equal the number on the right
     317        assertTrue(
     318            "Viewport marker not horizontally centered",
     319            Math.abs(rowMatcher.group(1).length() - rowMatcher.group(3).length()) < 4
     320        );
     321
     322        Matcher colMatcher = ImagePatternMatching.columnMatch(
     323            paintedSlippyMap,
     324            paintedSlippyMap.getWidth()/2,
     325            paletteMap,
     326            "^(w+)b(p+)b(w+)$",
     327            true
     328        );
     329
     330        // (within a tolerance for numerical error) the number of pixels on the top of the viewport marker
     331        // should equal the number on the bottom
     332        assertTrue(
     333            "Viewport marker not vertically centered",
     334            Math.abs(colMatcher.group(1).length() - colMatcher.group(3).length()) < 4
     335        );
     336
     337        // (within a tolerance for numerical error) the viewport marker should be square
     338        assertTrue(
     339            "Viewport marker not square",
     340            Math.abs(colMatcher.group(2).length() - rowMatcher.group(2).length()) < 4
     341        );
     342
     343        // now change the mapView size
     344        GuiHelper.runInEDTAndWaitWithException(() -> {
     345            mapView.setBounds(0, 0, 150, 300);
     346            Arrays.stream(mapView.getComponentListeners()).forEach(
     347                cl -> cl.componentResized(new ComponentEvent(mapView, ComponentEvent.COMPONENT_RESIZED))
     348            );
     349        });
     350        // minimap doesn't (yet?) listen for component resize events to update its viewport marker, so
     351        // trigger a zoom change
     352        mapView.zoomTo(mapView.getCenter());
     353        this.paintSlippyMap();
     354
     355        rowMatcher = ImagePatternMatching.rowMatch(
     356            paintedSlippyMap,
     357            paintedSlippyMap.getHeight()/2,
     358            paletteMap,
     359            "^(w+)b(p+)b(w+)$",
     360            true
     361        );
     362        assertTrue(
     363            "Viewport marker not horizontally centered",
     364            Math.abs(rowMatcher.group(1).length() - rowMatcher.group(3).length()) < 4
     365        );
     366
     367        colMatcher = ImagePatternMatching.columnMatch(
     368            paintedSlippyMap,
     369            paintedSlippyMap.getWidth()/2,
     370            paletteMap,
     371            "^(w+)b(p+)b(w+)$",
     372            true
     373        );
     374        assertTrue(
     375            "Viewport marker not vertically centered",
     376            Math.abs(colMatcher.group(1).length() - colMatcher.group(3).length()) < 4
     377        );
     378
     379        assertTrue(
     380            "Viewport marker not 2:1 aspect ratio",
     381            Math.abs(colMatcher.group(2).length() - (rowMatcher.group(2).length()*2.0)) < 5
     382        );
     383    }
    254384}
  • 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}