feat: close ordered pairs and property codec batches
This commit is contained in:
96
scripts/run-freecad-property-acceleration-roundtrip.ts
Normal file
96
scripts/run-freecad-property-acceleration-roundtrip.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { resolve } from 'node:path'
|
||||
import { unzipSync } from 'fflate'
|
||||
import { createWebCadFacade } from '../src/facade/mockFacade'
|
||||
|
||||
const root = resolve(new URL('..', import.meta.url).pathname)
|
||||
const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd')
|
||||
const sysroot = resolve(root, '.cache/freecad/sysroot')
|
||||
const scriptPath = resolve(root, 'scripts/freecad-property-acceleration-roundtrip.py')
|
||||
const outputDirectory = resolve(root, '.cache/freecad/property-acceleration-roundtrip')
|
||||
const nativePath = resolve(outputDirectory, 'native-initial.FCStd')
|
||||
const webPath = resolve(outputDirectory, 'web-edited.FCStd')
|
||||
const resavedPath = resolve(outputDirectory, 'native-resaved.FCStd')
|
||||
const reportPath = resolve(root, 'config/freecad-property-acceleration-roundtrip.json')
|
||||
if (!existsSync(executable)) throw new Error(`FreeCAD PropertyAcceleration roundtrip executable is missing: ${executable}`)
|
||||
await mkdir(outputDirectory, { recursive: true })
|
||||
|
||||
const runNative = (mode: 'create' | 'verify', path: string, resaved = '') => {
|
||||
const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
timeout: 120_000,
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
env: {
|
||||
...process.env,
|
||||
FREECAD_PROPERTY_ACCELERATION_ROUNDTRIP_MODE: mode,
|
||||
FREECAD_PROPERTY_ACCELERATION_ROUNDTRIP_PATH: path,
|
||||
FREECAD_PROPERTY_ACCELERATION_ROUNDTRIP_RESAVED_PATH: resaved,
|
||||
PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
|
||||
LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
|
||||
},
|
||||
})
|
||||
const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
|
||||
const marker = 'FREECAD_PROPERTY_ACCELERATION_ROUNDTRIP_RESULT='
|
||||
const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker))
|
||||
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PropertyAcceleration ${mode} probe failed with status ${execution.status}: ${execution.error?.message || output.trim()}`)
|
||||
return JSON.parse(line.slice(line.indexOf(marker) + marker.length))
|
||||
}
|
||||
|
||||
const native = runNative('create', nativePath)
|
||||
const nativeBytes = new Uint8Array(await readFile(nativePath))
|
||||
const facade = createWebCadFacade({ runtimeMode: 'mock' })
|
||||
const inspectedBefore = facade.project.fcstd.inspect(nativeBytes)
|
||||
const editedBytes = facade.project.fcstd.rewriteNumeric(nativeBytes, {
|
||||
objectName: 'AccelerationProbe',
|
||||
propertyName: 'Acceleration',
|
||||
typeId: 'App::PropertyAcceleration',
|
||||
value: 500,
|
||||
expectedValue: 1000,
|
||||
})
|
||||
await writeFile(webPath, editedBytes)
|
||||
const inspectedAfter = facade.project.fcstd.inspect(editedBytes)
|
||||
const initialFiles = unzipSync(nativeBytes)
|
||||
const editedFiles = unzipSync(editedBytes)
|
||||
const opaquePaths = Object.keys(initialFiles).filter((path) => path.toLowerCase() !== 'document.xml')
|
||||
const opaqueEntriesPreserved = opaquePaths.every((path) => Buffer.from(initialFiles[path]).equals(Buffer.from(editedFiles[path] ?? new Uint8Array())))
|
||||
const normalizeInspection = (inspection: typeof inspectedBefore) => inspection.objects.map((object) => ({ ...object, properties: object.properties.map((property) => property.name === 'Acceleration' ? { ...property, value: '<edited>' } : property) }))
|
||||
const semanticObjectsPreserved = JSON.stringify(normalizeInspection(inspectedBefore)) === JSON.stringify(normalizeInspection(inspectedAfter))
|
||||
const nativeAfter = runNative('verify', webPath, resavedPath)
|
||||
const resavedBytes = new Uint8Array(await readFile(resavedPath))
|
||||
const nativeOutputMatches = [nativeAfter.result.reopened, nativeAfter.result.resaved].every((snapshot) => snapshot.object.value.numeric === 500 && snapshot.object.trajectory.waypoints.every((waypoint: { acceleration: number }) => waypoint.acceleration === 500))
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
status: 'pass',
|
||||
baselineId: 'freecad-1.1.1-property-acceleration-roundtrip',
|
||||
freecadVersion: native.freecadVersion,
|
||||
gitCommit: native.gitCommit,
|
||||
archives: {
|
||||
nativeInitial: { path: '.cache/freecad/property-acceleration-roundtrip/native-initial.FCStd', bytes: nativeBytes.byteLength, sha256: createHash('sha256').update(nativeBytes).digest('hex') },
|
||||
webEdited: { path: '.cache/freecad/property-acceleration-roundtrip/web-edited.FCStd', bytes: editedBytes.byteLength, sha256: createHash('sha256').update(editedBytes).digest('hex') },
|
||||
nativeResaved: { path: '.cache/freecad/property-acceleration-roundtrip/native-resaved.FCStd', bytes: resavedBytes.byteLength, sha256: createHash('sha256').update(resavedBytes).digest('hex') },
|
||||
},
|
||||
nativeInitial: native.result,
|
||||
web: {
|
||||
before: inspectedBefore.objects.find((object) => object.name === 'AccelerationProbe')?.properties.find((property) => property.name === 'Acceleration'),
|
||||
after: inspectedAfter.objects.find((object) => object.name === 'AccelerationProbe')?.properties.find((property) => property.name === 'Acceleration'),
|
||||
objectSetPreserved: inspectedBefore.objects.map((object) => `${object.name}:${object.typeId}`).join('|') === inspectedAfter.objects.map((object) => `${object.name}:${object.typeId}`).join('|'),
|
||||
semanticObjectsPreserved,
|
||||
opaquePathsPreserved: opaquePaths.length,
|
||||
opaqueEntriesPreserved,
|
||||
},
|
||||
nativeAfter: nativeAfter.result,
|
||||
classification: {
|
||||
webValue: inspectedAfter.objects.find((object) => object.name === 'AccelerationProbe')?.properties.find((property) => property.name === 'Acceleration')?.value,
|
||||
reopenedValue: nativeAfter.result.reopened.object.value.numeric,
|
||||
resavedValue: nativeAfter.result.resaved.object.value.numeric,
|
||||
nativeOutputMatches,
|
||||
unknownSemanticDrift: !(opaqueEntriesPreserved && semanticObjectsPreserved && nativeOutputMatches),
|
||||
zeroUnknownDrift: opaqueEntriesPreserved && semanticObjectsPreserved && nativeOutputMatches,
|
||||
},
|
||||
}
|
||||
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`)
|
||||
console.log(JSON.stringify({ status: report.status, output: 'config/freecad-property-acceleration-roundtrip.json', web: report.classification, opaqueEntriesPreserved: report.web.opaqueEntriesPreserved }, null, 2))
|
||||
Reference in New Issue
Block a user