feat: F4.6g1 the echo server and the generic client windows

Oracle EchoWindowRun uses Java's echo server and client windows as a
person does; SaveAfterRunRun echo saves what Java keeps after the run.
The room takes setActive with a port, connect/disconnect/send, sends
both views and writes the server's port and the client's target back.
The editor's windows show Java's buttons, fields and lines; a port that
is no port is marked and not handed over (Java's button throws).
Core fix: an empty message (one empty segment with the push, as Java's
erstelleSegmente makes) counted as a repeat and never arrived.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 20:04:33 +00:00
co-authored by Claude Opus 5.5
parent 3dc2713f81
commit e31da62ae0
24 changed files with 1481 additions and 18 deletions
+33
View File
@@ -1460,3 +1460,36 @@ Gemessen (03.10.2026):
* Der Bildbetrachter heißt nach dem Öffnen wie die Datei; jedes geöffnete
Bild legt Java über das vorige, eine Textdatei zeigt es als leeres Bild,
ohne Fund bleibt alles.
### Echo-Server und Einfacher Client (F4.6g1)
`EchoWindowRun` öffnet Javas `GUIApplicationServerBausteinWindow` und
`GUIApplicationClientBausteinWindow` auf je einem `GUIDesktopPanel` im Netz
von mail-lan (Server 192.168.0.30, Client 192.168.0.20) und benutzt sie wie
eine Person: Port tippen und starten (auch „abc“), Adresse und Port tippen
und „Verbinden“ (auch Port „x“), Nachrichten senden (auch eine leere und
eine mit zwei Zeilen), den Server anhalten, während der Client verbunden
ist, „Trennen“, ein Port ohne Server, ein unbekannter Name. Je Schritt:
Knöpfe, Felder (und ob sie beschreibbar sind), Zeilen beider Fenster und die
Ausnahmen, die Javas Knöpfe warfen (`packages/sim/test/echoWindow.java.json`).
`SaveAfterRunRun echo` speichert danach, was Java vom Echo im Projekt
behält (`packages/sim/test/fixtures/after-run-echo.fls`).
Gemessen (03.10.2026):
* Server: „Starten“/„Anhalten“, Port 55555, das Feld nur angehalten
beschreibbar; ein Port, der keine Zahl ist, wirft
(`NumberFormatException`) und nichts geschieht.
* Client: Adresse leer, Port 55555; „Verbinden“/„Trennen“; „Senden“ nur
verbunden, danach ist das Nachrichtenfeld leer; die Felder sind verbunden
gesperrt und zeigen nach jeder Zeile wieder die Werte des Programms.
* Eine leere Nachricht geht als ein leeres Segment mit PSH hinaus
(`erstelleSegmente` macht mindestens eines) und kommt als „>>“, „<<“
zurück; Java beendet eine Nachricht mit jedem PSH, auch dem eines
wiederholten Segments.
* Der angehaltene Server schließt die Verbindung („Socket zu … geschlossen“,
beim Client „Socket zu … getrennt“). Ein Port ohne Server endet in
„Zeitüberschreitung beim Verbindungsaufbau“, ein unbekannter Name in „Ziel
konnte nicht bestimmt werden!“.
* Nach dem Lauf speichert Java den Port des Servers, dass er läuft, und
Adresse und Port, die der Client zuletzt nahm.
+14 -3
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.java" "$here/src/MailWindowRun.java" "$here/src/SaveAfterRunRun.java" "$here/src/TextEditorWindowRun.java" "$here/src/FileExplorerWindowRun.java"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.java" "$here/src/MailWindowRun.java" "$here/src/SaveAfterRunRun.java" "$here/src/TextEditorWindowRun.java" "$here/src/FileExplorerWindowRun.java" "$here/src/EchoWindowRun.java"
# FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing.
@@ -391,8 +391,9 @@ test -s "$output"
# What a run leaves in FILIUS' project: an example started, a story played,
# stopped as the design mode stops it and saved (SaveAfterRunRun): the room's
# write-back into the document (packages/sim/test/writeBack.test.ts).
for run in email:email_komplett dhcp:einfaches_rechnernetz_komplett; do
# write-back into the document (packages/sim/test/writeBack.test.ts; echo:
# packages/sim/test/echoWindow.test.ts).
for run in email:email_komplett dhcp:einfaches_rechnernetz_komplett echo:email_komplett; do
story="${run%%:*}"
output="$root/packages/sim/test/fixtures/after-run-$story.fls"
rm -f "$output"
@@ -421,6 +422,16 @@ HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -
"$root/packages/core/test/scenarios/mail-lan.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
test -s "$output"
# FILIUS' echo server and generic client windows used as a person does —
# the port typed (also one that is no number), "Verbinden", "Senden" (also
# nothing), the server stopped while connected, failures (EchoWindowRun): the
# room's echo windows (packages/sim/test/echoWindow.test.ts).
output="$root/packages/sim/test/echoWindow.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" EchoWindowRun \
"$root/packages/core/test/scenarios/mail-lan.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
test -s "$output"
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts)
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
+197
View File
@@ -0,0 +1,197 @@
// Uses FILIUS' own echo server and generic client windows
// (GUIApplicationServerBausteinWindow, GUIApplicationClientBausteinWindow on
// their GUIDesktopPanels) on mail-lan's network as a person does: the
// server's port field and start button, the client's address and port
// fields, "Verbinden"/"Trennen", the message field and "Senden" — a port
// that is no number too, an empty message, the server stopped while the
// client is connected, a port nobody listens on, a name nobody knows. After
// each step it writes what both windows show: the buttons' texts, the
// fields (and whether they can be typed into), the lines. The reference for
// the editor's echo windows (packages/sim/test/echoWindow.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes EchoWindowRun <scenario.json> <output json>
import filius.gui.anwendungssicht.GUIApplicationClientBausteinWindow;
import filius.gui.anwendungssicht.GUIApplicationServerBausteinWindow;
import filius.gui.anwendungssicht.GUIDesktopPanel;
import filius.software.system.Betriebssystem;
import java.awt.FlowLayout;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class EchoWindowRun {
static final String CLIENT = "00000000-0000-0000-0000-000000000201";
static final String SERVER = "00000000-0000-0000-0000-000000000203";
static final List<Object> steps = new ArrayList<>();
static GUIApplicationServerBausteinWindow server;
static GUIApplicationClientBausteinWindow client;
/** Exceptions the windows' buttons threw on the event thread since the last step. */
static final List<String> thrown = new ArrayList<>();
static Object field(Class<?> owner, Object object, String name) throws Exception {
Field field = owner.getDeclaredField(name);
field.setAccessible(true);
return field.get(object);
}
static void onEdt(Runnable action) throws Exception {
SwingUtilities.invokeLater(() -> {
try {
action.run();
} catch (RuntimeException error) {
synchronized (thrown) {
thrown.add(error.getClass().getSimpleName() + ": " + error.getMessage());
}
}
});
Thread.sleep(400);
}
static Map<String, Object> textField(JTextField field) {
Map<String, Object> shown = new LinkedHashMap<>();
shown.put("text", field.getText());
shown.put("editable", field.isEditable());
return shown;
}
static void step(String name) throws Exception {
Class<?> s = GUIApplicationServerBausteinWindow.class;
Class<?> c = GUIApplicationClientBausteinWindow.class;
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", name);
Map<String, Object> serverShown = new LinkedHashMap<>();
serverShown.put("button", ((JButton) field(s, server, "bStartStop")).getText());
serverShown.put("port", textField((JTextField) field(s, server, "tfPort")));
serverShown.put("log", ((JTextArea) field(s, server, "taLog")).getText());
result.put("server", serverShown);
Map<String, Object> clientShown = new LinkedHashMap<>();
clientShown.put("connect", ((JButton) field(c, client, "btVerbinden")).getText());
clientShown.put("sendEnabled", ((JButton) field(c, client, "btSenden")).isEnabled());
clientShown.put("address", textField((JTextField) field(c, client, "tfServerAdresse")));
clientShown.put("port", textField((JTextField) field(c, client, "tfServerPort")));
clientShown.put("message", ((JTextArea) field(c, client, "taSenden")).getText());
clientShown.put("output", ((JTextArea) field(c, client, "taAusgabe")).getText());
result.put("client", clientShown);
synchronized (thrown) {
result.put("thrown", new ArrayList<>(thrown));
thrown.clear();
}
steps.add(result);
}
static void type(Class<?> owner, Object window, String name, String text) throws Exception {
Object component = field(owner, window, name);
onEdt(() -> ((javax.swing.text.JTextComponent) component).setText(text));
}
static void press(Class<?> owner, Object window, String name, long wait) throws Exception {
JButton button = (JButton) field(owner, window, name);
onEdt(button::doClick);
Thread.sleep(wait);
}
public static void main(String[] args) throws Exception {
try {
run(args);
} catch (Throwable error) {
error.printStackTrace();
System.exit(1);
}
System.exit(0);
}
static void run(String[] args) throws Exception {
ScenarioNetwork network = ScenarioNetwork.read(args[0]);
Betriebssystem serverSystem = (Betriebssystem) network.nodes.get(SERVER).getSystemSoftware();
Betriebssystem clientSystem = (Betriebssystem) network.nodes.get(CLIENT).getSystemSoftware();
serverSystem.installApp("filius.software.clientserver.ServerBaustein", false);
clientSystem.installApp("filius.software.clientserver.ClientBaustein", false);
network.start();
Thread.sleep(500);
JFrame frame = new JFrame();
GUIDesktopPanel serverDesktop = new GUIDesktopPanel(serverSystem);
GUIDesktopPanel clientDesktop = new GUIDesktopPanel(clientSystem);
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(serverDesktop);
frame.getContentPane().add(clientDesktop);
server = (GUIApplicationServerBausteinWindow) serverDesktop.starteAnwendung("filius.software.clientserver.ServerBaustein");
client = (GUIApplicationClientBausteinWindow) clientDesktop.starteAnwendung("filius.software.clientserver.ClientBaustein");
SwingUtilities.invokeAndWait(() -> {
frame.pack();
frame.setVisible(true);
});
Thread.sleep(500);
Class<?> s = GUIApplicationServerBausteinWindow.class;
Class<?> c = GUIApplicationClientBausteinWindow.class;
step("open");
type(s, server, "tfPort", "abc");
press(s, server, "bStartStop", 500);
step("server: start with port abc");
type(s, server, "tfPort", "55555");
press(s, server, "bStartStop", 1000);
step("server: start with port 55555");
type(c, client, "tfServerAdresse", "192.168.0.30");
type(c, client, "tfServerPort", "x");
press(c, client, "btVerbinden", 500);
step("client: connect with port x");
type(c, client, "tfServerAdresse", "192.168.0.30");
type(c, client, "tfServerPort", "55555");
press(c, client, "btVerbinden", 6000);
step("client: connect to 192.168.0.30:55555");
type(c, client, "taSenden", "Hallo Echo");
press(c, client, "btSenden", 4000);
step("client: send Hallo Echo");
type(c, client, "taSenden", "");
press(c, client, "btSenden", 4000);
step("client: send nothing");
type(c, client, "taSenden", "Zeile 1\nZeile 2");
press(c, client, "btSenden", 4000);
step("client: send two lines");
press(s, server, "bStartStop", 4000);
step("server: stop while connected");
type(c, client, "taSenden", "noch da?");
press(c, client, "btSenden", 8000);
step("client: send after the server stopped");
press(c, client, "btVerbinden", 3000);
step("client: Trennen");
press(s, server, "bStartStop", 1000);
step("server: start again");
press(c, client, "btVerbinden", 6000);
step("client: connect again");
press(c, client, "btVerbinden", 3000);
step("client: Trennen again");
type(c, client, "tfServerPort", "4711");
press(c, client, "btVerbinden", 12000);
step("client: connect to a port nobody listens on");
type(c, client, "tfServerAdresse", "gibtsnicht");
type(c, client, "tfServerPort", "55555");
press(c, client, "btVerbinden", 12000);
step("client: connect to a name nobody knows");
Map<String, Object> output = new LinkedHashMap<>();
output.put("steps", steps);
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
network.stop();
System.out.println("echo window steps: " + steps.size());
}
}
@@ -18,11 +18,17 @@
// domain set to senior.org
// dhcp einfaches_rechnernetz_komplett: the notebooks get their
// addresses from the server (10 s)
// echo email_komplett, with FILIUS' echo server installed on Rechner 2
// and its client on Rechner 1 before the start: the server on port
// 7777 (as its window sets it), the client connected to it, a
// message sent, the client left connected
import filius.gui.GUIContainer;
import filius.gui.netzwerksicht.GUIKnotenItem;
import filius.hardware.knoten.Knoten;
import filius.rahmenprogramm.SzenarioVerwaltung;
import filius.software.clientserver.ClientBaustein;
import filius.software.clientserver.ServerBaustein;
import filius.software.email.Email;
import filius.software.email.EmailAnwendung;
import filius.software.email.EmailKonto;
@@ -93,6 +99,10 @@ public class SaveAfterRunRun {
if (story.equals("email")) {
((Betriebssystem) system("Rechner 1")).installAndStartAppIfAvailable("filius.software.lokal.Terminal");
}
if (story.equals("echo")) {
((Betriebssystem) system("Rechner 2")).installAndStartAppIfAvailable("filius.software.clientserver.ServerBaustein");
((Betriebssystem) system("Rechner 1")).installAndStartAppIfAvailable("filius.software.clientserver.ClientBaustein");
}
network.start();
Thread.sleep(2000);
switch (story) {
@@ -130,6 +140,22 @@ public class SaveAfterRunRun {
case "dhcp":
Thread.sleep(10000);
break;
case "echo": {
// GUIApplicationServerBausteinWindow's start button, then its client's "Verbinden" and "Senden".
ServerBaustein server = (ServerBaustein) system("Rechner 2").holeSoftware("filius.software.clientserver.ServerBaustein");
server.setPort(7777);
server.setAktiv(true);
server.setPort(7777);
Thread.sleep(1000);
ClientBaustein client = (ClientBaustein) system("Rechner 1").holeSoftware("filius.software.clientserver.ClientBaustein");
client.setZielIPAdresse(system("Rechner 2").primaryIPAdresse());
client.setZielPort(7777);
client.verbinden();
Thread.sleep(6000);
client.senden("Hallo");
Thread.sleep(4000);
break;
}
default:
throw new IllegalArgumentException("unknown story " + story);
}