From 4bef2efc38bd0e9fcb80d62a4a6507537c92ff73 Mon Sep 17 00:00:00 2001
From: Lars Hamann These tests represent nearly the exact behavior of the
+ * USE shell. Only some whitespace differences and time outputs are ignored. Each test consists of the following files:
+ *
-// *
-// *
-// * All .use) - this file provides the (possible empty) model used
-// * for the tests.in) - this file can contain any commands USE supports.
-// * The expected output must be specified by starting a line with a star *.use and .in files must share the same name, e.g., t555.use and t555.in for test
-// * case 555. These files must be placed in the folder it/resources/testfiles/shell.
-// *
-// * If an integration test fails, two additional files are created:
-// *
-// *
-// * These files can be used to easily diff expected and current output.
-// */
-//public class ShellIT {
-//
-// /**
-// * This TestFactory enumerates the {testcasename}.expected - The expected output calculated from the {testcase}.in-file{testcasename}.actual - The ouptut captured while running the test..in-files in th folder testfiles/shell.
-// * For each file a DynamicTest is created with the name of the file.
-// *
-// * @return A Stream with one DynamicTest for each *.in-file.
-// */
-// @TestFactory
-// public StreamFunction is used to map
-// * a given testinput-file given as a Path
-// * to a DynamicTest.
-// *
-// * @return A DynamicTest that uses the function assertShellExpression
-// * to test the given testinput file.
-// */
-// private Function
-// *
-// *
-// * @param testFile createCommandFile.useFile and the created command file (runUSE).validateOutput).Path to the test input file to execute.
-// * @param useFile Path to the USE file containing the model to load for the test.
-// */
-// private void assertShellExpressions(Path testFile, Path useFile) {
-//
-// Path cmdFile = testFile.resolveSibling(testFile.getFileName() + ".cmd");
-//
-// ListexpectedOutput
-// * and actualOutput.
-// * If they differ, two files are written at the location of the
-// * testFile. One with the expected output (.expected)
-// * and one with the actual output (.actual).
-// *
-// * @param testFile The Path to the testFile
-// * @param expectedOutput List of strings with the expected output (one String per line)
-// * @param actualOutput List of strings with the actual output (one String per line)
-// */
-// private void validateOutput(Path testFile, Listdata
-// * to the file loctaed by the Path file.
-// *
-// * If the file is not accessible, i.e., an IOException is thrown,
-// * the exceptions is cathed and the test case fails.
-// * @param data The List of string (lines) to write.
-// * @param file The path to the file to write (file is overwritten).
-// */
-// private void writeToFile(ListcmdFile.
-// * The file contains all commands that are specified in the inFile.
-// * The expected output, i.e., lines starting with a * are added to the list expectedOutput.
-// *
-// * @param inFile The Path to the test input file.
-// * @param cmdFile The Path where to create the command file.
-// * @return A List which is filled with the expected output of USE.
-// */
-// private ListuseFile as the model
-// * and the cmdFile to execute commands.
-// * The output is captured from the output and error streams.
-// *
-// * @param useFile Path to the USE model to load on startup
-// * @param cmdFile Path to the commands file to execute
-// *
-// * @return A List of strings. Each string is one line of output.
-// */
-// private List
+ *
All {@code .use and .in} files must share the same name, e.g., t555.use and t555.in for test
+ * case 555. These files must be placed in the folder {@code it/resources/testfiles/shell}.
If an integration test fails, two additional files are created: + *
{testcase}.in}-filetestfiles/shell}.
+ * For each file a {@code DynamicTest} is created with the name of the file.
+ *
+ * @return A {@code Stream with one DynamicTest for each *.in}-file.
+ */
+ @TestFactory
+ public Stream evaluateExpressionFiles() {
+ URL testDirURL = getClass().getClassLoader().getResource("testfiles/shell");
+ Path testDirPath = null;
+
+ if (testDirURL == null) {
+ fail("Directory for shell integration tests not found!");
+ }
+
+ try {
+ testDirPath = Path.of(testDirURL.toURI());
+ } catch (URISyntaxException e) {
+ fail("Directory for shell integration tests not found!");
+ }
+
+ try {
+ return Files.walk(testDirPath).filter(
+ path -> path.getFileName().toString().endsWith(".in")
+ ).map(mapInFileToTest());
+ } catch (IOException e) {
+ fail("Error iterating shell integration test input files!");
+ }
+
+ return Stream.empty();
+ }
+
+ /**
+ * This {@code Function} is used to map
+ * a given testinput-file given as a {@code Path}
+ * to a {@code DynamicTest}.
+ *
+ * @return A {@code DynamicTest that uses the function assertShellExpression}
+ * to test the given testinput file.
+ */
+ private Function mapInFileToTest() {
+ return path -> {
+ final String modelFilename = path.getFileName().toString().replace(".in", ".use");
+ final Path modelPath = path.resolveSibling(modelFilename);
+
+ return DynamicTest.dynamicTest(path.getFileName().toString(), path.toUri(), () -> assertShellExpressions(path, modelPath));
+ };
+ }
+
+ /**
+ * This function controls the overall process for test for a single testfile.
+ *
+ * The process is as follows:
+ *
+ * - a command file and the expected output are created by examining the input file (via {@code createCommandFile}.
+ * - USE is executed using the {@code useFile
and the created command file (runUSE}).
+ * The output of USE is compared to the expected output created in 1. ({@code validateOutput}).
+ *
+ *
+ * @param testFile {@code Path} to the test input file to execute.
+ * @param useFile {@code Path} to the USE file containing the model to load for the test.
+ */
+ private void assertShellExpressions(Path testFile, Path useFile) {
+
+ Path cmdFile = testFile.resolveSibling(testFile.getFileName() + ".cmd");
+
+ List expectedOutput = createCommandFile(testFile, cmdFile);
+
+ List actualOutput = runUSE(useFile, cmdFile).collect(Collectors.toList());
+
+ validateOutput(testFile, expectedOutput, actualOutput);
+ }
+
+ /**
+ * Compares the two lists of strings {@code expectedOutput}
+ * and {@code actualOutput}.
+ * If they differ, two files are written at the location of the
+ * {@code testFile . One with the expected output (.expected})
+ * and one with the actual output ({@code .actual}).
+ *
+ * @param testFile The {@code Path to the testFile}
+ * @param expectedOutput List of strings with the expected output (one String per line)
+ * @param actualOutput List of strings with the actual output (one String per line)
+ */
+ private void validateOutput(Path testFile, List expectedOutput, List actualOutput) {
+ //create a configured DiffRowGenerator
+ DiffRowGenerator generator = DiffRowGenerator.create()
+ .showInlineDiffs(true)
+ .mergeOriginalRevised(true)
+ .inlineDiffByWord(true)
+ .ignoreWhiteSpaces(true)
+ .lineNormalizer( (s) -> s ) // No normalization required
+ .oldTag((f, start) -> start ? "-\033[9m" : "\033[m-") //introduce markdown style for strikethrough
+ .newTag((f, start) -> start ? "+\033[97;42m" : "\033[m+") //introduce markdown style for bold
+ .build();
+
+ //compute the differences for two test texts.
+ List rows = generator.generateDiffRows(expectedOutput, actualOutput);
+ Predicate filter = d -> d.getTag() != DiffRow.Tag.EQUAL;
+
+ if (rows.stream().anyMatch(filter)) {
+ StringBuilder diffMsg = new StringBuilder("USE output does not match expected output!").append(System.lineSeparator());
+
+ diffMsg.append("Testfile: ").append(testFile).append(System.lineSeparator());
+
+ diffMsg.append(System.lineSeparator()).append("Note: the position is not the position in the input file!");
+ diffMsg.append(System.lineSeparator()).append(System.lineSeparator());
+
+ rows.stream().filter(filter).forEach(
+ row ->diffMsg.append(System.lineSeparator()).append(row.getOldLine())
+ );
+
+ writeToFile(expectedOutput, testFile.getParent().resolve(testFile.getFileName().toString() + ".expected"));
+ writeToFile(actualOutput, testFile.getParent().resolve(testFile.getFileName().toString() + ".actual"));
+
+ fail(diffMsg.toString());
+ }
+ }
+
+ /**
+ * Helper method that writes the list of strings {@code data}
+ * to the file located by the {@code Path} {@code file}.
+ *
+ * If the file is not accessible, i.e., an IOException is thrown,
+ * the exceptions is caught and the test case fails.
+ * @param data The {@code List} of string (lines) to write.
+ * @param file The path to the file to write (file is overwritten).
+ */
+ private void writeToFile(List data, Path file) {
+ try (FileWriter writer = new FileWriter(file.toFile())) {
+
+ for (String line : data) {
+ writer.write(line);
+ writer.write(System.lineSeparator());
+ }
+ } catch (IOException e) {
+ fail("Testoutput could not be written!", e);
+ }
+ }
+
+ /**
+ * Creates a USE-command file at the position located by the path {@code cmdFile}.
+ * The file contains all commands that are specified in the {@code inFile}.
+ * The expected output, i.e., lines starting with a {@code *} are added to the list {@code expectedOutput}.
+ *
+ * @param inFile The {@code Path} to the test input file.
+ * @param cmdFile The {@code Path} where to create the command file.
+ * @return A {@code List} which is filled with the expected output of USE.
+ */
+ private List createCommandFile(Path inFile, Path cmdFile) {
+ List expectedOutput = new LinkedList<>();
+
+ // Build USE command file and build expected output
+ try (
+ Stream linesStream = Files.lines(inFile, StandardCharsets.UTF_8);
+ FileWriter cmdWriter = new FileWriter(cmdFile.toFile(), StandardCharsets.UTF_8, false)
+ ) {
+
+ linesStream.forEach(inputLine -> {
+
+ // Ignore empty lines in expected, since they are also suppressed in actual output
+ if (inputLine.isBlank())
+ return;
+
+ if ((inputLine.startsWith("*") || inputLine.startsWith("#"))
+ && inputLine.substring(1).isBlank()) {
+ return;
+ }
+
+ if (inputLine.startsWith("*")) {
+ // Input line minus prefix(*) is expected output
+ expectedOutput.add(inputLine.substring(1).trim());
+ } else if (!inputLine.startsWith("#")) { // Not a comment
+ try {
+ cmdWriter.write(inputLine);
+ cmdWriter.write(System.lineSeparator());
+
+ // Multi line commands (backslash and dot) are ignored
+ if (!inputLine.matches("^[\\\\.]$")) {
+ expectedOutput.add(inputLine);
+ }
+ } catch (IOException e1) {
+ fail("Could not write USE command file for test!", e1);
+ }
+ }
+ });
+ } catch (IOException e) {
+ fail("Could not write USE command file for test!", e);
+ }
+
+ return expectedOutput;
+ }
+
+ /**
+ * Executes USE with the given {@code useFile} as the model
+ * and the {@code cmdFile} to execute commands.
+ * The output is captured from the output and error streams.
+ *
+ * @param useFile Path to the USE model to load on startup
+ * @param cmdFile Path to the commands file to execute
+ * @return A {@code List} of strings. Each string is one line of output.
+ */
+ private Stream runUSE(Path useFile, Path cmdFile) {
+
+ // We need to specify a concrete locale to always get the same formatted result
+ Locale.setDefault(new Locale("en", "US"));
+
+ Options.resetOptions();
+ USEWriter.getInstance().clearLog();
+
+ String homeDir = null;
+ try {
+ homeDir = useFile.getParent().resolve("../../../../../use-core/target/classes").toFile().getCanonicalPath();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ String[] args = new String[] {
+ "-nogui",
+ "-nr",
+ "-t",
+ "-it",
+ "-q",
+ "-oclAnyCollectionsChecks:E",
+ "-extendedTypeSystemChecks:E",
+ /* This is currently an unstable workaround
+ USE determines the plugin and the extensions to OCL by fixed paths.
+ For now, the use-core module contains the directories including the extensions
+ and an empty plugins folder.
+ The folder is located: use/use-core/target/classes
+ Therefore, this is used as the USE home
+ */
+ "-H=" + homeDir,
+ useFile.toString(),
+ cmdFile.toString()};
+
+ Main.main(args);
+
+ try (ByteArrayOutputStream protocol = new ByteArrayOutputStream();) {
+ USEWriter.getInstance().writeProtocolFile(protocol);
+ String output = protocol.toString();
+ return output.lines().filter(l -> !l.isBlank());
+ } catch (IOException e) {
+ fail(e);
+ }
+
+ return Stream.empty();
+ }
+}
diff --git a/use-gui/src/main/java/module-info.java b/use-gui/src/main/java/module-info.java
index 0846bb894..28a36d0fd 100644
--- a/use-gui/src/main/java/module-info.java
+++ b/use-gui/src/main/java/module-info.java
@@ -1,15 +1,14 @@
module use.gui {
- requires org.eclipse.jdt.annotation;
requires use.core;
requires vtd.xml;
- requires com.google.common;
requires itextpdf;
requires javafx.fxml;
requires javafx.web;
requires org.kordamp.desktoppanefx.core;
requires javafx.swing;
requires annotations;
- requires jline;
+ requires lombok;
+ requires com.google.common;
opens org.tzi.use.gui.main to javafx.fxml;
exports org.tzi.use.main.gui;
exports org.tzi.use.gui.views;
@@ -32,6 +31,4 @@
exports org.tzi.use.gui.views.diagrams.behavior.shared to com.google.common;
exports org.tzi.use.gui.views.selection to com.google.common;
exports org.tzi.use.gui.views.diagrams.statemachine to com.google.common;
-
-
}
\ No newline at end of file
diff --git a/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java b/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java
index ed54cfc10..157bd4aee 100644
--- a/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java
+++ b/use-gui/src/main/java/org/tzi/use/gui/mainFX/MainWindow.java
@@ -1418,7 +1418,7 @@ private void createClassDiagram() {
// Calling the Swing MainWindow to get the ClassDiagram out of it
org.tzi.use.gui.main.MainWindow mainwindow = org.tzi.use.gui.main.MainWindow.create(fSession, fPluginRuntime);
- ClassDiagramView cdv = new ClassDiagramView(mainwindow, fSession.system(), loadLayout);
+ ClassDiagramView cdv = new ClassDiagramView(mainwindow, fSession.system(), loadLayout, fPluginRuntime);
ViewFrame f = new ViewFrame("Class diagram", cdv, "ClassDiagram.gif");
JComponent c = (JComponent) f.getContentPane();
diff --git a/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java b/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java
index 983eaaab0..2cd4efd9b 100644
--- a/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java
+++ b/use-gui/src/main/java/org/tzi/use/gui/views/diagrams/elements/edges/EdgeBase.java
@@ -30,7 +30,6 @@
import java.util.Map;
import java.util.Set;
-import lombok.Getter;
import org.tzi.use.graph.DirectedEdgeBase;
import org.tzi.use.gui.util.PersistHelper;
import org.tzi.use.gui.views.diagrams.DiagramOptions;
@@ -155,7 +154,7 @@ public enum PropertyOwner {
* @param source The source node of the edge.
* @param target The target node of the edge.
* @param edgeName The name of the edge.
- * @param diagram The diagram this edge belongs to.
+ * @param opt The diagram options this edge uses to.
* @param completeEdgeMoveMovesUserWayPoints If true, user defined way points are moved by the edge if
* source and target are selected.
*/
From cb184e8b0c486aa9eab85106a1b5affb9e2a2a80 Mon Sep 17 00:00:00 2001
From: Lars Hamann
Date: Sun, 2 Nov 2025 13:43:47 +0100
Subject: [PATCH 2/3] fix: repaired integration test and output
- USEWriter was not used for System.out and System.err
- Added initialization at the very beginning of main()
- Added further encoding information
---
.idea/encodings.xml | 3 ++
.../java/org/tzi/use/main/shell/ShellIT.java | 38 ++++++++++---------
.../main/java/org/tzi/use/main/gui/Main.java | 8 +++-
3 files changed, 30 insertions(+), 19 deletions(-)
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index b992b4245..882bb50c6 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,6 +1,9 @@
+
+
+
diff --git a/use-gui/src/it/java/org/tzi/use/main/shell/ShellIT.java b/use-gui/src/it/java/org/tzi/use/main/shell/ShellIT.java
index 1e2a40e68..95f96f193 100644
--- a/use-gui/src/it/java/org/tzi/use/main/shell/ShellIT.java
+++ b/use-gui/src/it/java/org/tzi/use/main/shell/ShellIT.java
@@ -34,12 +34,13 @@
*
* Each test consists of the following files:
*
- * - a model file (suffix {@code .use}) - this file provides the (possible empty) model used
+ *
- A model file (suffix {@code .use}) - this file provides the (possible empty) model used
* for the tests
- * - an input file (suffix: {@code .in}) - this file can contain any commands USE supports.
+ *
- An input file (suffix: {@code .in}) - this file can contain any commands USE supports.
* The expected output must be specified by starting a line with a star {@code *}
- * - any other used file from the command line, e.g., ASSL- or command-files.
- *
+ * Any other used file from the command line, e.g., ASSL- or command-files.
+ *
+ *
*
* All {@code .use
and .in} files must share the same name, e.g., t555.use and t555.in for test
* case 555. These files must be placed in the folder {@code it/resources/testfiles/shell}.
@@ -107,10 +108,11 @@ private Function mapInFileToTest() {
*
* The process is as follows:
*
- * - a command file and the expected output are created by examining the input file (via {@code createCommandFile}.
+ * - A command file and the expected output are created by examining the input file (via {@code createCommandFile}).
* - USE is executed using the {@code useFile
and the created command file (runUSE}).
* The output of USE is compared to the expected output created in 1. ({@code validateOutput}).
- *
+ *
+ *
*
* @param testFile {@code Path} to the test input file to execute.
* @param useFile {@code Path} to the USE file containing the model to load for the test.
@@ -145,8 +147,8 @@ private void validateOutput(Path testFile, List expectedOutput, List s ) // No normalization required
- .oldTag((f, start) -> start ? "-\033[9m" : "\033[m-") //introduce markdown style for strikethrough
- .newTag((f, start) -> start ? "+\033[97;42m" : "\033[m+") //introduce markdown style for bold
+ .oldTag((f, start) -> start ? "-\033[9m" : "\033[m-") //introduce style for strikethrough
+ .newTag((f, start) -> start ? "+\033[97;42m" : "\033[m+") //introduce style for bold
.build();
//compute the differences for two test texts.
@@ -156,7 +158,7 @@ private void validateOutput(Path testFile, List expectedOutput, List expectedOutput, List
*
* If the file is not accessible, i.e., an IOException is thrown,
- * the exceptions is caught and the test case fails.
+ * the exception is caught and the test case fails.
* @param data The {@code List} of string (lines) to write.
- * @param file The path to the file to write (file is overwritten).
+ * @param file The path to the file to write (the file is overwritten).
*/
private void writeToFile(List data, Path file) {
try (FileWriter writer = new FileWriter(file.toFile())) {
@@ -189,7 +191,7 @@ private void writeToFile(List data, Path file) {
writer.write(System.lineSeparator());
}
} catch (IOException e) {
- fail("Testoutput could not be written!", e);
+ fail("Test output could not be written!", e);
}
}
@@ -213,7 +215,7 @@ private List createCommandFile(Path inFile, Path cmdFile) {
linesStream.forEach(inputLine -> {
- // Ignore empty lines in expected, since they are also suppressed in actual output
+ // Ignore empty lines in expected, since they are also suppressed in the actual output
if (inputLine.isBlank())
return;
@@ -230,7 +232,7 @@ private List createCommandFile(Path inFile, Path cmdFile) {
cmdWriter.write(inputLine);
cmdWriter.write(System.lineSeparator());
- // Multi line commands (backslash and dot) are ignored
+ // Multi-line commands (backslash and dot) are ignored
if (!inputLine.matches("^[\\\\.]$")) {
expectedOutput.add(inputLine);
}
@@ -258,12 +260,12 @@ private List createCommandFile(Path inFile, Path cmdFile) {
private Stream runUSE(Path useFile, Path cmdFile) {
// We need to specify a concrete locale to always get the same formatted result
- Locale.setDefault(new Locale("en", "US"));
+ Locale.setDefault(Locale.forLanguageTag("en-US"));
Options.resetOptions();
USEWriter.getInstance().clearLog();
- String homeDir = null;
+ String homeDir;
try {
homeDir = useFile.getParent().resolve("../../../../../use-core/target/classes").toFile().getCanonicalPath();
} catch (IOException e) {
@@ -291,7 +293,7 @@ private Stream runUSE(Path useFile, Path cmdFile) {
Main.main(args);
- try (ByteArrayOutputStream protocol = new ByteArrayOutputStream();) {
+ try (ByteArrayOutputStream protocol = new ByteArrayOutputStream()) {
USEWriter.getInstance().writeProtocolFile(protocol);
String output = protocol.toString();
return output.lines().filter(l -> !l.isBlank());
@@ -299,6 +301,6 @@ private Stream runUSE(Path useFile, Path cmdFile) {
fail(e);
}
- return Stream.empty();
+ return Stream.empty();
}
}
diff --git a/use-gui/src/main/java/org/tzi/use/main/gui/Main.java b/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
index 6ac6ec488..e6216f65a 100644
--- a/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
+++ b/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
@@ -2,15 +2,21 @@
import org.tzi.use.main.gui.fx.MainJavaFX;
import org.tzi.use.main.gui.swing.MainSwing;
+import org.tzi.use.util.USEWriter;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
+ // set System.out to the OldUSEWriter to protocol the output.
+ System.setOut(USEWriter.getInstance().getOut());
+ // set System.err to the OldUSEWriter to protocol the output.
+ System.setErr(USEWriter.getInstance().getErr());
+
boolean useJavaFX = false;
// cleanedArgs is needed to avoid giving -jfx into the MainJavaFX,
- // because Application.launch() tries to parse all arguments as JavaFX-compatible
+ // because Application.launch() tries to parse all arguments as JavaFX compatible
// and throws an error if it encounters an unknown one (like -jfx).
List cleanedArgs = new ArrayList<>();
for (String arg : args) {
From ccee6a8c49e5217a66c557b2bfef479b669aa3ff Mon Sep 17 00:00:00 2001
From: Lars Hamann <58341317+h-man2@users.noreply.github.com>
Date: Sun, 2 Nov 2025 14:32:21 +0100
Subject: [PATCH 3/3] Update
use-gui/src/main/java/org/tzi/use/main/gui/Main.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
use-gui/src/main/java/org/tzi/use/main/gui/Main.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/use-gui/src/main/java/org/tzi/use/main/gui/Main.java b/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
index e6216f65a..258cbaea0 100644
--- a/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
+++ b/use-gui/src/main/java/org/tzi/use/main/gui/Main.java
@@ -9,9 +9,9 @@
public class Main {
public static void main(String[] args) {
- // set System.out to the OldUSEWriter to protocol the output.
+ // set System.out to the USEWriter to protocol the output.
System.setOut(USEWriter.getInstance().getOut());
- // set System.err to the OldUSEWriter to protocol the output.
+ // set System.err to the USEWriter to protocol the output.
System.setErr(USEWriter.getInstance().getErr());
boolean useJavaFX = false;