chore: checkpoint web Blender migration source, tools and docs
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-10 11:18:01 +08:00
parent 122142c26c
commit f3e21c6b98
6887 changed files with 257154 additions and 12294 deletions

View File

@@ -0,0 +1,63 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { blendFormat, candidateInputs, fileHash, reportOutcome, reusable, sameInputs, validatePlan, validateSourceReview } from '../../../tools/web/workflow-case-lib.mjs';
function temporary(t) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wb-case-')); t.after(() => fs.rmSync(dir, { recursive: true, force: true })); return dir; }
const report = () => ({ stats: { expected: 1, unexpected: 0, flaky: 0, skipped: 0 }, errors: [], suites: [{ specs: [{ tests: [{ expectedStatus: 'passed', status: 'expected', results: [{ status: 'passed' }] }] }] }] });
test('source reviews reject stale hashes, malformed types and unbound contracts', () => {
const sha256 = 'a'.repeat(64), review = { schemaVersion: 1, status: 'REVIEWED_SCOPE', batchId: 'B1', verifiedUnitDelta: 0, bindings: [{ path: 'source.cc', sha256 }], contracts: [{ id: 'rna', scope: 'location', sourcePaths: ['source.cc'], proofStages: ['changedUI'] }] };
assert.equal(validateSourceReview(review, () => sha256), review);
assert.throws(() => validateSourceReview(review, () => 'b'.repeat(64)), /SOURCE_REVIEW_DRIFT/);
assert.throws(() => validateSourceReview({ ...review, bindings: [{ path: 'source.cc', sha256: 42 }] }, () => sha256));
assert.throws(() => validateSourceReview({ ...review, contracts: [{ ...review.contracts[0], sourcePaths: ['unreviewed.cc'] }] }, () => sha256), /must be bound/);
assert.throws(() => validateSourceReview({ ...review, contracts: [...review.contracts, ...review.contracts] }, () => sha256), /duplicate/);
});
test('report types are checked before comparisons; no coercion', () => {
assert.equal(reportOutcome(report()).passed, true);
for (const value of ['1', null, NaN, -1]) { const r = report(); r.stats.expected = value; assert.throws(() => reportOutcome(r), /integer/); }
assert.throws(() => reportOutcome({ ...report(), suites: {} }), /arrays/);
});
test('empty or inconsistent counters cannot produce a pass', () => {
const r = report(); r.suites = []; r.stats.expected = 0; assert.equal(reportOutcome(r).passed, false);
assert.equal(reportOutcome({ ...report(), stats: { ...report().stats, expected: 2 } }).passed, false);
});
test('skip, expected failure, retry, flaky outcome and global error remain failures', () => {
for (const status of ['skipped', 'flaky', 'unexpected']) { const r = report(); r.suites[0].specs[0].tests[0].status = status; assert.equal(reportOutcome(r).passed, false); }
const r = report(), t = r.suites[0].specs[0].tests[0]; t.expectedStatus = 'failed'; assert.equal(reportOutcome(r).passed, false);
t.expectedStatus = 'passed'; t.results.push({ status: 'passed' }); assert.equal(reportOutcome(r).passed, false);
assert.equal(reportOutcome({ ...report(), errors: [{ message: 'setup failed' }] }).passed, false);
});
test('reuse requires matching identity and all original artifacts', t => {
const file = path.join(temporary(t), 'evidence'); fs.writeFileSync(file, 'first');
const saved = { status: 'PASS', inputHash: 'input', artifacts: [{ path: file, sha256: fileHash(file) }] };
assert.equal(reusable(saved, 'input'), true); assert.equal(reusable(saved, 'different'), false);
fs.writeFileSync(file, 'drift'); assert.equal(reusable(saved, 'input'), false);
fs.unlinkSync(file); assert.equal(reusable(saved, 'input'), false); assert.equal(reusable({ ...saved, artifacts: [] }, 'input'), false);
});
test('candidate SHA and actual WASM bytes are required', t => {
const dir = temporary(t), native = path.join(dir, 'public/vendor/blender'); fs.mkdirSync(native, { recursive: true });
const js = path.join(native, 'web_engine.js'), wasm = path.join(native, 'web_engine.wasm'); fs.writeFileSync(js, 'export default {}'); fs.writeFileSync(wasm, Buffer.from([0,97,115,109,1,0,0,0]));
fs.writeFileSync(path.join(dir, 'candidate.json'), JSON.stringify({ sha256: { 'web_engine.js': fileHash(js), 'web_engine.wasm': fileHash(wasm) } }));
assert.equal(candidateInputs(dir).length, 3); fs.appendFileSync(wasm, 'changed'); assert.throws(() => candidateInputs(dir), /drift/);
});
test('blend compression is detected from bytes, and numeric strings stay distinct', t => {
const file = path.join(temporary(t), 'scene.blend');
for (const [bytes, format] of [[Buffer.from('BLENDER'), 'blend'], [Buffer.from([0x1f,0x8b]), 'gzip'], [Buffer.from([0x28,0xb5,0x2f,0xfd]), 'zstd']]) { fs.writeFileSync(file, bytes); assert.equal(blendFormat(file), format); }
fs.writeFileSync(file, 'notblend'); assert.throws(() => blendFormat(file), /header/);
assert.throws(() => sameInputs({ revision: 1 }, { revision: '1' }), /inputs changed/);
sameInputs({ revision: 1, values: [0, 1] }, { revision: 1, values: [0, 1] });
});
test('case plans preserve minimal/production order and reject coercible options', () => {
const p = JSON.parse(fs.readFileSync(new URL('../../../tools/web/workflow-cases/B1.json', import.meta.url), 'utf8'));
assert.equal(validatePlan(p).batchId, 'B1');
assert.throws(() => validatePlan({ ...p, timeoutMs: '180000' }), /integer/);
assert.throws(() => validatePlan({ ...p, nativeSession: 'true' }), /boolean/);
assert.throws(() => validatePlan({ ...p, fixtures: [...p.fixtures].reverse() }));
});
test('source suites require one source fixture and a bound Blender test path', () => {
const p = JSON.parse(fs.readFileSync(new URL('../../../tools/web/workflow-cases/B1.json', import.meta.url), 'utf8')), suite = 'blender-5.2.0/tests/python/bl_blendfile_io.py';
const source = { ...p, sourceSuite: suite, sourcePaths: [suite], fixtures: [{ ...p.fixtures[0], kind: 'source' }] }; assert.equal(validatePlan(source).sourceSuite, suite);
assert.throws(() => validatePlan({ ...source, sourcePaths: ['unbound.py'] })); assert.throws(() => validatePlan({ ...source, sourceSuite: '../outside.py' })); assert.throws(() => validatePlan({ ...source, fixtures: p.fixtures }));
});