Refactoring of TestAllDialog

This commit is contained in:
hneemann 2018-06-01 23:09:34 +02:00
parent 9c781d0731
commit 81e41a973c
3 changed files with 315 additions and 150 deletions

View File

@ -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<FileToTest> 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<FileToTest> 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<FileToTest> files;
private final ArrayList<FolderTestRunner.FileToTest> files;
private ArrayList<TableModelListener> listener;
private FileModel(ArrayList<FileToTest> files) {
private FileModel(ArrayList<FolderTestRunner.FileToTest> 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<FileToTest> files;
private final FileModel tableModel;
private final ShapeFactory shapeFactory;
private final ElementLibrary library;
private TestRunner(ArrayList<FileToTest> 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<TestCase> 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<FileToTest> files;
private StateRenderer(ArrayList<FileToTest> 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;
}

View File

@ -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<FileToTest> 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<FileToTest> 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<FileToTest> files;
private final FileChangedListener fileChangedListener;
private final ShapeFactory shapeFactory;
private final ElementLibrary library;
private TestRunner(ArrayList<FileToTest> 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<TestCase> 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);
}
}

View File

@ -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();
}
}