test(fls): the Java oracle opens files as FILIUS does (M8)
FlsOracle now loads through SzenarioVerwaltung.laden, FILIUS's full loading path: the archive unpacked, the project's own applications copied where FILIUS's class loader finds them, then the configuration. --network keeps the configuration-only load (netzwerkLaden). On the corpus this changes two files: anwendung_komplett.fls and multicast_server.fls then have their own applications (Accumulator, Broadcast) installed. Java's views of the written and the exported files are byte-equal to before: Java-FILIUS 2.14 opens our exports, with the classes they carry, as it opens a file of its own, and sees what it sees in the original. Until own applications on hosts are written (B2), the reader, writer and export tests leave them out (test/ownApplications.ts); a test checks that only those two files are touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -44,7 +44,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand
|
||||
| `src/dnsHosts.ts` | Java `DNSServer.leseRecordListe`, `ResourceRecord`, `Dateisystem.holeDatei`; Swift `State/TopologyDNSResolver.swift` (`TopologyDNSHostsFileCodec`) als Vergleich | neu nach Java: Felder nur an einzelnen Leerzeichen, keine Kommentare, ungültige TTL → 3600, Doppelte bleiben; ins Modell kommt nur, was Java beantwortet (Typ A/MX/NS, Name nicht leer); Javas Fehler nicht übernommen: Label mit Ziffer vorn erlaubt, kein Backslash, letztes Label nicht rein numerisch (RFC 1123/3696), Typ in jeder Schreibung (RFC 4343), negative TTL ungültig (RFC 2181); an 76 Fällen mit Java und Swift gemessen |
|
||||
| `src/topology.ts` | Java `SzenarioVerwaltung.netzwerkLaden`, Konstruktoren und Setter der Geräte; Swift `Persistence/TopologyProjectStore.swift` (Parser) als Vergleich | neu nach Java statt Übertragung: Geräteart aus der Klasse statt aus der Beschriftung, Geräte ohne Koordinaten bei 0,0 statt verworfen, Werte durch Javas Setter, die Zeichen-Enden eines Kabels, die Funk-Eigenschaft einer Netzwerkkarte (`wireless`, bis 30.09.2026 übersehen) |
|
||||
| `test/javabeans.test.ts` | — | neu: je XMLDecoder-Regel ein Test; alle 23 Korpus-Dateien, Klasse und Position jedes Geräts wie Java |
|
||||
| `test/topology.test.ts` | — | neu: alle 23 Korpus-Dateien Feld für Feld wie Java (Geräte, Schnittstellen, Kabel, Doku-Elemente), dazu die Regeln, die der Korpus nicht berührt |
|
||||
| `test/topology.test.ts` | — | neu: alle 23 Korpus-Dateien Feld für Feld wie Java (Geräte, Schnittstellen, Kabel, Doku-Elemente), dazu die Regeln, die der Korpus nicht berührt; Java lädt seit M8 über den vollständigen Ladeweg, eigene Anwendungen (`test/ownApplications.ts`) sind bis B2 ausgenommen |
|
||||
| `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java |
|
||||
| `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) |
|
||||
| `src/javaBeansWriter.ts` | — | neu: Schreiber für das `XMLDecoder`-Format, Gegenstück zu `javabeans.ts`; ausdrücklich typisierte Eingabe, geteilte Objekte über `id`/`idref`, was XML nicht fassen kann (und CR) als `<char code>`; von Java selbst geprüft (`BeansDump`) |
|
||||
|
||||
+14
-1
@@ -8021,7 +8021,14 @@
|
||||
"directory": "root",
|
||||
"children": []
|
||||
},
|
||||
"applications": [],
|
||||
"applications": [
|
||||
{
|
||||
"key": "filius.software.clientserver.Accumulator",
|
||||
"class": "filius.software.clientserver.Accumulator",
|
||||
"aktiv": true,
|
||||
"port": 55555
|
||||
}
|
||||
],
|
||||
"dhcpClient": false,
|
||||
"dhcpServer": {
|
||||
"aktiv": false,
|
||||
@@ -13239,6 +13246,12 @@
|
||||
"children": []
|
||||
},
|
||||
"applications": [
|
||||
{
|
||||
"key": "filius.software.clientserver.Broadcast",
|
||||
"class": "filius.software.clientserver.Broadcast",
|
||||
"aktiv": true,
|
||||
"port": 55555
|
||||
},
|
||||
{
|
||||
"key": "filius.software.clientserver.ServerBaustein",
|
||||
"class": "filius.software.clientserver.ServerBaustein",
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
type ProjectToExport,
|
||||
} from '../src/index.js';
|
||||
import { writeTopologyXml } from '../src/topologyWriter.js';
|
||||
import { withoutOwnApplications } from './ownApplications.js';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const corpusRoot = join(here, '../../../corpus');
|
||||
@@ -349,7 +350,8 @@ describe('the editor model exported, as Java loads it', () => {
|
||||
return nic.mac === derived ? derived : undefined;
|
||||
}),
|
||||
);
|
||||
const source = original[name]!;
|
||||
// The model does not install own applications yet (B2).
|
||||
const source = withoutOwnApplications(original[name]!);
|
||||
const nodeIds = project.graph.nodes.map((node) => node.id);
|
||||
const pairs = nodeIds.map((id) => project.remoteLinkConfigurations.get(id)?.pairIdentifier);
|
||||
// The account files of each host the export writes anew, by path, as the file has them.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// A project's own applications (classes under projekt/anwendungen): Java
|
||||
// installs them when it loads the whole archive (tools/java-oracle, laden);
|
||||
// this package reads, writes and exports them in a later step (B2). Until
|
||||
// then the tests compare Java's views without them.
|
||||
|
||||
import { FILIUS_APPLICATION_CLASSES } from '../src/index.js';
|
||||
|
||||
/** The corpus files with an own application installed on a host. */
|
||||
export const OWN_APPLICATION_FILES = ['java-examples/anwendung_komplett.fls', 'java-examples/multicast_server.fls'];
|
||||
|
||||
type View = { nodes?: { system: Record<string, unknown> }[] };
|
||||
|
||||
/** Java's view without the applications FILIUS does not ship. */
|
||||
export function withoutOwnApplications<T>(view: T): T {
|
||||
const { nodes } = view as View;
|
||||
if (nodes === undefined) return view;
|
||||
return {
|
||||
...view,
|
||||
nodes: nodes.map((node) =>
|
||||
node.system.applications === undefined
|
||||
? node
|
||||
: {
|
||||
...node,
|
||||
system: {
|
||||
...node.system,
|
||||
applications: (node.system.applications as { class: string }[]).filter((application) =>
|
||||
(FILIUS_APPLICATION_CLASSES as readonly string[]).includes(application.class),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** Whether Java's view has an own application installed. */
|
||||
export function hasOwnApplications(view: unknown): boolean {
|
||||
return JSON.stringify(withoutOwnApplications(view)) !== JSON.stringify(view);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type JavaEmailAccount,
|
||||
} from '../src/index.js';
|
||||
import { EXPECTED_WARNINGS } from './expectedWarnings.js';
|
||||
import { hasOwnApplications, OWN_APPLICATION_FILES, withoutOwnApplications } from './ownApplications.js';
|
||||
import { javaEmail, javaEmailAccount } from './javaEmailNames.js';
|
||||
|
||||
const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus');
|
||||
@@ -250,11 +251,18 @@ describe('corpus: the topology as Java loads it', () => {
|
||||
const topology = readTopology(values);
|
||||
const expected = javaView[name] as { nodes: { bounds: number[] | null }[] };
|
||||
|
||||
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected, topology));
|
||||
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(
|
||||
withoutTemplateFiles(withoutOwnApplications(expected), topology),
|
||||
);
|
||||
expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||
});
|
||||
}
|
||||
|
||||
it('leaves out own applications only where Java installs some', () => {
|
||||
const files = flsFiles(corpusRoot).map((file) => relative(corpusRoot, file));
|
||||
expect(files.filter((name) => hasOwnApplications(javaView[name]))).toEqual(OWN_APPLICATION_FILES);
|
||||
});
|
||||
|
||||
it("a router's constructor makes the same files in every corpus file", () => {
|
||||
for (const javaClass of ['Vermittlungsrechner', 'Gateway']) {
|
||||
const templates = [...(templateChildren.get(javaClass) ?? [])].map((text) => JSON.parse(text) as { directory?: string }[]);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import { decodeJavaBeans, readFlsArchive, readTopology, writeFlsArchive } from '../src/index.js';
|
||||
import type { JavaEmail } from '../src/javaEmail.js';
|
||||
import { writeTopologyXml, type TopologyToWrite } from '../src/topologyWriter.js';
|
||||
import { withoutOwnApplications } from './ownApplications.js';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const corpusRoot = join(here, '../../../corpus');
|
||||
@@ -103,7 +104,8 @@ describe('the topology written again, as Java loads it', () => {
|
||||
expect(java?.sha256, 'written.java.json is not about this output: UPDATE_WRITTEN=1, then npm run oracle:java').toBe(
|
||||
createHash('sha256').update(archive).digest('hex'),
|
||||
);
|
||||
expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(original[name]!) as JavaFile));
|
||||
// The written file has no archive entries: Java installs no own application (B2).
|
||||
expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(withoutOwnApplications(original[name]!)) as JavaFile));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,17 @@ Braucht JDK 17+, Maven und `xvfb-run`. Filius öffnet beim Laden sein
|
||||
Hauptfenster (`Information.reset` → `JMainFrame`), deshalb läuft es an einem
|
||||
virtuellen Bildschirm statt „headless".
|
||||
|
||||
* Gelesen wird über Filius' eigene, private Lademethode
|
||||
`SzenarioVerwaltung.netzwerkLaden` (per Reflection) — Javas Weg, kein Nachbau.
|
||||
* Gelesen wird so, wie Filius eine Datei öffnet: `SzenarioVerwaltung.laden`
|
||||
entpackt das Archiv, kopiert die eigenen Anwendungen
|
||||
(`projekt/anwendungen`) dorthin, wo Filius' Klassenlader sucht, und lädt
|
||||
dann die Konfiguration — Javas Weg, kein Nachbau (seit 30.09.2026, M8).
|
||||
`--network` lädt nur die Konfiguration über die private Methode
|
||||
`netzwerkLaden` (per Reflection), wie vorher. Der Unterschied am Korpus:
|
||||
In `anwendung_komplett.fls` und `multicast_server.fls` sind dann die
|
||||
eigenen Anwendungen `Accumulator` und `Broadcast` installiert. Filius'
|
||||
Klassenlader ist ein Singleton: Eine einmal geladene eigene Klasse bleibt
|
||||
für die übrigen Dateien desselben Laufs geladen. Am Korpus stört das
|
||||
nicht, denn keine Datei nutzt eine Klasse, die sie nicht mitbringt.
|
||||
* Stand: `gitlab.com/filius1/filius` @ `6640354` (2.14.0), wird beim ersten
|
||||
Lauf nach `.upstream/` geholt und gebaut.
|
||||
* **Gewürfeltes:** Was nicht in der Datei steht, würfelt Filius zum Teil
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
// Uses FILIUS's own private loader (SzenarioVerwaltung.netzwerkLaden) by
|
||||
// reflection, so the result is Java's view, not a re-implementation of it.
|
||||
//
|
||||
// Usage: java -Djava.awt.headless=true -cp filius.jar:lib/*:classes FlsOracle <corpus dir> <output json>
|
||||
// By default it loads as FILIUS opens a file (SzenarioVerwaltung.laden): the
|
||||
// archive unpacked, the project's own applications (projekt/anwendungen)
|
||||
// copied where FILIUS's class loader finds them, then the configuration.
|
||||
// --network loads the configuration alone (netzwerkLaden), as before M8.
|
||||
//
|
||||
// Usage: java -cp filius.jar:lib/*:classes FlsOracle <corpus dir> <output json> [--sha256] [--network]
|
||||
|
||||
import filius.gui.netzwerksicht.GUIDocuItem;
|
||||
import filius.gui.netzwerksicht.GUIKabelItem;
|
||||
@@ -63,7 +68,13 @@ import java.util.zip.ZipInputStream;
|
||||
|
||||
public class FlsOracle {
|
||||
|
||||
/** Load the configuration alone (netzwerkLaden) instead of the whole archive (laden). */
|
||||
static boolean networkOnly;
|
||||
static List<String> options;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
options = Arrays.asList(args).subList(2, args.length);
|
||||
networkOnly = options.contains("--network");
|
||||
Path root = Path.of(args[0]);
|
||||
List<Path> files;
|
||||
try (Stream<Path> walk = Files.walk(root)) {
|
||||
@@ -74,7 +85,7 @@ public class FlsOracle {
|
||||
String name = root.relativize(file).toString();
|
||||
try {
|
||||
Map<String, Object> view = read(file);
|
||||
if (args.length > 2 && args[2].equals("--sha256")) view.put("sha256", sha256(Files.readAllBytes(file)));
|
||||
if (options.contains("--sha256")) view.put("sha256", sha256(Files.readAllBytes(file)));
|
||||
result.put(name, view);
|
||||
} catch (Throwable error) {
|
||||
Throwable cause = error instanceof java.lang.reflect.InvocationTargetException && error.getCause() != null
|
||||
@@ -123,6 +134,13 @@ public class FlsOracle {
|
||||
}
|
||||
|
||||
static Map<String, Object> load(Path file) throws Exception {
|
||||
if (!networkOnly) {
|
||||
List<GUIKnotenItem> nodes = new ArrayList<>();
|
||||
List<GUIKabelItem> cables = new ArrayList<>();
|
||||
List<GUIDocuItem> docs = new ArrayList<>();
|
||||
boolean success = SzenarioVerwaltung.getInstance().laden(file.toString(), nodes, cables, docs);
|
||||
return describe(success, nodes, cables, docs);
|
||||
}
|
||||
Path xml = Files.createTempFile("konfiguration", ".xml");
|
||||
try {
|
||||
extractConfiguration(file, xml);
|
||||
|
||||
Reference in New Issue
Block a user