Files
Web_FreeCAD_Bitbybit/scripts/run-freecad-property-direction-failure.mjs
wangdequan d11566403d
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled
feat: close ordered pairs and property codec batches
2026-08-17 04:58:46 -04:00

99 lines
5.7 KiB
JavaScript

import { existsSync, mkdtempSync, rmSync } from 'node:fs'
import { readFile, writeFile } from 'node:fs/promises'
import { spawnSync } from 'node:child_process'
import { join, resolve } from 'node:path'
import { tmpdir } from 'node:os'
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-direction-failure.py')
const outputPath = resolve(root, 'config/freecad-property-direction-failure.json')
if (!existsSync(executable)) throw new Error(`FreeCAD PropertyDirection executable is missing: ${executable}`)
const baseEnv = {
...process.env,
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 runProbe = ({ mode = 'main', target = outputPath, timeout = 120_000 } = {}) => spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], {
cwd: root,
encoding: 'utf8',
timeout,
maxBuffer: 32 * 1024 * 1024,
env: {
...baseEnv,
FREECAD_PROPERTY_DIRECTION_FAILURE_OUTPUT: target,
FREECAD_PROPERTY_DIRECTION_FAILURE_MODE: mode,
},
})
const execution = runProbe({ timeout: Number(process.env.FREECAD_PROPERTY_DIRECTION_FAILURE_TIMEOUT_MS || 120_000) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_DIRECTION_FAILURE_RESULT=')) throw new Error(`FreeCAD PropertyDirection failure probe failed with status ${execution.status}: ${execution.error?.message || 'process failed'}\n${output.trim()}`)
const report = JSON.parse(await readFile(outputPath, 'utf8'))
const isolatedRoot = mkdtempSync(join(tmpdir(), 'freecad-property-direction-'))
try {
report.nonFiniteConsumerBoundaries = []
const isolatedTimeout = Number(process.env.FREECAD_PROPERTY_DIRECTION_NON_FINITE_TIMEOUT_MS || 5_000)
for (const hostId of ['Mirroring', 'ProjectOnSurface']) {
for (const valueId of ['nan', 'positive-infinity', 'negative-infinity']) {
const caseId = `${hostId}-${valueId}`
const attempts = []
for (let attempt = 1; attempt <= 2; attempt += 1) {
const target = join(isolatedRoot, `${caseId}-${attempt}.json`)
const startedAt = Date.now()
const isolated = runProbe({ mode: `non-finite:${hostId}:${valueId}`, target, timeout: isolatedTimeout })
const isolatedOutput = `${isolated.stdout || ''}\n${isolated.stderr || ''}`.trim()
const timedOut = isolated.error?.code === 'ETIMEDOUT'
let completedCase = null
if (!timedOut && isolated.status === 0 && isolatedOutput.includes('FREECAD_PROPERTY_DIRECTION_FAILURE_ISOLATED_RESULT=') && existsSync(target)) {
completedCase = JSON.parse(await readFile(target, 'utf8')).case
}
attempts.push({
attempt,
timeoutMs: isolatedTimeout,
elapsedMs: Date.now() - startedAt,
timedOut,
exitStatus: isolated.status,
signal: isolated.signal,
errorCode: isolated.error?.code || null,
progressMarkerObserved: isolatedOutput.includes(`FREECAD_PROPERTY_DIRECTION_PROGRESS=${caseId}`),
completionMarkerObserved: isolatedOutput.includes('FREECAD_PROPERTY_DIRECTION_FAILURE_ISOLATED_RESULT='),
diagnostic: isolatedOutput.split('\n').filter((line) => line && !line.startsWith('FreeCAD 0.')).slice(-8),
completedCase,
outcome: timedOut ? 'timeout' : completedCase ? 'completed' : 'process-exited-without-completion',
})
}
const outcomes = [...new Set(attempts.map(({ outcome }) => outcome))]
const stable = outcomes.length === 1 && attempts.every(({ progressMarkerObserved }) => progressMarkerObserved)
report.nonFiniteConsumerBoundaries.push({
id: caseId,
mode: `non-finite:${hostId}:${valueId}`,
attemptCount: attempts.length,
attempts,
stable,
classification: !stable
? 'native-nondeterministic'
: outcomes[0] === 'timeout'
? 'native-synchronous-consumer-timeout'
: outcomes[0] === 'process-exited-without-completion'
? 'native-process-exit-without-probe-completion'
: 'native-consumer-returned',
})
}
}
report.status = report.nonFiniteConsumerBoundaries.every(({ stable, classification }) => stable && classification !== 'native-consumer-returned') ? 'pass' : 'fail'
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`)
} finally {
rmSync(isolatedRoot, { recursive: true, force: true })
}
console.log(JSON.stringify({
status: report.status,
output: 'config/freecad-property-direction-failure.json',
acceptedCoercions: report.setterBoundaries.acceptedCoercions.map(({ id, accepted, after }) => ({ id, accepted, after })),
setterFailures: report.setterBoundaries.failures.map(({ id, exception, valuePreserved }) => ({ id, exception, valuePreserved })),
consumerFailures: report.consumerFailures.map(({ id, setterException, recomputeResult, afterRecompute }) => ({ id, setterException, recomputeResult, state: afterRecompute.state })),
nonFiniteConsumerBoundaries: report.nonFiniteConsumerBoundaries.map(({ id, classification, attemptCount, stable }) => ({ id, classification, attemptCount, stable })),
dependencyFailures: report.dependencyFailures.map(({ id, recomputeResult, afterRecompute }) => ({ id, recomputeResult, state: afterRecompute.state })),
transactionRestored: report.transaction.restored,
}, null, 2))