154 lines
7.8 KiB
JavaScript
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`,
|
|
);
|