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
+424
View File
@@ -0,0 +1,424 @@
{
"steps": [
{
"step": "open",
"server": {
"button": "Starten",
"port": {
"text": "55555",
"editable": true
},
"log": ""
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "",
"output": ""
},
"thrown": []
},
{
"step": "server: start with port abc",
"server": {
"button": "Starten",
"port": {
"text": "abc",
"editable": true
},
"log": ""
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "",
"output": ""
},
"thrown": [
"NumberFormatException: For input string: \"abc\""
]
},
{
"step": "server: start with port 55555",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "",
"output": ""
},
"thrown": []
},
{
"step": "client: connect with port x",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "x",
"editable": true
},
"message": "",
"output": ""
},
"thrown": [
"NumberFormatException: For input string: \"x\""
]
},
{
"step": "client: connect to 192.168.0.30:55555",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n"
},
"client": {
"connect": "Trennen",
"sendEnabled": true,
"address": {
"text": "192.168.0.30",
"editable": false
},
"port": {
"text": "55555",
"editable": false
},
"message": "",
"output": "Verbindung hergestellt\n"
},
"thrown": []
},
{
"step": "client: send Hallo Echo",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n"
},
"client": {
"connect": "Trennen",
"sendEnabled": true,
"address": {
"text": "192.168.0.30",
"editable": false
},
"port": {
"text": "55555",
"editable": false
},
"message": "",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n"
},
"thrown": []
},
{
"step": "client: send nothing",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n"
},
"client": {
"connect": "Trennen",
"sendEnabled": true,
"address": {
"text": "192.168.0.30",
"editable": false
},
"port": {
"text": "55555",
"editable": false
},
"message": "",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n"
},
"thrown": []
},
{
"step": "client: send two lines",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\n"
},
"client": {
"connect": "Trennen",
"sendEnabled": true,
"address": {
"text": "192.168.0.30",
"editable": false
},
"port": {
"text": "55555",
"editable": false
},
"message": "",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\n"
},
"thrown": []
},
{
"step": "server: stop while connected",
"server": {
"button": "Starten",
"port": {
"text": "55555",
"editable": true
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\n"
},
"thrown": []
},
{
"step": "client: send after the server stopped",
"server": {
"button": "Starten",
"port": {
"text": "55555",
"editable": true
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\n"
},
"thrown": []
},
{
"step": "client: Trennen",
"server": {
"button": "Starten",
"port": {
"text": "55555",
"editable": true
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\n"
},
"thrown": []
},
{
"step": "server: start again",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\nAnnahme von Verbindungsanfragen gestartet\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\n"
},
"thrown": []
},
{
"step": "client: connect again",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\nAnnahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:55719 hergestellt\n"
},
"client": {
"connect": "Trennen",
"sendEnabled": true,
"address": {
"text": "192.168.0.30",
"editable": false
},
"port": {
"text": "55555",
"editable": false
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\nFehler beim Verbindungsaufbau\nZeitüberschreitung beim Verbindungsaufbau\nVerbindung hergestellt\n"
},
"thrown": []
},
{
"step": "client: Trennen again",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\nAnnahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:55719 hergestellt\nSocket zu 192.168.0.20:55719 geschlossen\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\nFehler beim Verbindungsaufbau\nZeitüberschreitung beim Verbindungsaufbau\nVerbindung hergestellt\nVerbindung getrennt\nSocket zu 192.168.0.30:55555 getrennt\n"
},
"thrown": []
},
{
"step": "client: connect to a port nobody listens on",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\nAnnahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:55719 hergestellt\nSocket zu 192.168.0.20:55719 geschlossen\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "192.168.0.30",
"editable": true
},
"port": {
"text": "4711",
"editable": true
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\nFehler beim Verbindungsaufbau\nZeitüberschreitung beim Verbindungsaufbau\nVerbindung hergestellt\nVerbindung getrennt\nSocket zu 192.168.0.30:55555 getrennt\nFehler beim Verbindungsaufbau\nZeitüberschreitung beim Verbindungsaufbau\n"
},
"thrown": []
},
{
"step": "client: connect to a name nobody knows",
"server": {
"button": "Anhalten",
"port": {
"text": "55555",
"editable": false
},
"log": "Annahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:57292 hergestellt\n>>Hallo Echo\n<<Hallo Echo\n>>\n<<\n>>Zeile 1\nZeile 2\n<<Zeile 1\nZeile 2\nAnnahme von Verbindungsanfragen beendet\nSocket zu 192.168.0.20:57292 geschlossen\nServer-Socket geschlossen\nAnnahme von Verbindungsanfragen gestartet\nVerbindung zu 192.168.0.20:55719 hergestellt\nSocket zu 192.168.0.20:55719 geschlossen\n"
},
"client": {
"connect": "Verbinden",
"sendEnabled": false,
"address": {
"text": "gibtsnicht",
"editable": true
},
"port": {
"text": "55555",
"editable": true
},
"message": "noch da?",
"output": "Verbindung hergestellt\n<<Hallo Echo\n>>Hallo Echo\n<<\n>>\n<<Zeile 1\nZeile 2\n>>Zeile 1\nZeile 2\nSocket zu 192.168.0.30:55555 getrennt\nFehler beim Verbindungsaufbau\nZeitüberschreitung beim Verbindungsaufbau\nVerbindung hergestellt\nVerbindung getrennt\nSocket zu 192.168.0.30:55555 getrennt\nFehler beim Verbindungsaufbau\nZeitüberschreitung beim Verbindungsaufbau\nFehler beim Verbindungsaufbau\nZiel konnte nicht bestimmt werden!\n"
},
"thrown": []
}
]
}
+251
View File
@@ -0,0 +1,251 @@
// The echo server's and the generic client's windows in the room, against
// Java's (echoWindow.java.json, EchoWindowRun): Java's steps played as the
// editor's panels play them — the start button with the port typed,
// "Verbinden" with the address and port typed (or "Trennen" while
// connected), "Senden" only while connected —, the buttons, fields and lines
// both windows show after each; and what Java's project keeps of them after
// a run (fixtures/after-run-echo.fls, SaveAfterRunRun).
//
// Named: a port that is no number is not handed to the room (Java's window
// throws and does nothing; the panel marks the field); a connection attempt
// lasts three SYNs (F2.3c), so the one to the stopped server is awaited
// before it starts again. The client's own port is the threads' draw, in
// Java as here: written as PORT.
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import * as Y from 'yjs';
import type { EchoServerLogEntry, SimpleClientLogEntry } from '@mdg-filius/core';
import { addDevice, connect, emptyProject, readProject, setHostAddress, setSoftware, writeProject } from '@mdg-filius/doc';
import { openProjectArchive } from '@mdg-filius/fls';
import { portOfText, type EchoServerView, type HostMessage, type SimpleClientView } from '@mdg-filius/protocol';
import { clientText, serverText } from '../../core/test/programLines.js';
import { SimulationRoom, TICK_MILLISECONDS } from '../src/index.js';
interface WindowField {
readonly text: string;
readonly editable: boolean;
}
interface JavaStep {
readonly step: string;
readonly server: { readonly button: string; readonly port: WindowField; readonly log: string };
readonly client: { readonly connect: string; readonly sendEnabled: boolean; readonly address: WindowField; readonly port: WindowField; readonly message: string; readonly output: string };
readonly thrown: readonly string[];
}
const here = dirname(fileURLToPath(import.meta.url));
const java = JSON.parse(readFileSync(join(here, 'echoWindow.java.json'), 'utf8')) as { readonly steps: readonly JavaStep[] };
const corpus = join(here, '../../../corpus/java-examples');
/** The client's own port, drawn by the threads. */
const ownPort = (text: string) => text.replace(/192\.168\.0\.20:\d+/g, '192.168.0.20:PORT');
function setUp() {
const doc = new Y.Doc();
writeProject(doc, emptyProject());
let counter = 0;
const newId = () => `port-${++counter}`;
addDevice(doc, { kind: 'pc', id: 'client', name: 'Client', x: 0, y: 0, newId }, 'me');
addDevice(doc, { kind: 'pc', id: 'server', name: 'Server', x: 200, y: 0, newId }, 'me');
addDevice(doc, { kind: 'networkSwitch', id: 'switch', name: 'Switch', x: 100, y: 100, newId }, 'me');
connect(doc, { node: 'client' }, { node: 'switch' }, 'cable-1', 'me');
connect(doc, { node: 'server' }, { node: 'switch' }, 'cable-2', 'me');
setHostAddress(doc, 'client', 'ipAddress', '192.168.0.20', 'me');
setHostAddress(doc, 'server', 'ipAddress', '192.168.0.30', 'me');
setSoftware(doc, 'server', { install: ['echoServer'], remove: [] }, 'de', 'me');
setSoftware(doc, 'client', { install: ['simpleClient'], remove: [] }, 'de', 'me');
let now = 0;
const room = new SimulationRoom(doc, { now: () => now, seed: () => 3 });
const views = new Map<string, unknown>();
const lines = new Map<string, unknown[]>();
const errors: string[] = [];
const id = room.connect((message: HostMessage) => {
if (message.kind === 'view') views.set(message.app, message.view);
if (message.kind === 'app') lines.set(message.app, [...(lines.get(message.app) ?? []).slice(0, message.from), ...message.lines]);
if (message.kind === 'error') errors.push(message.code);
});
const send = (fields: object) => room.receive(id, { v: 1, ...fields });
send({ kind: 'sub', topics: ['app:server/echoServer', 'app:client/simpleClient'] });
send({ kind: 'start' });
const run = (milliseconds: number) => {
for (let elapsed = 0; elapsed < milliseconds; elapsed += TICK_MILLISECONDS) {
now += TICK_MILLISECONDS;
room.tick();
}
};
const act = (node: string, app: string, action: string, args: object = {}) => send({ kind: 'input', node, app, action, args, reqId: action });
return { doc, room, views, lines, errors, run, act, send };
}
describe("the echo server's and the generic client's windows, against Java's", () => {
it("play Java's steps as the panels play them", () => {
const { views, lines, run, act, errors } = setUp();
run(TICK_MILLISECONDS);
// What the panels hold: the fields as typed, the message.
let serverPortText = '';
let clientAddressText = '';
let clientPortText = '';
let message = '';
const server = () => views.get('echoServer') as EchoServerView;
const client = () => views.get('simpleClient') as SimpleClientView;
const serverLog = () => ((lines.get('echoServer') ?? []) as EchoServerLogEntry[]).map((line) => `${serverText(line)}\n`).join('');
const clientOutput = () => ((lines.get('simpleClient') ?? []) as SimpleClientLogEntry[]).map((line) => `${clientText(line)}\n`).join('');
// The fields follow the program when it changes (Java's aktualisieren sets them from it).
let seenServer = server();
let seenClient = client();
const follow = () => {
if (server() !== seenServer) serverPortText = String(server().port);
if (client() !== seenClient) {
clientAddressText = client().address;
clientPortText = String(client().port);
}
seenServer = server();
seenClient = client();
};
serverPortText = String(server().port);
clientAddressText = client().address;
clientPortText = String(client().port);
const shown = () => {
follow();
return {
server: { button: server().active ? 'Anhalten' : 'Starten', port: { text: serverPortText, editable: !server().active }, log: ownPort(serverLog()) },
client: {
connect: client().connected ? 'Trennen' : 'Verbinden',
sendEnabled: client().connected,
address: { text: clientAddressText, editable: !client().connected },
port: { text: clientPortText, editable: !client().connected },
message,
output: clientOutput(),
},
};
};
// The failed attempt, awaited (see below), is in the window one step before Java's shows it.
const timedOut = `${clientText({ kind: 'connectFailed', failure: 'timedOut' })}\n`;
const expected = (step: JavaStep) => ({
server: { ...step.server, log: ownPort(step.server.log) },
client: step.step === 'server: start again' ? { ...step.client, output: step.client.output + timedOut } : step.client,
});
// The panels' buttons.
const startStop = () => {
if (server().active) act('server', 'echoServer', 'setActive', { active: false });
else if (portOfText(serverPortText) !== undefined) act('server', 'echoServer', 'setActive', { active: true, port: portOfText(serverPortText) });
};
const connectButton = () => {
if (client().connected) act('client', 'simpleClient', 'disconnect');
else if (portOfText(clientPortText) !== undefined) act('client', 'simpleClient', 'connect', { address: clientAddressText, port: portOfText(clientPortText) });
};
const sendButton = () => {
if (!client().connected) return;
act('client', 'simpleClient', 'send', { text: message });
message = '';
};
const plays: Record<string, () => void> = {
'server: start with port abc': () => ((serverPortText = 'abc'), startStop(), run(500)),
'server: start with port 55555': () => ((serverPortText = '55555'), startStop(), run(1000)),
'client: connect with port x': () => ((clientAddressText = '192.168.0.30'), (clientPortText = 'x'), connectButton(), run(500)),
'client: connect to 192.168.0.30:55555': () => ((clientAddressText = '192.168.0.30'), (clientPortText = '55555'), connectButton(), run(6000)),
'client: send Hallo Echo': () => ((message = 'Hallo Echo'), sendButton(), run(4000)),
'client: send nothing': () => ((message = ''), sendButton(), run(4000)),
'client: send two lines': () => ((message = 'Zeile 1\nZeile 2'), sendButton(), run(4000)),
'server: stop while connected': () => (startStop(), run(4000)),
'client: send after the server stopped': () => ((message = 'noch da?'), sendButton(), run(8000)),
'client: Trennen': () => (connectButton(), run(3000)),
'server: start again': () => (startStop(), run(1000)),
'client: connect again': () => (connectButton(), run(6000)),
'client: Trennen again': () => (connectButton(), run(3000)),
// Three SYNs (F2.3c) last longer than Java's one: until the attempt failed.
'client: connect to a port nobody listens on': () => {
clientPortText = '4711';
connectButton();
for (let waited = 0; !clientOutput().endsWith(timedOut) && waited < 60_000; waited += 1000) run(1000);
},
'client: connect to a name nobody knows': () => ((clientAddressText = 'gibtsnicht'), (clientPortText = '55555'), connectButton(), run(12000)),
};
for (const step of java.steps) {
if (step.step !== 'open') plays[step.step]!();
expect(shown(), step.step).toEqual(expected(step));
// The press on "Verbinden" with the server stopped: Java's one SYN times out before the server starts again,
// the core's three (named in F2.3c) only later — awaited here, so that the server starts after it as in Java.
if (step.step === 'client: Trennen') {
for (let waited = 0; !clientOutput().endsWith(timedOut) && waited < 60_000; waited += 1000) run(1000);
}
// Java's window threw for a port that is no number; the panel hands nothing over.
expect(step.thrown.length > 0, step.step).toBe(step.step.includes('port abc') || step.step.includes('port x'));
}
expect(errors).toEqual([]);
});
it("leave in the project what Java's leaves after a run: the server's port and that it runs, the client's server", () => {
let ids = 0;
const project = openProjectArchive(new Uint8Array(readFileSync(join(corpus, 'email_komplett.fls'))), () => `00000000-0000-0000-0000-${(++ids).toString(16).padStart(12, '0')}`);
const doc = new Y.Doc();
writeProject(doc, project);
const id = (name: string) => readProject(doc).graph.nodes.find((node) => node.displayName === name)!.id;
setSoftware(doc, id('Rechner 2'), { install: ['echoServer'], remove: [] }, 'de', 'me');
setSoftware(doc, id('Rechner 1'), { install: ['simpleClient'], remove: [] }, 'de', 'me');
let now = 0;
const room = new SimulationRoom(doc, { now: () => now, seed: () => 5 });
const client = room.connect(() => {});
const act = (node: string, app: string, action: string, args: object = {}) => room.receive(client, { v: 1, kind: 'input', node: id(node), app, action, args, reqId: action });
const run = (milliseconds: number) => {
for (let elapsed = 0; elapsed < milliseconds; elapsed += TICK_MILLISECONDS) {
now += TICK_MILLISECONDS;
room.tick();
}
};
room.receive(client, { v: 1, kind: 'start' });
run(1000);
act('Rechner 2', 'echoServer', 'setActive', { active: true, port: 7777 });
run(1000);
const address = readProject(doc).deviceConfigurations.get(id('Rechner 2'))!.ipAddress;
act('Rechner 1', 'simpleClient', 'connect', { address, port: 7777 });
run(6000);
act('Rechner 1', 'simpleClient', 'send', { text: 'Hallo' });
run(4000);
room.receive(client, { v: 1, kind: 'stop' });
let javaIds = 0;
const saved = openProjectArchive(new Uint8Array(readFileSync(join(here, 'fixtures/after-run-echo.fls'))), () => `00000000-0000-0000-0000-${(++javaIds).toString(16).padStart(12, '0')}`);
const ours = readProject(doc);
for (const name of ['Rechner 1', 'Rechner 2']) {
const of = (each: typeof ours) => {
const node = each.graph.nodes.find((candidate) => candidate.displayName === name)!.id;
return { echoServer: each.echoServerConfigurations.get(node), simpleClient: each.simpleClientConfigurations.get(node), active: [...(each.activePrograms.get(node) ?? [])].sort() };
};
expect(of(ours), name).toEqual(of(saved));
}
});
it('refuses what the panels never hand over: a port that is no port, an action the program has not', () => {
const { run, act, errors, views } = setUp();
run(TICK_MILLISECONDS);
act('server', 'echoServer', 'setActive', { active: true, port: 70_000 });
act('server', 'echoServer', 'setActive', { active: true, port: 1.5 });
act('server', 'echoServer', 'setActive', { active: 'yes' });
act('server', 'echoServer', 'setActive', { active: 0 });
act('server', 'echoServer', 'restart');
act('client', 'simpleClient', 'connect', { address: '192.168.0.30', port: -1 });
act('client', 'simpleClient', 'connect', { address: 5, port: 80 });
act('client', 'simpleClient', 'send', {});
act('client', 'simpleClient', 'reset');
act('client', 'echoServer', 'setActive', { active: false });
act('server', 'simpleClient', 'disconnect');
expect(errors).toEqual(['badArgs', 'badArgs', 'badArgs', 'badArgs', 'unknownAction', 'badArgs', 'badArgs', 'badArgs', 'unknownAction', 'unknownApp', 'unknownApp']);
expect([portOfText('0'), portOfText('65535'), portOfText('000080'), portOfText('65536'), portOfText('+5'), portOfText(' 5'), portOfText('123456')]).toEqual([
0,
65_535,
80,
undefined,
undefined,
undefined,
undefined,
]);
// What is handed over is in the view: the port.
act('server', 'echoServer', 'setActive', { active: true, port: 7777 });
run(TICK_MILLISECONDS);
expect(views.get('echoServer')).toEqual({ active: true, port: 7777 });
});
});
Binary file not shown.
+1 -1
View File
@@ -458,7 +458,7 @@ describe("a simulation room's browser and web server windows", () => {
[server, 'webServer', 'setVirtualHost', { row: 0, column: 1, value: 2 }, 'badArgs'],
[server, 'webServer', 'restart', {}, 'unknownAction'],
[notebook, 'webServer', 'setActive', { active: true }, 'unknownApp'],
[server, 'echoServer', 'setActive', { active: true }, 'unknownAction'],
[server, 'gnutella', 'join', {}, 'unknownAction'],
];
for (const [target, app, action, args, code] of cases) {
send({ kind: 'input', node: target, app, action, args, reqId: `${app}.${action}.${JSON.stringify(args)}` });