feat(core): the command line with its local commands after Java, measured against Java

The terminal splits a line as Java's window does and runs ls, cd, pwd,
mkdir, touch, cp, mv, rm, cat, test, help, ipconfig and route on the
node's files, which are the project's own. Its texts are generated from
the bundle Java loads. TerminalCases feeds Java's Terminal without a
network (16 cases, de and en); eight cases name Java bugs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 20:32:35 +00:00
co-authored by Claude Opus 5.5
parent 058675a148
commit 016f9461df
17 changed files with 14404 additions and 7 deletions
+4 -1
View File
@@ -7,7 +7,8 @@
// A DNS server runs only when it is switched on (Java: DNSServer.aktiv); an
// echo, web or e-mail server is there either way, switched on or off (it can be
// switched at run time). The programs read the nodes' files (a web server its
// pages).
// pages); the command line changes them, and as they are the project's own,
// a project saved after the run holds the change, as in Java.
//
// Back the other way: what the simulation changes that Java keeps in the
// project (its objects are the project's), so a project saved after it holds
@@ -38,6 +39,7 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot {
emailServers.set(nodeId, { domain: configuration.domain, accounts: configuration.accounts, isActive: project.activePrograms.get(nodeId)?.has('emailServer') === true });
}
const emailClients = new Map([...project.emailClientConfigurations].filter(([nodeId]) => project.installedPrograms.get(nodeId)?.has('emailClient') === true));
const terminals = new Set([...project.installedPrograms].filter(([, programs]) => programs.has('commandPrompt')).map(([nodeId]) => nodeId));
const webBrowsers = new Set([...project.installedPrograms].filter(([, programs]) => programs.has('webBrowser')).map(([nodeId]) => nodeId));
return topologySnapshot({
nodes: project.graph.nodes.map((node) => ({
@@ -63,6 +65,7 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot {
webBrowserNodeIds: webBrowsers,
emailServersByNodeId: emailServers,
emailClientsByNodeId: emailClients,
terminalNodeIds: terminals,
fileSystemsByNodeId: project.virtualFileSystems,
});
}
+16
View File
@@ -857,6 +857,22 @@ describe('importProject rules', () => {
expect(client('Rechner 3').account).toEqual(project.emailClientConfigurations.get(receiver)!.account);
});
it("keeps what the command line did to a node's files in the project saved after the run", () => {
const name = 'java-examples/Internet_Komplett_mit_eMail_Webserver_Intranet_Portforwarding_Firewall_DHCP_DE.fls';
const project = openProjectArchive(new Uint8Array(readFileSync(join(corpusRoot, name))), counter());
const notebook = project.graph.nodes.find((each) => each.displayName === 'Notebook' && project.deviceConfigurations.get(each.id)?.ipAddress === '10.10.20.11')!.id;
const snapshot = projectSnapshot(project);
expect(snapshot.terminalNodeIds.has(notebook)).toBe(true);
const engine = new NetworkRuntimeEngine(14);
engine.handle({ type: 'start', snapshot, seed: 14, initialTimeMilliseconds: 0 });
for (const line of ['mkdir Projekt', 'cd Projekt', 'touch plan.txt']) engine.terminal(notebook)!.enter(line);
engine.handle({ type: 'advance', toMilliseconds: 1_000 });
const reopened = openProjectArchive(saveProjectArchive(projectWithSimulationChanges(project, engine.state.topologySnapshot)).archive, counter());
const reopenedNotebook = reopened.graph.nodes.find((each) => each.displayName === 'Notebook' && reopened.deviceConfigurations.get(each.id)?.ipAddress === '10.10.20.11')!.id;
expect(reopened.virtualFileSystems.get(reopenedNotebook)!.entry('/Projekt/plan.txt').content).toEqual({ kind: 'text', text: '' });
});
it("keeps a node's IP forwarding where it differs from its kind's: a host that forwards, a router that does not", () => {
const system = (ipForwardingEnabled: boolean) => ({ manualRoutes: [], ipForwardingEnabled });
const project = importProject(