simplified gui tests

This commit is contained in:
hneemann 2018-01-20 11:54:22 +01:00
parent e2f272f604
commit d8ad1d1fe1
3 changed files with 196 additions and 147 deletions

View File

@ -1,16 +1,17 @@
Distribution Checklist
Checklist to be filled out before software is distributed.
Checklist to be filled out before software is released.
This is to ensure that at least the most important GUI functions
work properly.
Preparation
[ ] Don't use the 'experimental mode'!
[ ] Remove jar library from settings.
[ ] Make sure ghdl is installed.
[ ] Run the GUI tests! (digital/src/test/java/de/neemann/digital/integration/TestInGUI.java)
General
[ ] build a MS-JK-FF with diagonal wires, use Copy&Paste and 'L' shortcuts
[ ] build a NOR based MS-JK-FF with diagonal wires, use Copy&Paste and 'L' shortcuts
[ ] save circuit
[ ] copy test case from the example (JK-MS.dig) to the created circuit
[ ] run tests
@ -36,4 +37,4 @@ Custom Jar Components
An error message should be shown.
[ ] Create the jar and select it in the settings.
Now load the example circuit. It should work.
[ ] Update the plugin example after distribution
[ ] Update the plugin example after release.

View File

@ -4,6 +4,7 @@ import de.neemann.digital.draw.elements.Circuit;
import de.neemann.digital.gui.Main;
import junit.framework.Assert;
import javax.swing.FocusManager;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
@ -13,13 +14,14 @@ import java.io.IOException;
import java.util.ArrayList;
import static java.awt.event.InputEvent.*;
import static org.junit.Assert.fail;
public class GuiTester {
private static final long SLEEP_TIME = 200;
private final ArrayList<Runnable> runnableList;
private Main main;
private Robot robot;
private String filename;
private Robot robot;
public GuiTester() {
this(null);
@ -35,16 +37,21 @@ public class GuiTester {
return this;
}
public GuiTester use(GuiTester tester) {
runnableList.addAll(tester.runnableList);
return this;
}
public GuiTester delay(int ms) {
add((GuiTester guiTester) -> Thread.sleep(ms));
add((gt) -> Thread.sleep(ms));
return this;
}
public GuiTester press(String key, int n) {
KeyStroke stroke = strokeFromString(key);
add(((GuiTester guiTester) -> {
add(((gt) -> {
for (int i = 0; i < n; i++)
pressNow(stroke);
gt.typeNow(stroke);
}));
return this;
}
@ -57,24 +64,29 @@ public class GuiTester {
return addStroke(KeyStroke.getKeyStroke(c));
}
public GuiTester typeTempFile(String name) throws IOException {
File f = File.createTempFile(name, ".dig");
public GuiTester typeTempFile(String name) {
File f = null;
try {
f = File.createTempFile(name, ".dig");
} catch (IOException e) {
fail(e.getMessage());
}
return type(f.getPath());
}
public GuiTester type(String s) {
add(((GuiTester guiTester) -> {
add(((gt) -> {
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
int code = KeyEvent.getExtendedKeyCodeForChar(ch);
if (ch == '/') {
robot.keyPress(KeyEvent.VK_SHIFT);
robot.keyPress(code);
robot.keyRelease(code);
robot.keyRelease(KeyEvent.VK_SHIFT);
gt.keyPress(KeyEvent.VK_SHIFT);
gt.keyPress(code);
gt.keyRelease(code);
gt.keyRelease(KeyEvent.VK_SHIFT);
} else {
robot.keyPress(code);
robot.keyRelease(code);
gt.keyPress(code);
gt.keyRelease(code);
}
}
}));
@ -83,9 +95,7 @@ public class GuiTester {
private GuiTester addStroke(KeyStroke stroke) {
Assert.assertNotNull("key stroke null is not allowed", stroke);
add((GuiTester guiTester) -> {
pressNow(stroke);
});
add((gt) -> gt.typeNow(stroke));
return this;
}
@ -111,76 +121,121 @@ public class GuiTester {
return isDisplay;
}
public void execute() throws Exception {
/**
* executes the test
*/
public void execute() {
if (isDisplay()) {
SwingUtilities.invokeAndWait(() -> {
if (filename != null) {
File file = new File(Resources.getRoot(), filename);
main = new Main.MainBuilder().setFileToOpen(file).build();
} else
main = new Main.MainBuilder().setCircuit(new Circuit()).build();
main.setVisible(true);
});
Thread.sleep(500);
try {
robot = new Robot();
robot.setAutoWaitForIdle(true);
int step = 0;
for (Runnable r : runnableList) {
if (step > 0) {
System.err.print("-");
Thread.sleep(SLEEP_TIME);
SwingUtilities.invokeAndWait(() -> {
if (filename != null) {
File file = new File(Resources.getRoot(), filename);
main = new Main.MainBuilder().setFileToOpen(file).build();
} else
main = new Main.MainBuilder().setCircuit(new Circuit()).build();
main.setVisible(true);
});
Thread.sleep(500);
try {
robot = new Robot();
robot.setAutoWaitForIdle(true);
int step = 0;
for (Runnable r : runnableList) {
if (step > 0) {
System.err.print("-");
Thread.sleep(SLEEP_TIME);
}
step++;
System.err.print(step);
r.run(this);
}
step++;
System.err.print(step);
r.run(this);
} finally {
SwingUtilities.invokeAndWait(() -> main.dispose());
}
} finally {
SwingUtilities.invokeAndWait(() -> main.dispose());
System.err.println();
} catch (Exception e) {
e.printStackTrace();
fail();
}
System.err.println();
}
}
private KeyStroke strokeFromString(String key) {
private static KeyStroke strokeFromString(String key) {
final KeyStroke keyStroke = KeyStroke.getKeyStroke(key);
Assert.assertNotNull("invalid key code <" + key + ">", keyStroke);
return keyStroke;
}
public void pressNow(String key) {
pressNow(strokeFromString(key));
/**
* Types the given key
*
* @param key the key to type
*/
public void typeNow(String key) {
typeNow(strokeFromString(key));
}
public void pressNow(KeyStroke stroke) {
/**
* Types the given key
*
* @param stroke the key to type
*/
public void typeNow(KeyStroke stroke) {
int mod = stroke.getModifiers();
if ((mod & SHIFT_DOWN_MASK) != 0) robot.keyPress(KeyEvent.VK_SHIFT);
if ((mod & CTRL_DOWN_MASK) != 0) robot.keyPress(KeyEvent.VK_CONTROL);
if ((mod & ALT_DOWN_MASK) != 0) robot.keyPress(KeyEvent.VK_ALT);
if ((mod & SHIFT_DOWN_MASK) != 0) keyPress(KeyEvent.VK_SHIFT);
if ((mod & CTRL_DOWN_MASK) != 0) keyPress(KeyEvent.VK_CONTROL);
if ((mod & ALT_DOWN_MASK) != 0) keyPress(KeyEvent.VK_ALT);
int keyCode = stroke.getKeyCode();
if (keyCode == 0) keyCode = KeyEvent.getExtendedKeyCodeForChar(stroke.getKeyChar());
robot.keyPress(keyCode);
robot.keyRelease(keyCode);
if ((mod & SHIFT_DOWN_MASK) != 0) robot.keyRelease(KeyEvent.VK_SHIFT);
if ((mod & CTRL_DOWN_MASK) != 0) robot.keyRelease(KeyEvent.VK_CONTROL);
if ((mod & ALT_DOWN_MASK) != 0) robot.keyRelease(KeyEvent.VK_ALT);
keyPress(keyCode);
keyRelease(keyCode);
if ((mod & SHIFT_DOWN_MASK) != 0) keyRelease(KeyEvent.VK_SHIFT);
if ((mod & CTRL_DOWN_MASK) != 0) keyRelease(KeyEvent.VK_CONTROL);
if ((mod & ALT_DOWN_MASK) != 0) keyRelease(KeyEvent.VK_ALT);
}
private void keyPress(int keyCode) {
robot.keyPress(keyCode);
}
private void keyRelease(int keyCode) {
robot.keyRelease(keyCode);
}
/**
* Every test step implements this Runnable
*/
interface Runnable {
/**
* Executed the test setp
*
* @param guiTester the gui tester
* @throws Exception Exception
*/
void run(GuiTester guiTester) throws Exception;
}
static class WindowCheck<W extends Window> implements Runnable {
private final Class<W> clazz;
/**
* Checks if the topmost window is a instance of <W>.
*
* @param <W> the type of the expected window class
*/
public static class WindowCheck<W extends Window> implements Runnable {
private final Class<W> expectedClass;
public WindowCheck(Class<W> clazz) {
this.clazz = clazz;
/**
* Creates a new instance
*
* @param expectedClass the expected window class
*/
public WindowCheck(Class<W> expectedClass) {
this.expectedClass = expectedClass;
}
@Override
public void run(GuiTester guiTester) throws Exception {
Window activeWindow = FocusManager.getCurrentManager().getActiveWindow();
if (activeWindow == null || !clazz.isAssignableFrom(activeWindow.getClass())) {
if (activeWindow == null || !expectedClass.isAssignableFrom(activeWindow.getClass())) {
Thread.sleep(500);
activeWindow = FocusManager.getCurrentManager().getActiveWindow();
}
@ -188,21 +243,35 @@ public class GuiTester {
Assert.assertTrue(getClass().getSimpleName()
+ ": wrong dialog on top! expected: <"
+ clazz.getSimpleName()
+ expectedClass.getSimpleName()
+ "> but was: <"
+ activeWindow.getClass().getSimpleName()
+ ">",
clazz.isAssignableFrom(activeWindow.getClass()));
expectedClass.isAssignableFrom(activeWindow.getClass()));
checkWindow((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
*/
public void checkWindow(W window) {
}
}
/**
* Checks if the topmost dialog contains the given strings.
*/
public static class CheckDialogText extends WindowCheck<JDialog> {
private final String[] expected;
/**
* 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);
this.expected = expected;
@ -231,6 +300,9 @@ public class GuiTester {
}
}
/**
* Closes the topmost window
*/
public static class CloseTopMost implements Runnable {
@Override
public void run(GuiTester guiTester) {

View File

@ -20,7 +20,7 @@ import java.util.List;
*/
public class TestInGUI extends TestCase {
public void testErrorAtStart1() throws Exception {
public void testErrorAtStart1() {
new GuiTester("dig/manualError/01_fastRuntime.dig")
.press("SPACE")
.add(new CheckErrorDialog("01_fastRuntime.dig", Lang.get("err_burnError")))
@ -30,7 +30,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtStart2() throws Exception {
public void testErrorAtStart2() {
new GuiTester("dig/manualError/02_fastRuntimeEmbed.dig")
.press("SPACE")
.add(new CheckErrorDialog("short.dig", Lang.get("err_burnError")))
@ -39,7 +39,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtStart3() throws Exception {
public void testErrorAtStart3() {
new GuiTester("dig/manualError/06_initPhase.dig")
.press("SPACE")
.add(new CheckErrorDialog("06_initPhase.dig", Lang.get("err_burnError")))
@ -49,7 +49,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtStart4() throws Exception {
public void testErrorAtStart4() {
new GuiTester("dig/manualError/07_creationPhase.dig")
.press("SPACE")
.add(new CheckErrorDialog("07_creationPhase.dig", "ErrorY"))
@ -59,7 +59,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtStart5() throws Exception {
public void testErrorAtStart5() {
new GuiTester("dig/manualError/08_twoFastClocks.dig")
.press("SPACE")
.add(new CheckErrorDialog(Lang.get("err_moreThanOneFastClock")))
@ -68,7 +68,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtTestExecution() throws Exception {
public void testErrorAtTestExecution() {
new GuiTester("dig/manualError/04_testExecution.dig")
.press("F8")
.add(new CheckErrorDialog("04_testExecution.dig", Lang.get("err_burnError")))
@ -76,7 +76,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtRunToBreak() throws Exception {
public void testErrorAtRunToBreak() {
new GuiTester("dig/manualError/05_runToBreak.dig")
.press("SPACE")
.delay(500)
@ -88,7 +88,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testErrorAtButtonPress() throws Exception {
public void testErrorAtButtonPress() {
new GuiTester("dig/manualError/03_fastRuntimeButton.dig")
.press("SPACE")
.delay(500)
@ -100,7 +100,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testAnalysis() throws Exception {
public void testAnalysis() {
new GuiTester("dig/manualError/09_analysis.dig")
.press("F9")
.delay(500)
@ -130,7 +130,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testExpression() throws Exception {
public void testExpression() {
new GuiTester()
.press("F10")
.press("RIGHT", 4)
@ -156,21 +156,25 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testParity() throws Exception {
private GuiTester createNew4VarTruthTable = new GuiTester()
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.press("F10")
.press("RIGHT", 1)
.press("DOWN", 1)
.press("RIGHT", 1)
.press("DOWN", 2)
.press("ENTER")
.press("DOWN")
.press("RIGHT", 4);
public void testParity() {
new GuiTester()
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.press("F10")
.press("RIGHT", 1)
.press("DOWN", 1)
.press("RIGHT", 1)
.press("DOWN", 2)
.press("ENTER")
.press("DOWN")
.press("RIGHT", 4)
.use(createNew4VarTruthTable)
.add(new EnterTruthTable(0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1))
.press("F1")
.delay(500)
@ -179,21 +183,9 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testEdges() throws Exception {
public void testEdges() {
new GuiTester()
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.press("F10")
.press("RIGHT", 1)
.press("DOWN", 1)
.press("RIGHT", 1)
.press("DOWN", 2)
.press("ENTER")
.press("DOWN")
.press("RIGHT", 4)
.use(createNew4VarTruthTable)
.add(new EnterTruthTable(0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1))
.press("F1")
.delay(500)
@ -202,7 +194,7 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testTwoOutOfThree() throws Exception {
public void testTwoOutOfThree() {
new GuiTester()
.press("F10")
.press("RIGHT", 4)
@ -219,27 +211,30 @@ public class TestInGUI extends TestCase {
.execute();
}
public void testCounterJK() throws Exception {
private GuiTester create4BitCounterTruthTable = new GuiTester()
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.press("F10")
.press("RIGHT", 1)
.press("DOWN", 2)
.press("RIGHT", 1)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.add(new GuiTester.WindowCheck<AllSolutionsDialog>(AllSolutionsDialog.class){
@Override
public void checkWindow(AllSolutionsDialog asd) {
asd.getParent().requestFocus();
}
})
.delay(500);
public void testCounterJK() {
new GuiTester()
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.press("F10")
.press("RIGHT", 1)
.press("DOWN", 2)
.press("RIGHT", 1)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.add(new GuiTester.WindowCheck<AllSolutionsDialog>(AllSolutionsDialog.class){
@Override
public void checkWindow(AllSolutionsDialog asd) {
asd.getParent().requestFocus();
}
})
.delay(500)
.use(create4BitCounterTruthTable)
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
@ -247,43 +242,25 @@ public class TestInGUI extends TestCase {
.delay(500)
.press("SPACE")
.delay(500)
.ask("Does the 4 bit counter run correctly?")
.add(new GuiTester.WindowCheck<>(Main.class))
.ask("Does the 4 bit counter run correctly?")
.add(new GuiTester.CloseTopMost())
.execute();
}
public void testCounterD() throws Exception {
public void testCounterD() {
new GuiTester()
.press("F10")
.press("RIGHT", 4)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.press("F10")
.press("RIGHT", 1)
.press("DOWN", 2)
.press("RIGHT", 1)
.press("DOWN", 2)
.press("ENTER")
.delay(500)
.add(new GuiTester.WindowCheck<AllSolutionsDialog>(AllSolutionsDialog.class){
@Override
public void checkWindow(AllSolutionsDialog asd) {
asd.getParent().requestFocus();
}
})
.delay(500)
.use(create4BitCounterTruthTable)
.press("F2")
.press("SPACE")
.delay(500)
.ask("Does the 4 bit counter run correctly?")
.add(new GuiTester.WindowCheck<>(Main.class))
.ask("Does the 4 bit counter run correctly?")
.add(new GuiTester.CloseTopMost())
.execute();
}
public void testHardware() throws Exception {
public void testHardware() {
new GuiTester("dig/manualError/10_hardware.dig")
.press("F9")
.delay(500)
@ -300,8 +277,7 @@ public class TestInGUI extends TestCase {
.press("control typed a")
.typeTempFile("test")
.press("ENTER")
.delay(5000)
.press("TAB", 2)
.delay(3000)
.add(new GuiTester.CheckDialogText("Design fits successfully"))
.add(new GuiTester.CloseTopMost())
.add(new GuiTester.CloseTopMost())
@ -352,10 +328,10 @@ public class TestInGUI extends TestCase {
public void run(GuiTester guiTester) throws Exception {
for (int v : values) {
if (v == 1) {
guiTester.pressNow("typed 1");
guiTester.typeNow("typed 1");
Thread.sleep(400);
} else
guiTester.pressNow("DOWN");
guiTester.typeNow("DOWN");
}
}
}