feat(fls): write the file systems of a .fls, loaded by Java as the original
topologyWriter.ts adds each node's file tree to the root its constructor makes, as XMLEncoder does (arbeitsVerzeichnis, files as new Datei(name, typ, inhalt)); a root of its own is set anew. The reader now reads the files of routers and home routers too: their constructor makes files from templates (the web interface), and the file adds to them (a web server's www.conf). systemSoftware.ts keeps what the file adds, and templateFiles says whether the constructor's files stay in front (false when the file sets a root of its own). The topology test finds our part at the end of Java's tree and the template in front, the same for every router of a class across the corpus. Checked by Java against Java: every corpus file, read and written again, loads with the same file systems as the original. The synthetic topology adds a nested tree with a renamed root, a nameless directory, a file without type, content with CR, CRLF and beyond-BMP characters, router files after the template and a home router with a root of its own. 9 mutation probes, each loaded by Java, were all caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,7 @@ function asJavaView(topology: FlsTopology, javaBounds: (number[] | null)[]) {
|
||||
}),
|
||||
...(node.system.modem === undefined ? {} : { modem: javaModem(node.system.modem) }),
|
||||
...(node.system.fileSystem === undefined ? {} : { fileSystem: javaFileTree(node.system.fileSystem) }),
|
||||
...('templateFiles' in node.system ? { templateFiles: undefined } : {}),
|
||||
...(node.system.applications === undefined ? {} : { applications: javaApplications(node.system.applications) }),
|
||||
...(node.system.firewall === undefined
|
||||
? {}
|
||||
@@ -202,19 +203,33 @@ function asJavaView(topology: FlsTopology, javaBounds: (number[] | null)[]) {
|
||||
};
|
||||
}
|
||||
|
||||
type JavaTree = { children: unknown[] };
|
||||
|
||||
/**
|
||||
* Java's view without the file systems of routers and home routers: their
|
||||
* files come from Java's constructor (templates of the firewall's web
|
||||
* interface), not from the .fls file, and the reader does not claim them.
|
||||
* The files a router's or home router's constructor makes, as Java loaded
|
||||
* each one: they come first in its root, what the file adds follows. Each
|
||||
* router's are cut off here and collected, and must be the same for every
|
||||
* router of a class across the corpus.
|
||||
*/
|
||||
function withoutTemplateFiles(view: unknown): unknown {
|
||||
const templateChildren = new Map<string, Set<string>>();
|
||||
|
||||
function withoutTemplateFiles(view: unknown, topology: FlsTopology): unknown {
|
||||
const { nodes, ...rest } = view as { nodes: { class: string; system: Record<string, unknown> }[] };
|
||||
return {
|
||||
...rest,
|
||||
nodes: nodes.map((node) => {
|
||||
if (node.class !== 'Vermittlungsrechner' && node.class !== 'Gateway') return node;
|
||||
const { fileSystem: _templateFiles, ...system } = node.system;
|
||||
return { ...node, system };
|
||||
nodes: nodes.map((node, index) => {
|
||||
const ours = topology.nodes[index]?.system;
|
||||
if ((node.class !== 'Vermittlungsrechner' && node.class !== 'Gateway') || ours?.templateFiles !== true) return node;
|
||||
const tree = node.system.fileSystem as JavaTree;
|
||||
const added = ours.fileSystem?.children.length ?? 0;
|
||||
const template = tree.children.slice(0, tree.children.length - added);
|
||||
const seen = templateChildren.get(node.class) ?? new Set<string>();
|
||||
seen.add(JSON.stringify(template));
|
||||
templateChildren.set(node.class, seen);
|
||||
return {
|
||||
...node,
|
||||
system: { ...node.system, fileSystem: { ...tree, children: tree.children.slice(tree.children.length - added) } },
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -227,10 +242,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));
|
||||
expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected, topology));
|
||||
expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []);
|
||||
});
|
||||
}
|
||||
|
||||
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 }[]);
|
||||
expect(templates, javaClass).toHaveLength(1);
|
||||
expect(templates[0]!.map((child) => child.directory), javaClass).toEqual(['webserver']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Rules the corpus does not exercise ─────────────────────────────────────
|
||||
@@ -433,6 +456,30 @@ describe('readTopology rules', () => {
|
||||
expect(warnings).toEqual(['node 2: file system: insert on a node is not read']);
|
||||
});
|
||||
|
||||
it("reads what a router's file adds to the constructor's files, or a root of its own", () => {
|
||||
const folder = (name: string) =>
|
||||
`<void method="add"><object class="javax.swing.tree.DefaultMutableTreeNode"><string>${name}</string></object></void>`;
|
||||
const { nodes } = topologyOf(`<object class="java.util.LinkedList">
|
||||
${nodeItem('<object class="filius.hardware.knoten.Vermittlungsrechner"/>')}
|
||||
${nodeItem(`<object class="filius.hardware.knoten.Gateway"><void property="systemSoftware"><void property="dateisystem">
|
||||
<void property="arbeitsVerzeichnis">${folder('www.conf')}</void>
|
||||
</void></void></object>`)}
|
||||
${nodeItem(`<object class="filius.hardware.knoten.Vermittlungsrechner"><void property="systemSoftware"><void property="dateisystem">
|
||||
<void property="arbeitsVerzeichnis"><object class="javax.swing.tree.DefaultMutableTreeNode"><string>root</string>${folder('own')}</object></void>
|
||||
</void></void></object>`)}
|
||||
${nodeItem(`<object class="filius.hardware.knoten.Gateway"><void property="systemSoftware">
|
||||
<void property="dateisystem"><object class="filius.software.system.Dateisystem"/></void>
|
||||
</void></object>`)}</object>`);
|
||||
const directory = (name: string, children: FlsFileTree[] = []): FlsFileTree => ({ kind: 'directory', name, children });
|
||||
|
||||
expect(nodes.map((node) => [node.system.fileSystem, node.system.templateFiles])).toEqual([
|
||||
[directory('root'), true],
|
||||
[directory('root', [directory('www.conf')]), true],
|
||||
[directory('root', [directory('own')]), false],
|
||||
[directory('root'), false],
|
||||
]);
|
||||
});
|
||||
|
||||
it("gives installed applications Java's measured defaults for what the file leaves out", () => {
|
||||
const put = (className: string, body = '') =>
|
||||
`<void method="put"><string>${className}</string><object class="${className}">${body}</object></void>`;
|
||||
|
||||
Reference in New Issue
Block a user