import { isDeepStrictEqual } from 'node:util' import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const [report, inventory] = await Promise.all([ readFile(resolve(root, 'config/freecad-property-fileincluded-success.json'), 'utf8').then(JSON.parse), readFile(resolve(root, 'config/freecad-property-fileincluded-inventory.json'), 'utf8').then(JSON.parse), ]) const fail = (message) => { throw new Error(`FreeCAD PropertyFileIncluded success check failed: ${message}`) } if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-fileincluded-success' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyFileIncluded' || report.hostCaseCount !== 20 || report.hostCases?.length !== 20 || report.diagnostics?.exception !== null) fail('baseline or report boundary is invalid') const setter = report.setterVariants const phaseIds = ['StringPath', 'BytesPath', 'TupleRename', 'OpenIoFile', 'Dictionary', 'CollisionA', 'CollisionB'] const phaseBaseNames = ['payload.bin', 'payload-two.bin', 'renamed.dat', 'payload-two1.bin', 'payload1.bin', 'collision.bin', 'collision1.bin'] if (setter?.phases?.map(({ id }) => id).join('|') !== phaseIds.join('|') || setter.phases.map(({ baseName }) => baseName).join('|') !== phaseBaseNames.join('|') || setter.objectCount !== 8 || setter.recomputeResult !== true) fail('setter success phases changed') for (const phase of setter.phases) { if (phase.propertyTypeId !== 'App::PropertyFileIncluded' || !isDeepStrictEqual(phase.propertyStatus, []) || !isDeepStrictEqual(phase.editorMode, []) || phase.exists !== true || phase.bytes <= 0 || phase.sha256 !== phase.sourceSha256 || phase.writeBits !== 0 || phase.underTransientDir !== true || !isDeepStrictEqual(phase.objectState, ['Touched']) || phase.statusString !== 'Touched' || phase.shape?.applicable !== false) fail(`${phase.id} did not preserve native bytes, ownership, state or metadata`) } const empty = setter.emptyString if (empty?.preserved !== true || empty.before?.value !== empty.after?.value || empty.before?.sha256 !== empty.after?.sha256 || empty.before?.bytes !== 38 || empty.after?.writeBits !== 0 || empty.after?.underTransientDir !== true) fail('empty-string no-op semantics changed') const collision = setter.collision if (collision?.distinctPaths !== true || collision.distinctBytes !== true || collision.first?.baseName !== 'collision.bin' || collision.second?.baseName !== 'collision1.bin' || collision.first.sha256 === collision.second.sha256 || collision.first.writeBits !== 0 || collision.second.writeBits !== 0) fail('same-name resource collision semantics changed') const inventoryKeys = inventory.records.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`) const caseKeys = report.hostCases.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`) if (!isDeepStrictEqual(caseKeys, inventoryKeys)) fail('host cases do not cover the complete locked inventory in order') const expectedAssets = { bin: { bytes: 38, sha256: '537d3bf00d6bbc268e5dc4bd9f436ee937b8e481ad6aac5b7fcc6edb29bb3246' }, wrl: { bytes: 54, sha256: 'b06eb69af003e2a1cc0e70e8833473a5dff8f0a4487d65fb96a5e79cc6e1b179' }, png: { bytes: 68, sha256: '431ced6916a2a21a156e38701afe55bbd7f88969fbbfc56d7fe099d47f265460' }, csv: { bytes: 214, sha256: '75a789684c89a28de4dc9e8c40fe836e0cdbeeb2f58de251dd0734adb98bda3a' }, skf: { bytes: 42, sha256: 'b32d9e3958b275212f00ab37b40e8cfb8f8fca5be7209713233cd55158031739' }, pat: { bytes: 39, sha256: '2409d42fbc1592ac7e7aa6ebb7daf60cf4e1ed670c29da62deccc795e870ce98' }, svg: { bytes: 88, sha256: '35b293e12fead414a67d8b703bb8cd7523e2361b52140430c7017b2e4780f723' }, } for (const [index, entry] of report.hostCases.entries()) { const inventoryRecord = inventory.records[index] const expectedStatus = inventoryRecord.status.map(String) const expectedEditor = isDeepStrictEqual(inventoryRecord.status, ['ReadOnly']) ? ['ReadOnly'] : [] const expectedAsset = expectedAssets[entry.source?.kind] const expectedBaseName = `host-${String(index).padStart(2, '0')}.${entry.source?.kind}` if (!expectedAsset || entry.source.bytes !== expectedAsset.bytes || entry.source.sha256 !== expectedAsset.sha256 || entry.archiveName !== expectedBaseName) fail(`${caseKeys[index]} source fixture changed`) for (const [phaseName, snapshot] of [['afterSet', entry.afterSet], ['afterRecompute', entry.afterRecompute]]) { if (snapshot?.propertyTypeId !== 'App::PropertyFileIncluded' || !isDeepStrictEqual(snapshot.propertyStatus, expectedStatus) || !isDeepStrictEqual(snapshot.editorMode, expectedEditor) || snapshot.baseName !== expectedBaseName || snapshot.exists !== true || snapshot.bytes !== expectedAsset.bytes || snapshot.sha256 !== expectedAsset.sha256 || snapshot.writeBits !== 0 || snapshot.underTransientDir !== true) fail(`${caseKeys[index]} ${phaseName} resource or metadata changed`) } if (entry.objectSetStable !== true || !isDeepStrictEqual(entry.afterRecompute.objectState, ['Up-to-date']) || entry.afterRecompute.statusString !== 'Valid') fail(`${caseKeys[index]} object set or final state changed`) const isOutput = isDeepStrictEqual(expectedStatus, ['27']) if (isOutput ? (entry.recomputeResult !== false || !isDeepStrictEqual(entry.afterSet.objectState, ['Up-to-date']) || entry.afterSet.statusString !== 'Valid') : (entry.recomputeResult !== true || !isDeepStrictEqual(entry.afterSet.objectState, ['Touched']) || entry.afterSet.statusString !== 'Touched')) fail(`${caseKeys[index]} touch/recompute behavior changed`) const shapeExpected = entry.objectTypeId === 'Sketcher::SketchObjectSF' if (entry.afterSet.shape?.applicable !== shapeExpected || shapeExpected && (entry.afterSet.shape.isNull !== true || entry.afterRecompute.shape.isNull !== true || entry.afterRecompute.shape.edges !== 0)) fail(`${caseKeys[index]} Shape applicability changed`) } if (report.hostCases.filter(({ default: value }) => value.exists).length !== 12 || report.hostCases.filter(({ afterSet }) => afterSet.exists).length !== 20 || report.hostCases.filter(({ afterSet, source }) => afterSet.sha256 === source.sha256).length !== 20 || report.hostCases.filter(({ afterSet }) => isDeepStrictEqual(afterSet.propertyStatus, ['ReadOnly'])).length !== 8 || report.hostCases.filter(({ afterSet }) => isDeepStrictEqual(afterSet.propertyStatus, ['27'])).length !== 1) fail('host success partition changed') console.log(JSON.stringify({ status: 'freecad-property-fileincluded-success-pass', propertyType: report.propertyType, setterInputs: ['string', 'bytes', 'tuple', 'io-file', 'dictionary'], emptyStringNoOp: empty.preserved, collisionNames: [collision.first.baseName, collision.second.baseName], hostCases: report.hostCaseCount, byteExactCases: report.hostCases.filter(({ afterSet, source }) => afterSet.sha256 === source.sha256).length, readOnlyCases: 8, outputCases: 1, shapeApplicableCases: 1, finalValidCases: report.hostCases.filter(({ afterRecompute }) => afterRecompute.statusString === 'Valid').length }, null, 2))