Merge branch 'master' into virtualTestSignals

# Conflicts:
#	src/main/java/de/neemann/digital/testing/TestCaseDescription.java
This commit is contained in:
hneemann 2020-11-23 14:17:33 +01:00
commit b551fbe298
14 changed files with 214 additions and 91 deletions

View File

@ -9,6 +9,7 @@ HEAD, planned as v0.26
- If a high-z value is connected to a logic gate input, the read value
is undefined.
- It is now possible to use a probe as output in a test case.
- Adds undo to text fields
- Fixed a bug in the Demuxer Verilog template that causes problems
when using multiple demuxers in the same circuit.
- Generic circuits are easier to debug: It is possible now to create

View File

@ -4,7 +4,7 @@
### The Commands to Execute
The `.config` File describes two things. At first it defines which external
The `.config` File describes two things. At first, it defines which external
executables are needed to be started to run a circuit on a specific board.
And second it creates the files which are required to run a circuit on a specific
board.
@ -84,7 +84,7 @@ The `filter` attribute enables the filtering of the files content. And finally t
is used to include this file file from other `.config` files.
The `content` tag defines the content of the file created.
In most cases the content of the file depends on the circuit. In this case a the `filter`
attribute needs to be set to `true`. After thät you are able to use the `<?...?>` or `{?...?}`
attribute needs to be set to `true`. After that you are able to use the `<?...?>` or `{?...?}`
fragments to generate the file in a flexible way.
As an example we want to create a file named `Pins_[name of circuit].pin` which contains all
the pins used by the circuit:
@ -105,7 +105,7 @@ The circuit contains the following pins:
</files>
```
The code enclosed by the `{?...?}` characters is executed and prints the list of all
used pins to the text file. The `<![CDATA[ ]]>` statemant ensures that the content
used pins to the text file. The `<![CDATA[ ]]>` statement ensures that the content
is considered as character data which is not analysed by the XML parser. If this statement
is not used the `i < sizeOf(...` code causes an XML parser error.
@ -200,7 +200,7 @@ bool to it.
### File Creation
A template allways begins with plain text which is copied to the file to generate.
A template always begins with plain text which is copied to the file to generate.
Special actions - data evaluations or control structures - are delimited
by `{?` and `?}` or `<?` and `?>`. Because the `.config` files are XML files the
`{?` and `?}` variant is easier to use in most cases.

View File

@ -17,7 +17,6 @@ import de.neemann.digital.draw.library.ElementNotFoundException;
import de.neemann.digital.draw.library.LibraryInterface;
import de.neemann.digital.hdl.hgs.*;
import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.TestCaseDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -220,9 +219,6 @@ public class SubstituteLibrary implements LibraryInterface {
}
private Object doImplicitTypeCasts(Class<?> expectedClass, Object val) {
if (expectedClass == TestCaseDescription.class)
return new TestCaseDescription(val.toString());
if (expectedClass == Integer.class && val instanceof Long) {
long l = (Long) val;
if (l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE)

View File

@ -874,6 +874,6 @@ public final class Keys {
* The test data
*/
public static final Key<TestCaseDescription> TESTDATA =
new Key<>("Testdata", () -> new TestCaseDescription(""));
new Key<>("Testdata", TestCaseDescription::new);
}

View File

@ -177,7 +177,7 @@ public class AttributeDialog extends JDialog {
final AbstractAction cancel = new AbstractAction(Lang.get("cancel")) {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
tryDispose();
}
};
@ -190,24 +190,7 @@ public class AttributeDialog extends JDialog {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
EditorHolder majorModification = null;
for (EditorHolder eh : editors)
if (eh.e.invisibleModification())
majorModification = eh;
if (majorModification == null)
dispose();
else {
int r = JOptionPane.showOptionDialog(
AttributeDialog.this,
Lang.get("msg_dataWillBeLost_n", majorModification.key.getName()),
Lang.get("msg_warning"),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE,
null, new String[]{Lang.get("btn_discard"), Lang.get("cancel")},
Lang.get("cancel"));
if (r == JOptionPane.YES_OPTION)
dispose();
}
tryDispose();
}
});
@ -217,6 +200,27 @@ public class AttributeDialog extends JDialog {
JComponent.WHEN_IN_FOCUSED_WINDOW);
}
private void tryDispose() {
EditorHolder majorModification = null;
for (EditorHolder eh : editors)
if (eh.e.invisibleModification())
majorModification = eh;
if (majorModification == null)
dispose();
else {
int r = JOptionPane.showOptionDialog(
AttributeDialog.this,
Lang.get("msg_dataWillBeLost_n", majorModification.key.getName()),
Lang.get("msg_warning"),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE,
null, new String[]{Lang.get("btn_discard"), Lang.get("btn_editFurther")},
Lang.get("cancel"));
if (r == JOptionPane.YES_OPTION)
dispose();
}
}
private EditorPanel findPanel(ArrayList<EditorPanel> panels, String panelId) {
for (EditorPanel p : panels)
if (panelId.equals(p.getPanelId()))

View File

@ -35,11 +35,9 @@ import de.neemann.gui.language.Language;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import javax.swing.undo.UndoManager;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
@ -177,6 +175,7 @@ public final class EditorFactory {
private final JTextComponent text;
private final JComponent compToAdd;
private final UndoManager undoManager;
private JPopupMenu popup;
public StringEditor(String value, Key<String> key) {
@ -219,6 +218,8 @@ public final class EditorFactory {
compToAdd = text;
}
text.setText(value);
undoManager = createUndoManager(text);
}
JPopupMenu getPopupMenu(String keyName) {
@ -286,8 +287,10 @@ public final class EditorFactory {
@Override
public void setValue(String value) {
if (!text.getText().equals(value))
if (!text.getText().equals(value)) {
text.setText(value);
undoManager.discardAllEdits();
}
}
public JTextComponent getTextComponent() {
@ -1108,4 +1111,30 @@ public final class EditorFactory {
comb.setSelectedItem(value);
}
}
/**
* Enables undo in the given text component.
*
* @param text the text component
* @return the undo manager
*/
public static UndoManager createUndoManager(JTextComponent text) {
final UndoManager undoManager;
undoManager = new UndoManager();
text.getDocument().addUndoableEditListener(undoManager);
text.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_Z && (e.getModifiersEx() & ToolTipAction.getCTRLMask()) != 0) {
if (undoManager.canUndo())
undoManager.undo();
} else if (e.getKeyCode() == KeyEvent.VK_Y && (e.getModifiersEx() & ToolTipAction.getCTRLMask()) != 0) {
if (undoManager.canRedo())
undoManager.redo();
}
}
});
return undoManager;
}
}

View File

@ -24,34 +24,57 @@ import de.neemann.gui.ToolTipAction;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import static de.neemann.digital.gui.components.EditorFactory.addF1Traversal;
import static de.neemann.digital.gui.components.EditorFactory.createUndoManager;
/**
* Dialog to show and edit the testing data source.
*/
public class TestCaseDescriptionDialog extends JDialog {
private final JTextArea text;
private final TestCaseDescription initialData;
private final VisualElement element;
private TestCaseDescription modifiedData;
private boolean circuitModified = false;
/**
* Creates a new data dialog
* Creates a new data dialog.
*
* @param parent the parent component
* @param data the data to edit
* @param element the element to be modified
* @param parent the parent component
* @param initialData the data to edit
*/
public TestCaseDescriptionDialog(Window parent, TestCaseDescription data, VisualElement element) {
public TestCaseDescriptionDialog(Window parent, TestCaseDescription initialData) {
this(parent, initialData, null);
}
/**
* Creates a new data dialog.
* This constructor allows to open the dialog in a modeless way.
*
* @param parent the parent component
* @param initialData the data to edit
* @param element the element to be modified
*/
public TestCaseDescriptionDialog(Window parent, TestCaseDescription initialData, VisualElement element) {
super(parent,
Lang.get("key_Testdata"),
element == null ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.element = element;
this.initialData = initialData;
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
String initialDataString = data.getDataString();
JTextArea text = addF1Traversal(new JTextArea(data.getDataString(), 30, 50));
text = addF1Traversal(new JTextArea(initialData.getDataString(), 30, 50));
text.setFont(new Font(Font.MONOSPACED, Font.PLAIN, Screen.getInstance().getFontSize()));
createUndoManager(text);
addWindowListener(new ClosingWindowListener());
JScrollPane scrollPane = new JScrollPane(text);
getContentPane().add(scrollPane);
scrollPane.setRowHeaderView(new TextLineNumber(text, 3));
@ -91,7 +114,7 @@ public class TestCaseDescriptionDialog extends JDialog {
buttons.add(new ToolTipAction(Lang.get("cancel")) {
@Override
public void actionPerformed(ActionEvent actionEvent) {
dispose();
tryDispose();
}
}.createJButton());
@ -100,10 +123,10 @@ public class TestCaseDescriptionDialog extends JDialog {
@Override
public void actionPerformed(ActionEvent e) {
try {
data.setDataString(text.getText());
if (parent instanceof Main) {
CircuitComponent cc = ((Main) parent).getCircuitComponent();
element.getElementAttributes().set(Keys.TESTDATA, data);
element.getElementAttributes().set(Keys.TESTDATA, new TestCaseDescription(text.getText()));
circuitModified = true;
cc.getMain().startTests();
}
} catch (ParserException | IOException e1) {
@ -117,12 +140,12 @@ public class TestCaseDescriptionDialog extends JDialog {
@Override
public void actionPerformed(ActionEvent e) {
try {
data.setDataString(text.getText());
modifiedData = new TestCaseDescription(text.getText());
if (element != null
&& !initialDataString.equals(data.getDataString())
&& isStateChanged()
&& parent instanceof Main) {
CircuitComponent cc = ((Main) parent).getCircuitComponent();
cc.modify(new ModifyAttribute<>(element, Keys.TESTDATA, new TestCaseDescription(data)));
cc.modify(new ModifyAttribute<>(element, Keys.TESTDATA, modifiedData));
}
dispose();
} catch (ParserException | IOException e1) {
@ -138,4 +161,48 @@ public class TestCaseDescriptionDialog extends JDialog {
setLocationRelativeTo(parent);
}
/**
* Shows the dialog and returns the modified data
*
* @return the modified data or null if not modified
*/
public TestCaseDescription showDialog() {
modifiedData = null;
setVisible(true);
return modifiedData;
}
private boolean isStateChanged() {
return !initialData.getDataString().equals(text.getText());
}
private void tryDispose() {
if (isStateChanged()) {
int r = JOptionPane.showOptionDialog(
this,
Lang.get("msg_dataWillBeLost_n", Keys.TESTDATA.getName()),
Lang.get("msg_warning"),
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE,
null, new String[]{Lang.get("btn_discard"), Lang.get("btn_editFurther")},
Lang.get("cancel"));
if (r == JOptionPane.YES_OPTION)
myDispose();
} else
myDispose();
}
private void myDispose() {
dispose();
if (circuitModified)
element.getElementAttributes().set(Keys.TESTDATA, initialData);
}
private final class ClosingWindowListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
tryDispose();
}
}
}

View File

@ -19,10 +19,12 @@ import java.awt.*;
import java.awt.event.ActionEvent;
/**
* The test case description editor
*/
public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCaseDescription> {
private final TestCaseDescription data;
private final TestCaseDescription initialData;
private TestCaseDescription data;
/**
* Creates a new editor
@ -31,7 +33,8 @@ public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCas
* @param key the data key
*/
public TestCaseDescriptionEditor(TestCaseDescription data, Key<TestCaseDescription> key) {
this.data = new TestCaseDescription(data);
this.data = data;
this.initialData = data;
}
@Override
@ -46,7 +49,10 @@ public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCas
panel.add(new ToolTipAction(Lang.get("btn_edit")) {
@Override
public void actionPerformed(ActionEvent e) {
new TestCaseDescriptionDialog(SwingUtilities.getWindowAncestor(panel), data, null).setVisible(true);
TestCaseDescriptionDialog tdd = new TestCaseDescriptionDialog(SwingUtilities.getWindowAncestor(panel), data);
TestCaseDescription d = tdd.showDialog();
if (d != null)
data = d;
}
}.createJButton());
@ -73,6 +79,12 @@ public class TestCaseDescriptionEditor extends EditorFactory.LabelEditor<TestCas
}
@Override
public void setValue(TestCaseDescription value) {
public boolean invisibleModification() {
return !initialData.equals(data);
}
@Override
public void setValue(TestCaseDescription data) {
this.data = data;
}
}

View File

@ -144,6 +144,9 @@ public class HDLCircuit implements Iterable<HDLNode>, HDLModel.BitProvider, Prin
for (HDLNet n : listOfNets)
n.fixBits();
for (HDLNet n : listOfNets)
n.checkPinControlUsage();
// fix inverted inputs
ArrayList<HDLNode> newNodes = new ArrayList<>();
for (HDLNode n : nodes) {

View File

@ -203,6 +203,8 @@ public class HDLNet implements Printable, HasName {
* @return true if this is a clock net
*/
public boolean isClock() {
if (output == null)
return false;
return output.isClock();
}
@ -231,4 +233,17 @@ public class HDLNet implements Printable, HasName {
return !inOutputs.isEmpty();
}
/**
* Checks whether a PinControl component is used correctly.
*
* @throws HDLException thrown if PinControl us used the wrong way
*/
public void checkPinControlUsage() throws HDLException {
if (output == null && inOutputs.size() > 0) {
if (inputs.size() > 1)
throw new HDLException("multiple components connected to PinControl output");
if (inputs.size() == 1 && inputs.get(0).getParent() != null)
throw new HDLException("only a single output is allowed on a PinControl component");
}
}
}

View File

@ -18,18 +18,31 @@ import java.util.ArrayList;
* The test data.
*/
public class TestCaseDescription {
private String dataString;
private final String dataString;
private transient LineEmitter lines;
private transient ArrayList<String> names;
private transient ArrayList<VirtualSignal> virtualSignals;
/**
* creates a new instance
*/
public TestCaseDescription() {
this.dataString = "";
}
/**
* creates a new instance
*
* @param data the test case description
* @throws IOException IOException
* @throws ParserException ParserException
*/
public TestCaseDescription(String data) {
public TestCaseDescription(String data) throws IOException, ParserException {
this.dataString = data;
Parser tdp = new Parser(data).parse();
lines = tdp.getLines();
names = tdp.getNames();
}
/**
@ -38,7 +51,7 @@ public class TestCaseDescription {
* @param valueToCopy the instance to copy
*/
public TestCaseDescription(TestCaseDescription valueToCopy) {
this(valueToCopy.dataString);
this.dataString = valueToCopy.dataString;
}
/**
@ -48,23 +61,6 @@ public class TestCaseDescription {
return dataString;
}
/**
* Sets the data and checks its validity
*
* @param data the data
* @throws IOException thrown if data is not valid
* @throws ParserException thrown if data is not valid
*/
public void setDataString(String data) throws IOException, ParserException {
if (!data.equals(dataString)) {
Parser tdp = new Parser(data).parse();
dataString = data;
lines = tdp.getLines();
names = tdp.getNames();
virtualSignals = tdp.getVirtualSignals();
}
}
private void check() throws TestingDataException {
if (lines == null || names == null) {
try {

View File

@ -46,6 +46,7 @@ import de.neemann.digital.gui.remote.RemoteException;
import de.neemann.digital.lang.Lang;
import de.neemann.digital.testing.TestCaseDescription;
import de.neemann.digital.testing.TestCaseElement;
import de.neemann.digital.testing.parser.ParserException;
import de.neemann.gui.ErrorMessage;
import junit.framework.TestCase;
@ -1375,10 +1376,14 @@ public class TestInGUI extends TestCase {
@Override
public void checkWindow(GuiTester gt, Main main) {
main.getCircuitComponent().getCircuit().add(
new VisualElement(TestCaseElement.DESCRIPTION.getName())
.setAttribute(TESTDATA, new TestCaseDescription(testdata))
.setShapeFactory(main.getCircuitComponent().getLibrary().getShapeFactory()));
try {
main.getCircuitComponent().getCircuit().add(
new VisualElement(TestCaseElement.DESCRIPTION.getName())
.setAttribute(TESTDATA, new TestCaseDescription(testdata))
.setShapeFactory(main.getCircuitComponent().getLibrary().getShapeFactory()));
} catch (IOException | ParserException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@ -28,8 +28,8 @@ public class TestDataTest extends TestCase {
// try to set a non parsable string
try {
td.setDataString(DATA3);
assertTrue(false);
td = new TestCaseDescription(DATA3);
fail();
} catch (IOException | ParserException e) {
assertTrue(true);
}
@ -46,7 +46,7 @@ public class TestDataTest extends TestCase {
assertEquals(DATA1, td.getDataString());
// try to set a parsable string
td.setDataString(DATA2);
td = new TestCaseDescription(DATA2);
// TestData has changed!
assertEquals(DATA2, td.getDataString());
}

View File

@ -27,11 +27,6 @@
<elementAttributes/>
<pos x="520" y="20"/>
</visualElement>
<visualElement>
<elementName>VDD</elementName>
<elementAttributes/>
<pos x="500" y="0"/>
</visualElement>
<visualElement>
<elementName>Out</elementName>
<elementAttributes>
@ -89,7 +84,7 @@
<p2 x="460" y="40"/>
</wire>
<wire>
<p1 x="500" y="60"/>
<p1 x="460" y="60"/>
<p2 x="520" y="60"/>
</wire>
<wire>
@ -100,22 +95,22 @@
<p1 x="340" y="20"/>
<p2 x="340" y="100"/>
</wire>
<wire>
<p1 x="500" y="0"/>
<p2 x="500" y="60"/>
</wire>
<wire>
<p1 x="360" y="40"/>
<p2 x="360" y="80"/>
</wire>
<wire>
<p1 x="460" y="40"/>
<p2 x="460" y="80"/>
<p2 x="460" y="60"/>
</wire>
<wire>
<p1 x="460" y="-40"/>
<p2 x="460" y="20"/>
</wire>
<wire>
<p1 x="460" y="60"/>
<p2 x="460" y="80"/>
</wire>
</wires>
<measurementOrdering/>
</circuit>