31 lines
5.2 KiB
JavaScript
31 lines
5.2 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname)
|
|
const runtimePath = resolve(root, '.cache/freecad/reference-desktop.json')
|
|
const reportPath = resolve(root, 'config/freecad-property-file-inventory.json')
|
|
const sourcePaths = ['.cache/freecad/FreeCAD/src/App/PropertyFile.h', '.cache/freecad/FreeCAD/src/App/PropertyFile.cpp', '.cache/freecad/FreeCAD/src/Gui/propertyeditor/PropertyItem.h', '.cache/freecad/FreeCAD/src/Gui/propertyeditor/PropertyItem.cpp']
|
|
const fail = (message) => { throw new Error(`FreeCAD PropertyFile inventory check failed: ${message}`) }
|
|
const [runtimeContent, reportContent, ...sourceContents] = await Promise.all([readFile(runtimePath), readFile(reportPath), ...sourcePaths.map((path) => readFile(resolve(root, path)))])
|
|
const runtime = JSON.parse(runtimeContent)
|
|
const report = JSON.parse(reportContent)
|
|
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyFile' || report.classification !== 'opaque-fcstd-proxy' || report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('report boundary is invalid')
|
|
if (report.recordCount !== 16 || !Array.isArray(report.records) || report.records.length !== 16) fail('record count is not exactly sixteen')
|
|
if (report.storage?.inheritedFrom !== 'App::PropertyString' || report.storage.xmlElement !== 'String' || report.storage.externalResource !== true || report.storage.absoluteHostPathPolicy !== 'preserve-or-reject; never rewrite into browser project') fail('storage boundary is invalid')
|
|
if (report.provenance?.runtime?.path !== '.cache/freecad/reference-desktop.json' || report.provenance.runtime.bytes !== runtimeContent.length || report.provenance.runtime.sha256 !== createHash('sha256').update(runtimeContent).digest('hex')) fail('runtime provenance is stale')
|
|
for (const [index, path] of sourcePaths.entries()) if (report.provenance.sources?.[index]?.path !== path || report.provenance.sources[index].bytes !== sourceContents[index].length || report.provenance.sources[index].sha256 !== createHash('sha256').update(sourceContents[index]).digest('hex')) fail(`source provenance is stale for ${path}`)
|
|
const nativeMatches = []
|
|
for (const objectType of runtime.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) if (property.typeId === 'App::PropertyFile') nativeMatches.push({ objectTypeId: objectType.typeId, objectAvailable: objectType.available === true, probeStatus: objectType.probeStatus, propertyName: property.name, group: property.group, status: property.status, defaultRaw: property.default })
|
|
const normalize = (record) => ({ objectTypeId: record.objectTypeId, objectAvailable: record.objectAvailable, probeStatus: record.probeStatus, propertyName: record.propertyName, group: record.group, status: record.status, defaultRaw: record.defaultRaw })
|
|
if (nativeMatches.length !== 16 || JSON.stringify(nativeMatches.map(normalize).sort((a, b) => `${a.objectTypeId}.${a.propertyName}`.localeCompare(`${b.objectTypeId}.${b.propertyName}`))) !== JSON.stringify(report.records.map(normalize).sort((a, b) => `${a.objectTypeId}.${a.propertyName}`.localeCompare(`${b.objectTypeId}.${b.propertyName}`)))) fail('report does not match the locked runtime oracle')
|
|
for (const record of report.records) {
|
|
if (record.objectAvailable !== true || record.probeStatus !== 'available' || JSON.stringify(record.status) !== '[]' || record.editor !== 'Gui::PropertyEditor::PropertyFileItem' || record.valueModel?.kind !== 'path-string' || record.valueModel.representation !== 'App::PropertyString-derived UTF-8 path' || record.valueModel.externalResource !== true || record.inputs?.accepted?.join('|') !== 'string|{filename:string,filter?:string}' || record.inputs?.emptyStringAccepted !== true || record.dependencies?.length !== 0 || record.applicability?.requiredObjectTypeId !== record.objectTypeId) fail(`native contract changed for ${record.objectTypeId}.${record.propertyName}`)
|
|
}
|
|
const propertyHeader = sourceContents[0].toString('utf8')
|
|
const propertySource = sourceContents[1].toString('utf8')
|
|
const editorHeader = sourceContents[2].toString('utf8')
|
|
const editorSource = sourceContents[3].toString('utf8')
|
|
if (!propertyHeader.includes('class AppExport PropertyFile: public PropertyString') || !propertyHeader.includes('Gui::PropertyEditor::PropertyFileItem') || !propertySource.includes('TYPESYSTEM_SOURCE(App::PropertyFile, App::PropertyString)') || !propertySource.includes('PyDict_Check(value)') || !propertySource.includes('dict.hasKey("filter")') || !propertySource.includes('dict.hasKey("filename")') || !editorHeader.includes('class GuiExport PropertyFileItem: public PropertyItem') || !editorSource.includes('PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyFileItem)') || !editorSource.includes('Gui::FileChooser')) fail('locked PropertyFile setter/editor contract changed')
|
|
console.log(JSON.stringify({ status: 'freecad-property-file-inventory-pass', propertyType: report.propertyType, recordCount: report.recordCount, objectTypes: report.records.map(({ objectTypeId }) => objectTypeId), classification: report.classification }, null, 2))
|