From 7c086d87961f331658ddbe7c053dd976b316f75c Mon Sep 17 00:00:00 2001 From: hneemann Date: Thu, 26 Nov 2020 13:19:15 +0100 Subject: [PATCH] Allows to set the simulator to a certain line in the a test result. --- .../de/neemann/digital/data/ValueTable.java | 14 ++- .../neemann/digital/data/ValueTableModel.java | 13 ++- .../java/de/neemann/digital/gui/Main.java | 35 +++++++- .../components/testing/ValueTableDialog.java | 62 +++++++++---- .../neemann/digital/testing/TestExecutor.java | 89 ++++++++++++++----- .../digital/testing/parser/TestRow.java | 19 ++++ 6 files changed, 188 insertions(+), 44 deletions(-) diff --git a/src/main/java/de/neemann/digital/data/ValueTable.java b/src/main/java/de/neemann/digital/data/ValueTable.java index ed5afe18b..7aa039801 100644 --- a/src/main/java/de/neemann/digital/data/ValueTable.java +++ b/src/main/java/de/neemann/digital/data/ValueTable.java @@ -140,10 +140,20 @@ public class ValueTable extends Observable implements Iterable { * @return the value stored at the given position */ public Value getTableValue(int rowIndex, int columnIndex) { + return getTableRow(rowIndex).getValue(columnIndex); + } + + /** + * Returns a table row + * + * @param rowIndex the index of the table row + * @return the table row + */ + public TestRow getTableRow(int rowIndex) { if (tableRowIndex == null) - return values.get(rowIndex).getValue(columnIndex); + return values.get(rowIndex); else - return values.get(tableRowIndex.get(rowIndex)).getValue(columnIndex); + return values.get(tableRowIndex.get(rowIndex)); } /** diff --git a/src/main/java/de/neemann/digital/data/ValueTableModel.java b/src/main/java/de/neemann/digital/data/ValueTableModel.java index 04ee9cdd0..10eacff21 100644 --- a/src/main/java/de/neemann/digital/data/ValueTableModel.java +++ b/src/main/java/de/neemann/digital/data/ValueTableModel.java @@ -6,6 +6,7 @@ package de.neemann.digital.data; import de.neemann.digital.core.Observer; +import de.neemann.digital.testing.parser.TestRow; import javax.swing.*; import javax.swing.event.TableModelEvent; @@ -20,7 +21,7 @@ import java.util.ArrayList; public class ValueTableModel implements TableModel, Observer { private final ValueTable values; - private ArrayList listeners; + private final ArrayList listeners; /** * Creates a new table model @@ -99,4 +100,14 @@ public class ValueTableModel implements TableModel, Observer { l.tableChanged(tme); }); } + + /** + * Returns a table row + * + * @param row the number of the table row + * @return the table row + */ + public TestRow getRow(int row) { + return values.getTableRow(row); + } } diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index 86a7313bc..4831741f9 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -1137,7 +1137,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS .setVisible(true); ensureModelIsStopped(); - } catch (NodeException | ElementNotFoundException | PinException | TestingDataException | RuntimeException e1) { + } catch (TestingDataException | RuntimeException e1) { showError(Lang.get("msg_runningTestError"), e1); } } @@ -1589,6 +1589,26 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS } } + /** + * Starts the simulation and allows to advance the model to a certain state. + * + * @param advanceSimulator needs to be implemented to advance the model + */ + public void startSimulation(AdvanceSimulator advanceSimulator) { + SwingUtilities.invokeLater(() -> { + runModelState.enter(false, null); + SwingUtilities.invokeLater(() -> { + if (model != null && model.isRunning()) { + try { + advanceSimulator.advance(Main.this); + circuitComponent.graphicHasChanged(); + } catch (Exception e) { + showError("Message", e); + } + } + }); + }); + } @Override public void hasChanged() { @@ -2113,4 +2133,17 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS } } } + + /** + * Allows to start the simulation and advance the model to a certain state. + */ + public interface AdvanceSimulator { + /** + * Advances the model to a certain state + * + * @param main main + * @throws Exception Exception + */ + void advance(Main main) throws Exception; + } } diff --git a/src/main/java/de/neemann/digital/gui/components/testing/ValueTableDialog.java b/src/main/java/de/neemann/digital/gui/components/testing/ValueTableDialog.java index 1b13308db..68ef503b9 100644 --- a/src/main/java/de/neemann/digital/gui/components/testing/ValueTableDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/testing/ValueTableDialog.java @@ -6,19 +6,19 @@ package de.neemann.digital.gui.components.testing; import de.neemann.digital.core.ErrorDetector; -import de.neemann.digital.core.NodeException; import de.neemann.digital.data.Value; import de.neemann.digital.data.ValueTable; import de.neemann.digital.data.ValueTableModel; import de.neemann.digital.draw.elements.Circuit; -import de.neemann.digital.draw.elements.PinException; import de.neemann.digital.draw.library.ElementLibrary; -import de.neemann.digital.draw.library.ElementNotFoundException; +import de.neemann.digital.gui.Main; import de.neemann.digital.gui.SaveAsHelper; import de.neemann.digital.gui.components.data.GraphDialog; import de.neemann.digital.lang.Lang; +import de.neemann.digital.testing.TestCaseDescription; import de.neemann.digital.testing.TestExecutor; import de.neemann.digital.testing.TestingDataException; +import de.neemann.digital.testing.parser.TestRow; import de.neemann.gui.IconCreator; import de.neemann.gui.LineBreaker; import de.neemann.gui.MyFileChooser; @@ -29,6 +29,8 @@ import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; @@ -50,7 +52,7 @@ public class ValueTableDialog extends JDialog { private static final Icon ICON_GRAPH = IconCreator.create("measurement-graph.png"); - private final ArrayList resultTableData; + private final ArrayList resultTableData; private final JTabbedPane tp; private final Window owner; private final ToolTipAction asGraph; @@ -80,7 +82,7 @@ public class ValueTableDialog extends JDialog { JFileChooser fileChooser = new MyFileChooser(); fileChooser.setFileFilter(new FileNameExtensionFilter("Comma Separated Values", "csv")); new SaveAsHelper(ValueTableDialog.this, fileChooser, "csv") - .checkOverwrite(resultTableData.get(tab)::saveCSV); + .checkOverwrite(resultTableData.get(tab).valueTable::saveCSV); } }.setToolTip(Lang.get("menu_saveData_tt")).createJMenuItem()); @@ -90,7 +92,7 @@ public class ValueTableDialog extends JDialog { public void actionPerformed(ActionEvent actionEvent) { int tab = tp.getSelectedIndex(); if (tab < 0) tab = 0; - new GraphDialog(ValueTableDialog.this, Lang.get("win_testdata_N", tp.getTitleAt(tab)), resultTableData.get(tab)) + new GraphDialog(ValueTableDialog.this, Lang.get("win_testdata_N", tp.getTitleAt(tab)), resultTableData.get(tab).valueTable) .disableTable() .setVisible(true); } @@ -113,12 +115,9 @@ public class ValueTableDialog extends JDialog { * @param circuit the circuit * @param library the library to use * @return this for chained calls - * @throws NodeException NodeException - * @throws TestingDataException DataException - * @throws PinException PinException - * @throws ElementNotFoundException ElementNotFoundException + * @throws TestingDataException DataException */ - public ValueTableDialog addTestResult(java.util.List tsl, Circuit circuit, ElementLibrary library) throws TestingDataException, ElementNotFoundException, PinException, NodeException { + public ValueTableDialog addTestResult(java.util.List tsl, Circuit circuit, ElementLibrary library) throws TestingDataException { Collections.sort(tsl); int i = 0; int errorTabIndex = -1; @@ -142,10 +141,11 @@ public class ValueTableDialog extends JDialog { if (testResult.toManyResults()) tabName += " " + Lang.get("msg_test_missingLines"); - tp.addTab(tabName, tabIcon, new JScrollPane(createTable(testResult.getValueTable()))); + ValueTableHolder vth = new ValueTableHolder(testResult.getValueTable(), ts.getTestCaseDescription()); + tp.addTab(tabName, tabIcon, new JScrollPane(createTable(vth))); if (testResult.toManyResults()) tp.setToolTipTextAt(i, new LineBreaker().toHTML().breakLines(Lang.get("msg_test_missingLines_tt"))); - resultTableData.add(testResult.getValueTable()); + resultTableData.add(vth); i++; errorDetector.check(); } catch (Exception e) { @@ -168,18 +168,32 @@ public class ValueTableDialog extends JDialog { * @return this for chained calls */ public ValueTableDialog addValueTable(String name, ValueTable valueTable) { - tp.addTab(name, new JScrollPane(createTable(valueTable))); - resultTableData.add(valueTable); + tp.addTab(name, new JScrollPane(createTable(new ValueTableHolder(valueTable)))); + resultTableData.add(new ValueTableHolder(valueTable)); pack(); setLocationRelativeTo(owner); return this; } - private JTable createTable(ValueTable valueTable) { - JTable table = new JTable(new ValueTableModel(valueTable)); + private JTable createTable(ValueTableHolder valueTableHolder) { + ValueTableModel vtm = new ValueTableModel(valueTableHolder.valueTable); + JTable table = new JTable(vtm); table.setDefaultRenderer(Value.class, new ValueRenderer()); table.setDefaultRenderer(Integer.class, new NumberRenderer()); + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int r = table.getSelectedRow(); + if (r > 0 && r < vtm.getRowCount() && valueTableHolder.testCaseDescription != null) { + TestRow row = vtm.getRow(r); + if (owner instanceof Main) { + Main main = (Main) owner; + main.startSimulation(m -> new TestExecutor(valueTableHolder.testCaseDescription, m.getModel()).executeTo(row.getRow())); + } + } + } + }); final Font font = table.getFont(); table.setRowHeight(font.getSize() * 6 / 5); return table; @@ -233,4 +247,18 @@ public class ValueTableDialog extends JDialog { } } + private static final class ValueTableHolder { + private final ValueTable valueTable; + private final TestCaseDescription testCaseDescription; + + private ValueTableHolder(ValueTable valueTable) { + this.valueTable = valueTable; + testCaseDescription = null; + } + + private ValueTableHolder(ValueTable valueTable, TestCaseDescription testCaseDescription) { + this.valueTable = valueTable; + this.testCaseDescription = testCaseDescription; + } + } } diff --git a/src/main/java/de/neemann/digital/testing/TestExecutor.java b/src/main/java/de/neemann/digital/testing/TestExecutor.java index d76c2b64f..abde88e25 100644 --- a/src/main/java/de/neemann/digital/testing/TestExecutor.java +++ b/src/main/java/de/neemann/digital/testing/TestExecutor.java @@ -37,6 +37,7 @@ public class TestExecutor { private boolean errorOccurred; private int failedCount; private int passedCount; + private int rowCount; private boolean toManyResults = false; private ArrayList inputs; private ArrayList outputs; @@ -85,15 +86,51 @@ public class TestExecutor { lines = testCase.getLines(); } + /** + * Sets the model to the given row. + * + * @param row the row to advance the model to + * @throws TestingDataException DataException + * @throws ParserException ParserException + */ + public void executeTo(int row) throws TestingDataException, ParserException { + execute(new LineListener() { + private int r = row; + + @Override + public void add(TestRow testRow) { + Value[] values = testRow.getValues(); + Value[] res = new Value[values.length]; + + if (r >= 0) { + advanceModel(model, testRow, values, res); + r--; + } + } + }, false); + } + /** * Creates the result by comparing the testing vector with the given model * * @return the result of the test execution * @throws TestingDataException DataException - * @throws NodeException NodeException * @throws ParserException ParserException */ - public TestExecutor.Result execute() throws TestingDataException, NodeException, ParserException { + public TestExecutor.Result execute() throws TestingDataException, ParserException { + return execute(values -> checkRow(model, values), true); + } + + /** + * Executes the test and sends all the test lines to the {@link LineListener} provided. + * + * @param lineListener the line listener to use + * @param closeModel if true the model is closed + * @return the result of the test execution + * @throws TestingDataException DataException + * @throws ParserException ParserException + */ + private TestExecutor.Result execute(LineListener lineListener, boolean closeModel) throws TestingDataException, ParserException { try { HashSet usedSignals = new HashSet<>(); inputs = new ArrayList<>(); @@ -160,11 +197,12 @@ public class TestExecutor { errorOccurred = true; }, ModelEventType.ERROR_OCCURRED); - lines.emitLines(new LineListenerResolveDontCare(values -> checkRow(model, values), inputs), context); + lines.emitLines(new LineListenerResolveDontCare(lineListener, inputs), context); return new Result(); } finally { - model.close(); + if (closeModel) + model.close(); } } @@ -178,6 +216,30 @@ public class TestExecutor { Value[] values = testRow.getValues(); Value[] res = new Value[values.length]; + advanceModel(model, testRow, values, res); + + boolean ok = true; + for (TestSignal out : outputs) { + MatchedValue matchedValue = new MatchedValue(values[out.index], out.value); + res[out.index] = matchedValue; + if (!matchedValue.isPassed()) + ok = false; + } + + if (ok) + passedCount++; + else + failedCount++; + + if (visibleRows < (ok ? MAX_RESULTS : ERR_RESULTS)) { + visibleRows++; + results.add(new TestRow(res, testRow.getDescription()).setRow(rowCount)); + rowCount++; + } else + toManyResults = true; + } + + private void advanceModel(Model model, TestRow testRow, Value[] values, Value[] res) { boolean clockIsUsed = false; // set all values except the clocks for (TestSignal in : inputs) { @@ -219,25 +281,6 @@ public class TestExecutor { errorOccurred = true; throw e; } - - boolean ok = true; - for (TestSignal out : outputs) { - MatchedValue matchedValue = new MatchedValue(values[out.index], out.value); - res[out.index] = matchedValue; - if (!matchedValue.isPassed()) - ok = false; - } - - if (ok) - passedCount++; - else - failedCount++; - - if (visibleRows < (ok ? MAX_RESULTS : ERR_RESULTS)) { - visibleRows++; - results.add(new TestRow(res, testRow.getDescription())); - } else - toManyResults = true; } private void addClockRow(int cols, String description) { diff --git a/src/main/java/de/neemann/digital/testing/parser/TestRow.java b/src/main/java/de/neemann/digital/testing/parser/TestRow.java index fa80123d1..27f960c63 100644 --- a/src/main/java/de/neemann/digital/testing/parser/TestRow.java +++ b/src/main/java/de/neemann/digital/testing/parser/TestRow.java @@ -13,6 +13,7 @@ import de.neemann.digital.data.Value; public class TestRow { private final Value[] values; private final String description; + private int rowCount; /** * Creates a new instance @@ -58,4 +59,22 @@ public class TestRow { public String getDescription() { return description; } + + /** + * Sets the row number + * + * @param rowCount the number to set + * @return this for chained calls + */ + public TestRow setRow(int rowCount) { + this.rowCount = rowCount; + return this; + } + + /** + * @return the row number of this row + */ + public int getRow() { + return rowCount; + } }