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:
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user