Ticket #16010: v1-0003-add-ExtendedDialogMocker-WindowMocker.patch

File v1-0003-add-ExtendedDialogMocker-WindowMocker.patch, 8.9 KB (added by ris, 16 months ago)
  • new file test/unit/org/openstreetmap/josm/testutils/mockers/ExtendedDialogMocker.java

    From 12929766eaef398a802dd8242aa031b76e0ed9f2 Mon Sep 17 00:00:00 2001
    From: Robert Scott <code@humanleg.org.uk>
    Date: Tue, 13 Feb 2018 20:59:31 +0000
    Subject: [PATCH v1 3/5] add ExtendedDialogMocker, WindowMocker
    
    ---
     .../testutils/mockers/ExtendedDialogMocker.java    | 149 +++++++++++++++++++++
     .../josm/testutils/mockers/WindowMocker.java       |  39 ++++++
     2 files changed, 188 insertions(+)
     create mode 100644 test/unit/org/openstreetmap/josm/testutils/mockers/ExtendedDialogMocker.java
     create mode 100644 test/unit/org/openstreetmap/josm/testutils/mockers/WindowMocker.java
    
    diff --git a/test/unit/org/openstreetmap/josm/testutils/mockers/ExtendedDialogMocker.java b/test/unit/org/openstreetmap/josm/testutils/mockers/ExtendedDialogMocker.java
    new file mode 100644
    index 000000000..7d28ad65e
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.testutils;
     3
     4import static org.junit.Assert.fail;
     5
     6import java.awt.Component;
     7import java.awt.GraphicsEnvironment;
     8import java.util.Arrays;
     9import java.util.HashMap;
     10import java.util.Map;
     11import java.util.Optional;
     12import java.util.WeakHashMap;
     13
     14import org.openstreetmap.josm.gui.ExtendedDialog;
     15import org.openstreetmap.josm.tools.Logging;
     16
     17import mockit.Deencapsulation;
     18import mockit.Invocation;
     19import mockit.Mock;
     20import mockit.MockUp;
     21
     22/**
     23 * MockUp for {@link ExtendedDialog} allowing a test to pre-seed uses of {@link ExtendedDialog}
     24 * with mock "responses". This works best with {@link ExtendedDialog}s which have their contents set
     25 * through {@link ExtendedDialog#setContent(String)} as simple strings. In such a case, responses can
     26 * be defined through a mapping from content {@link String}s to button indexes ({@link Integer}s) or
     27 * button names ({@link String}s). Example:
     28 *
     29 * <pre>
     30 *      new ExtendedDialogMocker(ImmutableMap.<String, Object>builder()
     31 *          .put("JOSM version 8,001 required for plugin baz_plugin.", "Download Plugin")
     32 *          .put("JOSM version 7,001 required for plugin dummy_plugin.", "Cancel")
     33 *          .put("Are you sure you want to do foo bar?", ExtendedDialog.DialogClosedOtherwise)
     34 *          .build()
     35 *      );
     36 * </pre>
     37 *
     38 * Testing examples with more complicated contents would require overriding
     39 * {@link #getMockResult(ExtendedDialog)} with custom logic.
     40 *
     41 * The default {@link #getMockResult(ExtendedDialog)} will raise an
     42 * {@link junit.framework.AssertionFailedError} on an {@link ExtendedDialog} activation without a
     43 * matching mapping entry or if the named button doesn't exist.
     44 *
     45 * {@link #simpleStringContentMockResultMap} is exposed as a public field to allow for situations
     46 * where the desired result might need to be changed mid-test.
     47 */
     48public class ExtendedDialogMocker extends MockUp<ExtendedDialog> {
     49    /**
     50     * Because we're unable to add fields to the mocked class, we need to use this external global
     51     * mapping to be able to keep a note of the most recently set simple String contents of each
     52     * {@link ExtendedDialog} instance - {@link ExtendedDialog} doesn't store this information
     53     * itself, instead converting it directly into the embedded {@link Component}.
     54     */
     55    protected final Map<ExtendedDialog, String> simpleStringContentMemo = new WeakHashMap<ExtendedDialog, String>();
     56
     57    /**
     58     * Mapping to {@link Object}s so response button can be specified by String (label) or Integer -
     59     * sorry, no type safety as java doesn't support union types
     60     */
     61    final Map<String, Object> simpleStringContentMockResultMap;
     62
     63    /**
     64     * Construct an {@link ExtendedDialogMocker} with an empty {@link #simpleStringContentMockResultMap}.
     65     */
     66    public ExtendedDialogMocker() {
     67        this(new HashMap<String, Object>());
     68    }
     69
     70    /**
     71     * Construct an {@link ExtendedDialogMocker} with the provided {@link #simpleStringContentMockResultMap}.
     72     * @param simpleStringContentMockResultMap mapping of {@link ExtendedDialog} string contents to
     73     *      result button label or integer index.
     74     */
     75    public ExtendedDialogMocker(final Map<String, Object> simpleStringContentMockResultMap) {
     76        if (GraphicsEnvironment.isHeadless()) {
     77            new WindowMocker();
     78        }
     79        this.simpleStringContentMockResultMap = simpleStringContentMockResultMap;
     80    }
     81
     82    protected String getContentDescription(final String stringContent) {
     83        return Optional.ofNullable(stringContent)
     84            .map(sc -> "\""+sc+"\"")
     85            .orElse("[no content or content non-simple]");
     86    }
     87
     88    protected int getMockResult(final ExtendedDialog instance) {
     89        final String stringContent = this.simpleStringContentMemo.get(instance);
     90        final Object result = this.simpleStringContentMockResultMap.get(stringContent);
     91
     92        if (result == null) {
     93            fail(
     94                "Unexpected ExtendedDialog content: " + this.getContentDescription(stringContent)
     95            );
     96        } else if (result instanceof Integer) {
     97            return (Integer) result;
     98        } else if (result instanceof String) {
     99            final String[] bTexts = Deencapsulation.getField(instance, "bTexts");
     100            final int position = Arrays.asList(bTexts).indexOf((String) result);
     101            if (position == -1) {
     102                fail("Unable to find button labeled \"" + result + "\". Instead found: " + Arrays.toString(bTexts));
     103            }
     104            // buttons are numbered with 1-based indexing
     105            return 1 + position;
     106        }
     107
     108        throw new IllegalArgumentException(
     109            "ExtendedDialog contents mapped to unsupported type of Object: " + result
     110        );
     111    }
     112
     113    @Mock
     114    private void setupDialog(final Invocation invocation) {
     115        if (!GraphicsEnvironment.isHeadless()) {
     116            invocation.proceed();
     117        }
     118        // else do nothing - WindowMocker-ed Windows doesn't work well enough for some of the
     119        // component constructions
     120    }
     121
     122    @Mock
     123    private void setVisible(final Invocation invocation, final boolean value) {
     124        if (value == true) {
     125            final int mockResult = this.getMockResult((ExtendedDialog) invocation.getInvokedInstance());
     126            Deencapsulation.setField((ExtendedDialog) invocation.getInvokedInstance(), "result", mockResult);
     127            Logging.info(
     128                "ExtendedDialogMocker answering {0} to ExtendedDialog with content {1}",
     129                mockResult,
     130                this.getContentDescription(this.simpleStringContentMemo.get(invocation.getInvokedInstance()))
     131            );
     132        }
     133    }
     134
     135    @Mock
     136    private ExtendedDialog setContent(final Invocation invocation, final String message) {
     137        final ExtendedDialog retval = invocation.proceed(message);
     138        // must set this *after* the regular invocation else that will fall through to
     139        // setContent(Component, boolean) which would overwrite it (with null)
     140        this.simpleStringContentMemo.put((ExtendedDialog) invocation.getInvokedInstance(), message);
     141        return retval;
     142    }
     143
     144    @Mock
     145    private ExtendedDialog setContent(final Invocation invocation, final Component content, final boolean placeContentInScrollPane) {
     146        this.simpleStringContentMemo.put((ExtendedDialog) invocation.getInvokedInstance(), null);
     147        return invocation.proceed(content, placeContentInScrollPane);
     148    }
     149}
  • new file test/unit/org/openstreetmap/josm/testutils/mockers/WindowMocker.java

    diff --git a/test/unit/org/openstreetmap/josm/testutils/mockers/WindowMocker.java b/test/unit/org/openstreetmap/josm/testutils/mockers/WindowMocker.java
    new file mode 100644
    index 000000000..64440647a
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.testutils;
     3
     4import java.awt.Frame;
     5import java.awt.GraphicsConfiguration;
     6import java.awt.Window;
     7
     8import mockit.Invocation;
     9import mockit.Mock;
     10import mockit.MockUp;
     11
     12/**
     13 * MockUp for a {@link Window} which simply (and naively) makes its constructor(s) a no-op. This has
     14 * the advantage of removing the isHeadless check. Though if course it also leaves you with
     15 * uninintialized objects, and so of course they don't *necessarily* work properly. But often they
     16 * work *just enough* to behave how a test needs them to. Exercise left to the reader to discover
     17 * the limits here.
     18 */
     19public class WindowMocker extends MockUp<Window> {
     20    @Mock
     21    private void $init(final Invocation invocation) {
     22    }
     23
     24    @Mock
     25    private void $init(final Invocation invocation, final Window window) {
     26    }
     27
     28    @Mock
     29    private void $init(final Invocation invocation, final Frame frame) {
     30    }
     31
     32    @Mock
     33    private void $init(final Invocation invocation, final GraphicsConfiguration gc) {
     34    }
     35
     36    @Mock
     37    private void $init(final Invocation invocation, final Window window, final GraphicsConfiguration gc) {
     38    }
     39}