From 30b7e6987001669a6b4b03bb5c5696d282b9b1e5 Mon Sep 17 00:00:00 2001 From: hneemann Date: Sat, 20 Jan 2018 15:48:06 +0100 Subject: [PATCH] added some more gui test cases --- README.md | 2 +- .../digital/draw/library/ElementLibrary.java | 7 + .../testing/TestCaseDescriptionDialog.java | 5 + .../digital/integration/GuiTester.java | 132 +++++++++++++++--- .../digital/integration/TestInGUI.java | 123 ++++++++++++++-- .../resources/dig/manualError/11_editTest.dig | 61 ++++++++ 6 files changed, 300 insertions(+), 30 deletions(-) create mode 100644 src/test/resources/dig/manualError/11_editTest.dig diff --git a/README.md b/README.md index 7e3264236..b36c4c145 100644 --- a/README.md +++ b/README.md @@ -193,5 +193,5 @@ If you want to build Digital from the source code: * Before you send a pull request, make sure that at least `mvn install` runs without errors. * Don't introduce new findbugs issues. * Try to keep the test coverage high. The target is 80% test coverage at all non GUI components. -* So far, there are only a few GUI tests, so that the overall test coverage is only slightly below 70%. +* So far, there are only a few GUI tests, so that the overall test coverage is only slightly above 70%. Try to keep the amount of untested GUI code low. diff --git a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java index 3367c1a07..d5f61eb85 100644 --- a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java +++ b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java @@ -285,6 +285,13 @@ public class ElementLibrary implements Iterable this.shapeFactory = shapeFactory; } + /** + * @return the shape factory + */ + public ShapeFactory getShapeFactory() { + return shapeFactory; + } + /** * @return the node with the custom elements */ diff --git a/src/main/java/de/neemann/digital/gui/components/testing/TestCaseDescriptionDialog.java b/src/main/java/de/neemann/digital/gui/components/testing/TestCaseDescriptionDialog.java index e36b44f66..36f465a21 100644 --- a/src/main/java/de/neemann/digital/gui/components/testing/TestCaseDescriptionDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/testing/TestCaseDescriptionDialog.java @@ -19,6 +19,7 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.IOException; +import java.util.HashSet; /** * Dialog to show and edit the testing data source. @@ -46,6 +47,10 @@ public class TestCaseDescriptionDialog extends JDialog { JTextArea text = new JTextArea(data.getDataString(), 30, 50); text.setFont(new Font(Font.MONOSPACED, Font.PLAIN, Screen.getInstance().getFontSize())); + HashSet set = new HashSet<>(text.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); + set.add(KeyStroke.getKeyStroke("F1")); + text.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set); + JScrollPane scrollPane = new JScrollPane(text); getContentPane().add(scrollPane); scrollPane.setRowHeaderView(new TextLineNumber(text, 3)); diff --git a/src/test/java/de/neemann/digital/integration/GuiTester.java b/src/test/java/de/neemann/digital/integration/GuiTester.java index 93bf675e9..6387ab843 100644 --- a/src/test/java/de/neemann/digital/integration/GuiTester.java +++ b/src/test/java/de/neemann/digital/integration/GuiTester.java @@ -2,8 +2,10 @@ package de.neemann.digital.integration; import de.neemann.digital.draw.elements.Circuit; import de.neemann.digital.gui.Main; +import jdk.nashorn.internal.scripts.JD; import junit.framework.Assert; +import javax.sql.rowset.JdbcRowSet; import javax.swing.FocusManager; import javax.swing.*; import javax.swing.text.JTextComponent; @@ -14,6 +16,7 @@ import java.io.IOException; import java.util.ArrayList; import static java.awt.event.InputEvent.*; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class GuiTester { @@ -79,7 +82,7 @@ public class GuiTester { for (int i = 0; i < s.length(); i++) { final char ch = s.charAt(i); int code = KeyEvent.getExtendedKeyCodeForChar(ch); - if (ch == '/') { + if (ch == '/' || Character.isUpperCase(ch)) { gt.keyPress(KeyEvent.VK_SHIFT); gt.keyPress(code); gt.keyRelease(code); @@ -114,6 +117,11 @@ public class GuiTester { return this; } + public GuiTester mouseClick(int x, int y, int buttons) { + add((gs) -> gs.mouseClickNow(x, y, buttons)); + return this; + } + private boolean isDisplay() { final boolean isDisplay = !GraphicsEnvironment.isHeadless(); if (!isDisplay) @@ -194,6 +202,28 @@ public class GuiTester { if ((mod & ALT_DOWN_MASK) != 0) keyRelease(KeyEvent.VK_ALT); } + /** + * Clicks the mouse + * + * @param x the x coordinate relative to the topmost window + * @param y the x coordinate relative to the topmost window + * @param buttons the button mask + */ + public void mouseClickNow(int x, int y, int buttons) { + Container activeWindow = FocusManager.getCurrentManager().getActiveWindow(); + Point p = new Point(x, y); + + if (activeWindow instanceof JDialog) + activeWindow = ((JDialog) activeWindow).getContentPane(); + else if (activeWindow instanceof JFrame) + activeWindow = ((JFrame) activeWindow).getContentPane(); + + SwingUtilities.convertPointToScreen(p, activeWindow); + robot.mouseMove(p.x, p.y); + robot.mousePress(buttons); + robot.mouseRelease(buttons); + } + private void keyPress(int keyCode) { robot.keyPress(keyCode); } @@ -248,54 +278,113 @@ public class GuiTester { + activeWindow.getClass().getSimpleName() + ">", expectedClass.isAssignableFrom(activeWindow.getClass())); - checkWindow((W) activeWindow); + checkWindow(guiTester, (W) activeWindow); } /** * Is called if the expected window was found. * Override this method to implement own tests of the window found. * - * @param window the found window of expected type + * @param guiTester the GuiTester + * @param window the found window of expected type */ - public void checkWindow(W window) { + public void checkWindow(GuiTester guiTester, W window) { } } + /** + * Traverses all the components in the topmost window. + */ + public static abstract class ComponentTraverse extends WindowCheck { + + /** + * creates a new instance + */ + public ComponentTraverse(Class expected) { + super(expected); + } + + @Override + public void checkWindow(GuiTester guiTester, W dialog) { + traverseComponents(dialog); + } + + void traverseComponents(Container cp) { + for (int i = 0; i < cp.getComponentCount(); i++) { + Component component = cp.getComponent(i); + visit(component); + if (component instanceof Container) { + traverseComponents((Container) component); + } + } + } + + public abstract void visit(Component component); + } + + /** * Checks if the topmost dialog contains the given strings. */ - public static class CheckDialogText extends WindowCheck { + public static class CheckTextInWindow extends ComponentTraverse { private final String[] expected; + private StringBuilder text; /** * Checks if the topmost dialog contains the given strings. * * @param expected test fails if one of the strings is missing */ - public CheckDialogText(String... expected) { - super(JDialog.class); + public CheckTextInWindow(Class expectedClass, String... expected) { + super(expectedClass); this.expected = expected; + text = new StringBuilder(); } @Override - public void checkWindow(JDialog dialog) { - StringBuilder text = new StringBuilder(); - collectText(dialog.getContentPane(), text); + public void checkWindow(GuiTester guiTester, W window) { + super.checkWindow(guiTester, window); String t = text.toString(); for (String e : expected) - Assert.assertTrue(t + " does not contain " + e, t.contains(e)); + Assert.assertTrue("<" + t + "> does not contain <" + e + ">", t.contains(e)); } - void collectText(Container cp, StringBuilder text) { - for (int i = 0; i < cp.getComponentCount(); i++) { - Component component = cp.getComponent(i); - if (component instanceof JLabel) { - text.append(((JLabel) component).getText()); - } else if (component instanceof JTextComponent) { - text.append((((JTextComponent) component).getText())); - } else if (component instanceof Container) { - collectText((Container) component, text); - } + @Override + public void visit(Component component) { + if (component instanceof JTabbedPane) { + JTabbedPane t = ((JTabbedPane) component); + for (int j = 0; j < t.getTabCount(); j++) + text.append(t.getTitleAt(j)); + } else if (component instanceof JLabel) { + text.append(((JLabel) component).getText()); + } else if (component instanceof JTextComponent) { + text.append((((JTextComponent) component).getText())); + } + } + } + + public static class CheckTableRows extends ComponentTraverse { + private final int expectedRows; + private int rows; + private int tableCount; + + public CheckTableRows(Class expectedClass, int expectedRows) { + super(expectedClass); + this.expectedRows = expectedRows; + } + + @Override + public void checkWindow(GuiTester guiTester, W window) { + super.checkWindow(guiTester, window); + assertEquals("row count does not match", expectedRows, rows); + assertEquals("only one table allowed", 1, tableCount); + } + + @Override + public void visit(Component component) { + if (component instanceof JTable) { + rows = ((JTable) component).getModel().getRowCount(); + tableCount++; } } } @@ -311,4 +400,5 @@ public class GuiTester { activeWindow.dispose(); } } + } diff --git a/src/test/java/de/neemann/digital/integration/TestInGUI.java b/src/test/java/de/neemann/digital/integration/TestInGUI.java index 07ed5094f..2afbcf9a7 100644 --- a/src/test/java/de/neemann/digital/integration/TestInGUI.java +++ b/src/test/java/de/neemann/digital/integration/TestInGUI.java @@ -2,17 +2,28 @@ package de.neemann.digital.integration; import de.neemann.digital.analyse.expression.Expression; import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.elements.VisualElement; +import de.neemann.digital.draw.elements.Wire; +import de.neemann.digital.draw.graphics.GraphicMinMax; +import de.neemann.digital.draw.graphics.Vector; import de.neemann.digital.gui.Main; import de.neemann.digital.gui.components.karnaugh.KarnaughMapDialog; import de.neemann.digital.gui.components.table.AllSolutionsDialog; import de.neemann.digital.gui.components.table.ExpressionListenerStore; import de.neemann.digital.gui.components.table.TableDialog; +import de.neemann.digital.gui.components.testing.ValueTableDialog; import de.neemann.digital.lang.Lang; import de.neemann.gui.ErrorMessage; import junit.framework.TestCase; +import javax.swing.*; +import java.awt.*; +import java.awt.event.InputEvent; +import java.io.File; import java.util.List; +import static de.neemann.digital.draw.shapes.GenericShape.SIZE; + /** * These tests are excluded from the maven build because gui tests are sometimes fragile. * They may not behave as expected on all systems. @@ -109,7 +120,7 @@ public class TestInGUI extends TestCase { .delay(500) .add(new GuiTester.WindowCheck(KarnaughMapDialog.class) { @Override - public void checkWindow(KarnaughMapDialog kMapDialog) { + public void checkWindow(GuiTester guiTester, KarnaughMapDialog kMapDialog) { List res = kMapDialog.getResults(); assertEquals(1, res.size()); Expression r = res.get(0).getExpression(); @@ -120,7 +131,7 @@ public class TestInGUI extends TestCase { .press("F2") .add(new GuiTester.WindowCheck
(Main.class) { @Override - public void checkWindow(Main main) { + public void checkWindow(GuiTester guiTester, Main main) { Circuit c = main.getCircuitComponent().getCircuit(); assertEquals(4, c.getElements().size()); } @@ -143,7 +154,7 @@ public class TestInGUI extends TestCase { .delay(500) .add(new GuiTester.WindowCheck
(Main.class) { @Override - public void checkWindow(Main main) { + public void checkWindow(GuiTester guiTester, Main main) { Circuit c = main.getCircuitComponent().getCircuit(); assertEquals(7, c.getElements().size()); } @@ -224,9 +235,9 @@ public class TestInGUI extends TestCase { .press("DOWN", 2) .press("ENTER") .delay(500) - .add(new GuiTester.WindowCheck(AllSolutionsDialog.class){ + .add(new GuiTester.WindowCheck(AllSolutionsDialog.class) { @Override - public void checkWindow(AllSolutionsDialog asd) { + public void checkWindow(GuiTester guiTester, AllSolutionsDialog asd) { asd.getParent().requestFocus(); } }) @@ -260,6 +271,25 @@ public class TestInGUI extends TestCase { .execute(); } + public void testDraw() { + new GuiTester() + .add(new DrawCircuit("../../main/dig/sequential/JK-MS.dig")) + .press("F8") + .delay(500) + .add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, "ok")) + .add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 8)) + .add(new GuiTester.CloseTopMost()) + .press("control typed z",65) + .delay(1000) + .press("control typed y",65) + .press("F8") + .delay(500) + .add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, "ok")) + .add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 8)) + .add(new GuiTester.CloseTopMost()) + .execute(); + } + public void testHardware() { new GuiTester("dig/manualError/10_hardware.dig") .press("F9") @@ -278,7 +308,28 @@ public class TestInGUI extends TestCase { .typeTempFile("test") .press("ENTER") .delay(3000) - .add(new GuiTester.CheckDialogText("Design fits successfully")) + .add(new GuiTester.CheckTextInWindow<>(JDialog.class, "Design fits successfully")) + .add(new GuiTester.CloseTopMost()) + .add(new GuiTester.CloseTopMost()) + .execute(); + } + + public void testTestEditor() { + new GuiTester("dig/manualError/11_editTest.dig") + .mouseClick(200, 200, InputEvent.BUTTON3_MASK) + .type("testIdentzz") + .press("TAB") + .press("SPACE") + .type("A B C\n0 0 0\n0 1 0\n1 0 0\n1 1 1") + .press("F1") + .press("TAB") + .press("SPACE") + .press("TAB", 4) + .press("SPACE") + .press("F8") + .delay(500) + .add(new GuiTester.CheckTextInWindow<>(ValueTableDialog.class, "testIdentzz", "ok")) + .add(new GuiTester.CheckTableRows<>(ValueTableDialog.class, 4)) .add(new GuiTester.CloseTopMost()) .add(new GuiTester.CloseTopMost()) .execute(); @@ -293,7 +344,7 @@ public class TestInGUI extends TestCase { } @Override - public void checkWindow(ErrorMessage.ErrorDialog errorDialog) { + public void checkWindow(GuiTester guiTester, ErrorMessage.ErrorDialog errorDialog) { String errorMessage = errorDialog.getErrorMessage(); for (String e : expected) assertTrue(errorMessage + " does not contain " + e, errorMessage.contains(e)); @@ -309,7 +360,7 @@ public class TestInGUI extends TestCase { } @Override - public void checkWindow(TableDialog td) { + public void checkWindow(GuiTester guiTester, TableDialog td) { ExpressionListenerStore exp = td.getLastGeneratedExpressions(); assertEquals(1, exp.getResults().size()); Expression res = exp.getResults().get(0).getExpression(); @@ -335,4 +386,60 @@ public class TestInGUI extends TestCase { } } } + + private class DrawCircuit extends GuiTester.WindowCheck
{ + private final String filename; + + public DrawCircuit(String filename) { + super(Main.class); + this.filename = filename; + } + + @Override + public void checkWindow(GuiTester guiTester, Main main) { + File file = new File(Resources.getRoot(), filename); + try { + Circuit circuit = Circuit.loadCircuit(file, main.getCircuitComponent().getLibrary().getShapeFactory()); + + int xMin = Integer.MAX_VALUE; + int yMin = Integer.MAX_VALUE; + for (Wire w : circuit.getWires()) { + if (w.p1.x < xMin) xMin = w.p1.x; + if (w.p2.x < xMin) xMin = w.p2.x; + if (w.p1.y < yMin) yMin = w.p1.y; + if (w.p2.y < yMin) yMin = w.p2.y; + } + + Point loc = main.getCircuitComponent().getLocation(); + xMin -= loc.x + SIZE * 5; + yMin -= loc.y + SIZE * 2; + + for (Wire w : circuit.getWires()) { + guiTester.mouseClickNow(w.p1.x - xMin, w.p1.y - yMin, InputEvent.BUTTON1_MASK); + Thread.sleep(100); + if (w.p1.x != w.p2.x && w.p1.y != w.p2.y) + guiTester.typeNow("typed d"); + + guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON1_MASK); + guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON3_MASK); + Thread.sleep(100); + } + + for (VisualElement v : circuit.getElements()) { + Vector pos = v.getPos(); + v.setPos(new Vector(0, 0)); + final GraphicMinMax minMax = v.getMinMax(false); + pos = pos.add(minMax.getMax()); + main.getCircuitComponent().setPartToInsert(v); + guiTester.mouseClickNow(pos.x - xMin, pos.y - yMin, InputEvent.BUTTON1_MASK); + Thread.sleep(200); + } + + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } diff --git a/src/test/resources/dig/manualError/11_editTest.dig b/src/test/resources/dig/manualError/11_editTest.dig new file mode 100644 index 000000000..4e8f7ba47 --- /dev/null +++ b/src/test/resources/dig/manualError/11_editTest.dig @@ -0,0 +1,61 @@ + + + 1 + + + + Testcase + + + + + And + + + + + In + + + Label + A + + + + + + In + + + Label + B + + + + + + Out + + + Label + C + + + + + + + + + + + + + + + + + + + + \ No newline at end of file