Files
workinf_Blender_Wasm/tools/web/check-library-operation-inventory.mjs
mes123456 5a11045ca5
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Advance Blender 5.2 web parity through M12-03D
2026-08-17 17:30:27 -04:00

154 lines
7.8 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const inventory = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-03A/library-operation-inventory.json"), "utf8"));
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
const sources = new Map();
assert.equal(inventory.schemaVersion, 1);
assert.equal(inventory.task, "M12-03A");
assert.equal(inventory.enablingTask, true);
assert.equal(inventory.parityStateChange, false);
assert.equal(inventory.blenderVersion, "5.2.0");
assert.equal(inventory.nextTask, "M12-03B");
assert.deepEqual(inventory.sources.map((source) => source.role), [
"LINK_APPEND_CONTEXT_API",
"LINK_APPEND_CLOSURE",
"LINK_APPEND_OPERATORS",
"PYTHON_LIBRARY_LOAD",
"BLEND_LIBRARY_READER",
"OVERRIDE_STORAGE",
"OVERRIDE_API",
"OVERRIDE_CLOSURE",
"CURRENT_WEB_LIBRARY_GATE",
]);
for (const source of inventory.sources) {
const bytes = fs.readFileSync(path.join(root, source.path));
assert.equal(sha256(bytes), source.sha256, `${source.role} source hash drifted`);
sources.set(source.role, bytes.toString("utf8"));
}
assert.equal(sha256(fs.readFileSync(path.join(root, inventory.runtimeFixture.path))), inventory.runtimeFixture.sha256);
assert.equal(inventory.dataBlockSurface.discoverableCollections.length, 36);
assert.equal(new Set(inventory.dataBlockSurface.discoverableCollections).size, 36);
assert.deepEqual(inventory.dataBlockSurface.onlyAppendableCollections, ["screens", "workspaces"]);
assert.deepEqual(inventory.operations.map((operation) => operation.operation), ["APPEND", "LINK", "LIBRARY_OVERRIDE"]);
for (const operation of inventory.operations) {
assert.ok(operation.dataBlockRoles.length >= 6, `${operation.operation} has an incomplete role inventory`);
assert.ok(operation.closure && Object.keys(operation.closure).length >= 5, `${operation.operation} has an incomplete closure inventory`);
}
const context = sources.get("LINK_APPEND_CONTEXT_API");
for (const token of [
"LINK_APPEND_ACT_KEEP_LINKED",
"LINK_APPEND_ACT_REUSE_LOCAL",
"LINK_APPEND_ACT_MAKE_LOCAL",
"LINK_APPEND_ACT_COPY_LOCAL",
"LINK_APPEND_TAG_INDIRECT",
"LINK_APPEND_TAG_LIBOVERRIDE_DEPENDENCY",
"LINK_APPEND_TAG_LIBOVERRIDE_DEPENDENCY_ONLY",
"ID *new_id",
"Library *source_library",
"ID *liboverride_id",
"ID *reusable_local_id",
"enum class ProcessStage",
]) assert.ok(context.includes(token), `link/append context token is missing: ${token}`);
const closure = sources.get("LINK_APPEND_CLOSURE");
for (const token of [
"BKE_library_foreach_ID_link",
"IDWALK_CB_EMBEDDED",
"IDWALK_CB_EMBEDDED_NOT_OWNING",
"IDWALK_CB_INTERNAL",
"IDWALK_CB_LOOPBACK",
"LINK_APPEND_TAG_LIBOVERRIDE_DEPENDENCY_ONLY",
"new_id->lib->runtime->parent",
]) assert.ok(closure.includes(token), `dependency closure token is missing: ${token}`);
assert.match(closure, /BKE_blendfile_append\(/);
assert.match(closure, /BKE_blendfile_override\(/);
const operators = sources.get("LINK_APPEND_OPERATORS");
for (const token of [
"BKE_idtype_idcode_is_linkable(idcode)",
"BKE_idtype_idcode_is_only_appendable(idcode)",
"BLO_LIBLINK_APPEND_RECURSIVE",
"BLO_LIBLINK_APPEND_LOCAL_ID_REUSE",
"void WM_OT_link",
"void WM_OT_append",
]) assert.ok(operators.includes(token), `operator inventory token is missing: ${token}`);
const pythonLoad = sources.get("PYTHON_LIBRARY_LOAD");
for (const argument of inventory.operatorSurface.pythonLoadArguments) {
assert.ok(pythonLoad.includes(`\"${argument}\"`), `Python load argument is missing: ${argument}`);
}
for (const token of [
"if (!is_library && !BKE_idtype_idcode_is_linkable(code))",
"if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append))",
"create_liboverrides",
"BKE_blendfile_link(lapp_context",
"BKE_blendfile_append(lapp_context",
"BKE_blendfile_override(lapp_context",
]) assert.ok(pythonLoad.includes(token), `Python library closure token is missing: ${token}`);
const reader = sources.get("BLEND_LIBRARY_READER");
for (const token of ["BLO_library_link_begin", "BLO_library_link_named_part", "BLO_library_link_end"]) {
assert.ok(reader.includes(token), `reader stage is missing: ${token}`);
}
const overrideStorage = sources.get("OVERRIDE_STORAGE");
for (const token of [
"struct IDOverrideLibraryPropertyOperation",
"struct IDOverrideLibraryProperty",
"struct IDOverrideLibrary",
"ID *reference",
"ID *hierarchy_root",
"ListBaseT<IDOverrideLibraryProperty> properties",
]) assert.ok(overrideStorage.includes(token), `override storage token is missing: ${token}`);
const overrideApi = sources.get("OVERRIDE_API");
for (const token of [
"BKE_lib_override_library_create(",
"BKE_lib_override_library_resync(",
"BKE_lib_override_library_operations_create(",
]) assert.ok(overrideApi.includes(token), `override API token is missing: ${token}`);
assert.match(sources.get("OVERRIDE_CLOSURE"), /BKE_library_foreach_ID_link/);
const web = sources.get("CURRENT_WEB_LIBRARY_GATE");
assert.match(web, /gateLibraryMutation\(operation:/);
assert.match(web, /LIBRARY_MUTATION_UNAVAILABLE/);
for (const gap of inventory.currentWebGap.missing) assert.ok(!inventory.currentWebGap.represented.includes(gap));
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
assert.match(execFileSync(blender, ["--version"], { encoding: "utf8" }), /^Blender 5\.2\.0 LTS/m);
const script = [
"import bpy,json",
`fixture=${JSON.stringify(path.join(root, inventory.runtimeFixture.path))}`,
"result={}",
"result['appendProperties']=[p.identifier for p in bpy.ops.wm.append.get_rna_type().properties if p.identifier != 'rna_type']",
"result['linkProperties']=[p.identifier for p in bpy.ops.wm.link.get_rna_type().properties if p.identifier != 'rna_type']",
"ctx=bpy.data.libraries.load(fixture,link=True)",
"source,target=ctx.__enter__()",
"result['collections']=sorted([name for name in dir(source) if not name.startswith('_') and name not in {'libraries','version'}])",
"result['libraries']=[{'filepath':item.filepath,'isArchive':item.is_archive} for item in source.libraries]",
"ctx.__exit__(None,None,None)",
"result['overrideRNA']={name:[{'identifier':p.identifier,'type':p.type,'isReadonly':p.is_readonly} for p in getattr(bpy.types,name).bl_rna.properties if p.identifier != 'rna_type'] for name in ['IDOverrideLibrary','IDOverrideLibraryProperty','IDOverrideLibraryPropertyOperation']}",
"print('M12_03A='+json.dumps(result,separators=(',',':'),sort_keys=True))",
].join(";");
const runtimeOutput = execFileSync(blender, ["--background", "--factory-startup", "--python-expr", script], { cwd: root, encoding: "utf8" });
const runtimeLine = runtimeOutput.split(/\r?\n/).find((line) => line.startsWith("M12_03A="));
assert.ok(runtimeLine, "Blender link/append/override runtime inventory is missing");
const runtime = JSON.parse(runtimeLine.slice("M12_03A=".length));
assert.deepEqual(runtime.appendProperties, inventory.operatorSurface.appendProperties);
assert.deepEqual(runtime.linkProperties, inventory.operatorSurface.linkProperties);
assert.deepEqual(runtime.collections, inventory.dataBlockSurface.discoverableCollections);
assert.deepEqual(runtime.libraries, inventory.runtimeFixture.nestedLibraries);
assert.deepEqual(runtime.overrideRNA, inventory.overrideRNA);
process.stdout.write(
`library-operation-inventory-ok roots=${runtime.collections.length} appendOnly=${inventory.dataBlockSurface.onlyAppendableCollections.length} operations=${inventory.operations.length} overrideFields=${Object.values(runtime.overrideRNA).reduce((sum, values) => sum + values.length, 0)} gaps=${inventory.currentWebGap.missing.length} next=${inventory.nextTask}\n`,
);