import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createHash } from 'node:crypto'; import { loadWasmProbe } from './wasm-probe-runtime.mjs'; // Run in a child process so a synchronous native stall has a hard deadline. const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const directory = process.argv[2]; const report = { status: 'failed', stages: [], calls: [], assertions: [] }; const started = performance.now(); let kernel; const persist = () => fs.writeFileSync(path.join(directory, 'independent-native.json'), JSON.stringify(report, null, 2)); const stage = name => { report.stages.push({ name, enteredMs: performance.now() - started }); persist(); }; const done = () => { report.stages.at(-1).completedMs = performance.now() - started; persist(); }; const call = (document, action, details = {}, reject = false) => { const args = { ...details, document, action, includeMesh: true }; stage(`${document}:${action}`); const response = JSON.parse(kernel.ccall('fc_web_dispatch_json', 'string', ['string'], [JSON.stringify({ cmd: 'partdesign.document', args })])); report.calls.push({ args, response }); done(); assert.equal(response.ok, !reject, JSON.stringify(response)); if (reject) { assert.ok(response.error); return response; } assert.equal(response.result.document, document); return response.result; }; const profile = width => ({ object: 'Sketch1', body: 'Body1', profileType: 'Rectangle', parameters: { Width: width, Height: 8 }, position: [0, 0, 0] }); const check = (snapshot, width) => { assert.equal(snapshot.objectCount, snapshot.objects.length); const body = snapshot.objects.find(object => object.name === 'Body1'); const sketch = snapshot.objects.find(object => object.name === 'Sketch1'); const pad = snapshot.objects.find(object => object.name === 'Pad1'); assert.equal(body.type, 'PartDesign::Body'); assert.equal(body.tip, 'Pad1'); assert.ok(body.group.includes('Sketch1') && body.group.includes('Pad1')); assert.equal(sketch.type, 'Sketcher::SketchObject'); assert.equal(sketch.body, 'Body1'); assert.equal(sketch.geometryCount, 4); assert.ok(sketch.constraintCount >= 8); assert.equal(sketch.dof, 0); assert.equal(sketch.fullyConstrained, true); assert.deepEqual(sketch.parameters, { Width: width, Height: 8 }); assert.equal(pad.type, 'PartDesign::Pad'); assert.equal(pad.body, 'Body1'); assert.equal(pad.profile, 'Sketch1'); assert.equal(pad.length, 5); assert.ok(Math.abs(pad.volume - width * 8 * 5) < 1e-7); assert.ok(pad.mesh.triangleCount > 0 && pad.mesh.positions.length > 0); }; try { stage('load-and-initialize-wasm'); ({ kernel } = await loadWasmProbe(path.join(root, 'web/public/kernel'), 'FreeCADWebPartDesignRuntimeProbe')); done(); const primary = 'CrossNative', other = 'CrossOther', restored = 'CrossRestored'; for (const [document, width] of [[primary, 12], [other, 7]]) { call(document, 'create'); call(document, 'createBody', { object: 'Body1' }); call(document, 'setSketch', profile(width)); check(call(document, 'setFeature', { object: 'Pad1', body: 'Body1', profile: 'Sketch1', featureType: 'Pad', length: 5, reversed: false }), width); } const unrelated = call(other, 'snapshot'); const baseline = call(primary, 'snapshot'); const edited = call(primary, 'setSketch', profile(14)); check(edited, 14); assert.equal(edited.undoCount, baseline.undoCount + 1); check(call(primary, 'undo'), 12); const undone = call(primary, 'snapshot'); assert.equal(undone.redoCount, 1); call(primary, 'setSketch', profile(-1), true); assert.deepEqual(call(primary, 'snapshot'), undone, 'Rejected native edit must preserve geometry AND pending redo'); check(call(primary, 'redo'), 14); assert.deepEqual(call(other, 'snapshot'), unrelated, 'Same-name objects in the second document remain untouched'); report.assertions.push('Constrained sketch and dependent Pad recompute; undo/redo and rejected edit preserve both documents'); const filename = '/tmp/CrossNative.FCStd'; call(primary, 'save', { path: filename }); const bytes = kernel.FS.readFile(filename); assert.ok(bytes.length > 1000); assert.deepEqual(Array.from(bytes.slice(0, 2)), [80, 75]); fs.writeFileSync(path.join(directory, 'independent-native.FCStd'), bytes); report.file = { byteLength: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') }; call(primary, 'close'); check(call(restored, 'open', { path: filename }), 14); check(call(restored, 'setSketch', profile(15)), 15); check(call(restored, 'undo'), 14); check(call(restored, 'redo'), 15); assert.deepEqual(call(other, 'snapshot'), unrelated); report.assertions.push('Actual FCStd restores Body/Tip/profile/constraints and permits fresh edit, undo and redo'); call(restored, 'close'); call(other, 'close'); report.status = 'passed'; } catch (error) { report.error = String(error.stack ?? error); process.exitCode = 1; } finally { stage('cleanup'); kernel?.PThread?.terminateAllThreads(); done(); }