From 81e41a973c36bac80a5a36f353f6eb5ff8e03020 Mon Sep 17 00:00:00 2001 From: hneemann Date: Fri, 1 Jun 2018 23:09:34 +0200 Subject: [PATCH] Refactoring of TestAllDialog --- .../gui/components/testing/TestAllDialog.java | 168 ++---------- .../digital/testing/FolderTestRunner.java | 258 ++++++++++++++++++ .../digital/testing/FolderTestRunnerTest.java | 39 +++ 3 files changed, 315 insertions(+), 150 deletions(-) create mode 100644 src/main/java/de/neemann/digital/testing/FolderTestRunner.java create mode 100644 src/test/java/de/neemann/digital/testing/FolderTestRunnerTest.java diff --git a/src/main/java/de/neemann/digital/gui/components/testing/TestAllDialog.java b/src/main/java/de/neemann/digital/gui/components/testing/TestAllDialog.java index f4b3fd3f6..3e0ee2a91 100644 --- a/src/main/java/de/neemann/digital/gui/components/testing/TestAllDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/testing/TestAllDialog.java @@ -5,21 +5,11 @@ */ package de.neemann.digital.gui.components.testing; -import de.neemann.digital.core.Model; -import de.neemann.digital.core.NodeException; -import de.neemann.digital.draw.elements.Circuit; -import de.neemann.digital.draw.elements.PinException; -import de.neemann.digital.draw.elements.VisualElement; import de.neemann.digital.draw.library.ElementLibrary; -import de.neemann.digital.draw.library.ElementNotFoundException; -import de.neemann.digital.draw.model.ModelCreator; import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.gui.Main; import de.neemann.digital.lang.Lang; -import de.neemann.digital.testing.TestCaseDescription; -import de.neemann.digital.testing.TestCaseElement; -import de.neemann.digital.testing.TestExecutor; -import de.neemann.digital.testing.TestingDataException; +import de.neemann.digital.testing.FolderTestRunner; import javax.swing.*; import javax.swing.event.TableModelEvent; @@ -30,10 +20,7 @@ import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; -import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; /** * Tests all the files in a given folder @@ -41,7 +28,7 @@ import java.util.Comparator; public class TestAllDialog extends JDialog { /** - * Creates a new dialog + * Creates a new dialog and starts the test execution. * * @param frame the parent frame * @param folder the folder to scan @@ -50,12 +37,11 @@ public class TestAllDialog extends JDialog { */ public TestAllDialog(Frame frame, File folder, ShapeFactory shapeFactory, ElementLibrary library) { super(frame, Lang.get("msg_testResult"), false); - ArrayList files = new ArrayList<>(); - scan(folder.getPath().length() + 1, folder, files); + FolderTestRunner folderTestRunner = new FolderTestRunner(folder); - final FileModel tableModel = new FileModel(files); + final FileModel tableModel = new FileModel(folderTestRunner.getFiles()); JTable table = new JTable(tableModel); - table.getColumnModel().getColumn(1).setCellRenderer(new StateRenderer(files)); + table.getColumnModel().getColumn(1).setCellRenderer(new StateRenderer()); getContentPane().add(new JScrollPane(table)); pack(); setLocationRelativeTo(frame); @@ -66,7 +52,7 @@ public class TestAllDialog extends JDialog { if (mouseEvent.getClickCount() == 2) { int row = table.getSelectedRow(); if (row >= 0) { - File f = files.get(row).file; + File f = folderTestRunner.getFiles().get(row).getFile(); new Main.MainBuilder() .setParent(frame) .setFileToOpen(f) @@ -78,51 +64,18 @@ public class TestAllDialog extends JDialog { } }); - Thread t = new Thread(new TestRunner(files, tableModel, shapeFactory, library)); - t.setDaemon(true); - t.start(); + folderTestRunner.startTests( + (f, row) -> SwingUtilities.invokeLater(() -> tableModel.messageChanged(row)), + shapeFactory, + library); } - private void scan(int rootLength, File folder, ArrayList files) { - File[] fileList = folder.listFiles(); - if (fileList != null) { - Arrays.sort(fileList, Comparator.comparing(f -> f.getPath().toLowerCase())); - for (File f : fileList) - if (f.isDirectory()) - scan(rootLength, f, files); - else if (f.isFile() && f.getName().endsWith(".dig")) - files.add(new FileToTest(rootLength, f)); - } - } - - private static final class FileToTest { - private enum State {unknown, passed, error, failed} - - private final File file; - private final String name; - private String message = "-"; - private State state = State.unknown; - - private FileToTest(int rootLength, File file) { - this.file = file; - name = file.getPath().substring(rootLength); - } - - public String getName() { - return name; - } - - private void setMessage(String message, State state) { - this.message = message; - this.state = state; - } - } private final static class FileModel implements TableModel { - private final ArrayList files; + private final ArrayList files; private ArrayList listener; - private FileModel(ArrayList files) { + private FileModel(ArrayList files) { this.files = files; listener = new ArrayList<>(); } @@ -159,12 +112,12 @@ public class TestAllDialog extends JDialog { @Override public Object getValueAt(int row, int col) { - FileToTest file = files.get(row); + FolderTestRunner.FileToTest file = files.get(row); switch (col) { case 0: return file.getName(); default: - return file.message; + return file; } } @@ -189,100 +142,14 @@ public class TestAllDialog extends JDialog { } } - - private static final class TestRunner implements Runnable { - private final ArrayList files; - private final FileModel tableModel; - private final ShapeFactory shapeFactory; - private final ElementLibrary library; - - private TestRunner(ArrayList files, FileModel tableModel, ShapeFactory shapeFactory, ElementLibrary library) { - this.files = files; - this.tableModel = tableModel; - this.shapeFactory = shapeFactory; - this.library = library; - } - - @Override - public void run() { - for (int i = 0; i < files.size(); i++) { - FileToTest f = files.get(i); - try { - Circuit circuit = Circuit.loadCircuit(f.file, shapeFactory); - ArrayList testCases = new ArrayList<>(); - for (VisualElement el : circuit.getElements()) { - if (el.equalsDescription(TestCaseElement.TESTCASEDESCRIPTION)) { - String label = el.getElementAttributes().getCleanLabel(); - TestCaseDescription testData = el.getElementAttributes().get(TestCaseElement.TESTDATA); - testCases.add(new TestCase(label, testData)); - } - } - if (testCases.isEmpty()) - setMessage(f, i, Lang.get("err_noTestData"), FileToTest.State.unknown); - else { - Model model = new ModelCreator(circuit, library).createModel(false); - StringBuilder sb = new StringBuilder(); - int rowCount = 0; - for (TestCase tc : testCases) { - try { - TestExecutor te = new TestExecutor(tc.testData).create(model); - if (te.allPassed()) { - rowCount += te.getResult().getRows(); - } else { - if (sb.length() > 0) - sb.append("; "); - sb.append(Lang.get("msg_test_N_Failed", tc.label)); - } - } catch (TestingDataException | NodeException e) { - if (sb.length() > 0) - sb.append("; "); - sb.append(tc.label).append(": ").append(e.getMessage()); - } - } - if (sb.length() == 0) - setMessage(f, i, Lang.get("msg_testPassed_N", rowCount), FileToTest.State.passed); - else - setMessage(f, i, sb.toString(), FileToTest.State.failed); - } - - } catch (IOException | NodeException | ElementNotFoundException | PinException e) { - setMessage(f, i, e.getMessage(), FileToTest.State.error); - } - } - } - - private void setMessage(FileToTest f, int i, String message, FileToTest.State state) { - SwingUtilities.invokeLater(() -> { - f.setMessage(message, state); - tableModel.messageChanged(i); - }); - } - - private final class TestCase { - private final String label; - private final TestCaseDescription testData; - - private TestCase(String label, TestCaseDescription testData) { - this.label = label; - this.testData = testData; - } - } - } - - private static final class StateRenderer extends DefaultTableCellRenderer { - private final ArrayList files; - - private StateRenderer(ArrayList files) { - this.files = files; - } @Override public Component getTableCellRendererComponent(JTable jTable, Object o, boolean b, boolean b1, int row, int i1) { - final Component tc = super.getTableCellRendererComponent(jTable, o, b, b1, row, i1); + final JLabel tc = (JLabel) super.getTableCellRendererComponent(jTable, o, b, b1, row, i1); - FileToTest f = files.get(row); - switch (f.state) { + FolderTestRunner.FileToTest f = (FolderTestRunner.FileToTest) o; + switch (f.getStatus()) { case error: tc.setBackground(Color.LIGHT_GRAY); break; @@ -296,6 +163,7 @@ public class TestAllDialog extends JDialog { tc.setBackground(ValueTableDialog.FAILED_COLOR); break; } + tc.setText(f.getMessage()); return tc; } diff --git a/src/main/java/de/neemann/digital/testing/FolderTestRunner.java b/src/main/java/de/neemann/digital/testing/FolderTestRunner.java new file mode 100644 index 000000000..56a609756 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/FolderTestRunner.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.testing; + +import de.neemann.digital.core.Model; +import de.neemann.digital.core.NodeException; +import de.neemann.digital.draw.elements.Circuit; +import de.neemann.digital.draw.elements.PinException; +import de.neemann.digital.draw.elements.VisualElement; +import de.neemann.digital.draw.library.ElementLibrary; +import de.neemann.digital.draw.library.ElementNotFoundException; +import de.neemann.digital.draw.model.ModelCreator; +import de.neemann.digital.draw.shapes.ShapeFactory; +import de.neemann.digital.lang.Lang; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Runs all tests in al circuits in a folder + */ +public class FolderTestRunner { + private final ArrayList files; + private Thread thread; + + /** + * Creates a new instance + * + * @param folder the folder to scan + */ + public FolderTestRunner(File folder) { + files = new ArrayList<>(); + scan(folder.getPath().length() + 1, folder); + } + + private void scan(int rootLength, File folder) { + File[] fileList = folder.listFiles(); + if (fileList != null) { + Arrays.sort(fileList, Comparator.comparing(f -> f.getPath().toLowerCase())); + for (File f : fileList) + if (f.isDirectory()) + scan(rootLength, f); + else if (f.isFile() && f.getName().endsWith(".dig")) + files.add(new FileToTest(rootLength, f)); + } + } + + /** + * Starts all the tests. + * The test execution is done in a new thread, so this method returns immediately. + * + * @param fileChangedListener the listsener to notify if a file status changed + * @param shapeFactory the shape factory + * @param library the element library + */ + public void startTests(FileChangedListener fileChangedListener, ShapeFactory shapeFactory, ElementLibrary library) { + thread = new Thread(new TestRunner(files, fileChangedListener, shapeFactory, library)); + thread.setDaemon(true); + thread.start(); + } + + /** + * Waits until tests are done. + * + * @throws InterruptedException InterruptedException + */ + public void waitUntilFinished() throws InterruptedException { + thread.join(); + } + + /** + * @return the list of files to test + */ + public ArrayList getFiles() { + return files; + } + + /** + * Describes the file to test + */ + public static final class FileToTest { + + /** + * the status of the file + */ + public enum Status { + /** + * status unknown + */ + unknown, + /** + * all tests have passed + */ + passed, + /** + * there was an exception during model building or execution + */ + error, + /** + * at least one test has failed + */ + failed + } + + private final File file; + private final String name; + private String message = "-"; + private FileToTest.Status status = FileToTest.Status.unknown; + private int rowCount; + + + private FileToTest(int rootLength, File file) { + this.file = file; + name = file.getPath().substring(rootLength); + } + + /** + * @return the name of this file + */ + public String getName() { + return name; + } + + private void setMessage(String message, FileToTest.Status status) { + this.message = message; + this.status = status; + } + + /** + * @return the message to show + */ + public String getMessage() { + return message; + } + + /** + * @return the status of this file + */ + public Status getStatus() { + return status; + } + + /** + * @return the tested file + */ + public File getFile() { + return file; + } + + private void setTestRows(int rowCount) { + this.rowCount = rowCount; + } + + /** + * @return the number of test case rows + */ + public int getRowCount() { + return rowCount; + } + } + + private static final class TestRunner implements Runnable { + private final ArrayList files; + private final FileChangedListener fileChangedListener; + private final ShapeFactory shapeFactory; + private final ElementLibrary library; + + private TestRunner(ArrayList files, FileChangedListener fileChangedListener, ShapeFactory shapeFactory, ElementLibrary library) { + this.files = files; + this.fileChangedListener = fileChangedListener; + this.shapeFactory = shapeFactory; + this.library = library; + } + + @Override + public void run() { + for (int i = 0; i < files.size(); i++) { + FileToTest f = files.get(i); + try { + Circuit circuit = Circuit.loadCircuit(f.file, shapeFactory); + ArrayList testCases = new ArrayList<>(); + for (VisualElement el : circuit.getElements()) { + if (el.equalsDescription(TestCaseElement.TESTCASEDESCRIPTION)) { + String label = el.getElementAttributes().getCleanLabel(); + TestCaseDescription testData = el.getElementAttributes().get(TestCaseElement.TESTDATA); + testCases.add(new TestCase(label, testData)); + } + } + if (testCases.isEmpty()) + setMessage(f, i, Lang.get("err_noTestData"), FileToTest.Status.unknown); + else { + Model model = new ModelCreator(circuit, library).createModel(false); + StringBuilder sb = new StringBuilder(); + int rowCount = 0; + for (TestCase tc : testCases) { + try { + TestExecutor te = new TestExecutor(tc.testData).create(model); + if (te.allPassed()) { + rowCount += te.getResult().getRows(); + } else { + if (sb.length() > 0) + sb.append("; "); + sb.append(Lang.get("msg_test_N_Failed", tc.label)); + } + } catch (TestingDataException | NodeException e) { + if (sb.length() > 0) + sb.append("; "); + sb.append(tc.label).append(": ").append(e.getMessage()); + } + } + if (sb.length() == 0) { + f.setTestRows(rowCount); + setMessage(f, i, Lang.get("msg_testPassed_N", rowCount), FileToTest.Status.passed); + } else + setMessage(f, i, sb.toString(), FileToTest.Status.failed); + } + + } catch (IOException | NodeException | ElementNotFoundException | PinException e) { + setMessage(f, i, e.getMessage(), FileToTest.Status.error); + } + } + } + + private void setMessage(FileToTest f, int i, String message, FileToTest.Status status) { + f.setMessage(message, status); + fileChangedListener.messageChanged(f, i); + } + } + + private static final class TestCase { + private final String label; + private final TestCaseDescription testData; + + private TestCase(String label, TestCaseDescription testData) { + this.label = label; + this.testData = testData; + } + } + + /** + * Interface to notify a listener for changes + */ + public interface FileChangedListener { + /** + * Called if a file message has changed + * + * @param f the file changed + * @param row the row index + */ + void messageChanged(FileToTest f, int row); + } +} diff --git a/src/test/java/de/neemann/digital/testing/FolderTestRunnerTest.java b/src/test/java/de/neemann/digital/testing/FolderTestRunnerTest.java new file mode 100644 index 000000000..05d8ff47d --- /dev/null +++ b/src/test/java/de/neemann/digital/testing/FolderTestRunnerTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.testing; + +import de.neemann.digital.draw.library.ElementLibrary; +import de.neemann.digital.draw.shapes.ShapeFactory; +import de.neemann.digital.integration.Resources; +import junit.framework.TestCase; + +import java.io.File; +import java.io.IOException; + +public class FolderTestRunnerTest extends TestCase { + + private static final int[] ROWS = new int[]{8, 512, 4, 4, 512}; + + public void testFolderTest() throws InterruptedException, IOException { + File f = new File(Resources.getRoot(), "dig/test/arith"); + FolderTestRunner ft = new FolderTestRunner(f); + assertEquals(5, ft.getFiles().size()); + + + ElementLibrary library = new ElementLibrary(); + library.setRootFilePath(f.getParentFile()); + ShapeFactory shapeFactory = new ShapeFactory(library); + ft.startTests( + (fileToTest, row) -> { + assertEquals("row " + row, ROWS[row], fileToTest.getRowCount()); + assertEquals(FolderTestRunner.FileToTest.Status.passed, fileToTest.getStatus()); + }, + shapeFactory, + library); + + ft.waitUntilFinished(); + } +} \ No newline at end of file