Files
workinf_Blender_Wasm/tools/web/check-asset-catalog-field-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

137 lines
7.0 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";
import ts from "../../web/node_modules/typescript/lib/typescript.js";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const manifestPath = path.join(root, "tests/golden/M12-01A/asset-catalog-field-inventory.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const sourceByRole = new Map();
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
assert.equal(manifest.schemaVersion, 1);
assert.equal(manifest.task, "M12-01A");
assert.equal(manifest.enablingTask, true);
assert.equal(manifest.parityStateChange, false);
assert.equal(manifest.blenderVersion, "5.2.0");
assert.equal(manifest.nextTask, "M12-01B");
const expectedRoles = [
"DNA_ASSET_STORAGE",
"RNA_ASSET_API",
"ASSET_BLEND_IO",
"CATALOG_MODEL",
"CATALOG_PATH",
"CATALOG_FILE_API",
"CATALOG_FILE_FORMAT",
"CURRENT_WEB_V1_SNAPSHOT",
];
assert.deepEqual(manifest.sources.map((source) => source.role), expectedRoles);
for (const source of manifest.sources) {
const absolutePath = path.join(root, source.path);
const bytes = fs.readFileSync(absolutePath);
assert.equal(sha256(bytes), source.sha256, `${source.role} source hash drifted`);
sourceByRole.set(source.role, bytes.toString("utf8"));
}
const expectedTagFields = ["next", "prev", "name"];
const expectedDNAFields = [
"local_type_info",
"properties",
"catalog_id",
"catalog_simple_name",
"author",
"description",
"copyright",
"license",
"tags",
"active_tag",
"tot_tags",
"flag",
"preferred_import_method",
"_pad",
];
assert.deepEqual(manifest.assetTag.fields.map((field) => field.name), expectedTagFields);
assert.deepEqual(manifest.assetMetaData.dnaFields.map((field) => field.name), expectedDNAFields);
assert.equal(new Set(expectedDNAFields).size, manifest.assetMetaData.dnaFields.length);
const dna = sourceByRole.get("DNA_ASSET_STORAGE");
for (const field of [...manifest.assetTag.fields, ...manifest.assetMetaData.dnaFields]) {
assert.ok(dna.includes(field.sourceToken), `DNA token for ${field.name} is missing`);
}
assert.equal(manifest.assetTag.fields.find((field) => field.name === "name").maximumStorageBytesIncludingNull, 64);
assert.equal(manifest.assetMetaData.dnaFields.find((field) => field.name === "properties").semantic, "CUSTOM_METADATA_NO_ID_POINTERS");
assert.equal(manifest.assetMetaData.dnaFields.find((field) => field.name === "catalog_simple_name").semantic, "RECOVERY_ONLY_NOT_AUTHORITY");
const rna = sourceByRole.get("RNA_ASSET_API");
const assetDataDefinition = rna.slice(rna.indexOf("static void rna_def_asset_data"), rna.indexOf("static void rna_def_asset_representation"));
const sourceRNAProperties = [...assetDataDefinition.matchAll(/RNA_def_property\(srna, "([^"]+)"/g)].map((match) => match[1]);
assert.deepEqual(sourceRNAProperties, manifest.assetMetaData.rnaProperties.map((property) => property.identifier));
assert.match(rna, /RNA_def_property_string_maxlength\(prop, MAX_NAME\)/);
assert.match(rna, /RNA_def_function\(srna, "new", "rna_AssetMetaData_tag_new"\)/);
assert.match(rna, /RNA_def_function\(srna, "remove", "rna_AssetMetaData_tag_remove"\)/);
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 python = [
"import bpy,json",
"props=[{'identifier':p.identifier,'type':p.type,'isReadonly':p.is_readonly,'isRuntime':p.is_runtime} for p in bpy.types.AssetMetaData.bl_rna.properties if p.identifier != 'rna_type']",
"print('M12_RNA='+json.dumps(props,separators=(',',':')))",
].join(";");
const runtimeOutput = execFileSync(blender, ["--factory-startup", "--background", "--python-expr", python], { encoding: "utf8" });
const runtimeLine = runtimeOutput.split(/\r?\n/).find((line) => line.startsWith("M12_RNA="));
assert.ok(runtimeLine, "Blender AssetMetaData RNA report is missing");
assert.deepEqual(JSON.parse(runtimeLine.slice("M12_RNA=".length)), manifest.assetMetaData.rnaProperties);
const blendIO = sourceByRole.get("ASSET_BLEND_IO");
for (const value of ["properties", "author", "description", "copyright", "license", "tags"]) {
assert.match(blendIO, new RegExp(`asset_data->${value}`), `${value} is not bound to Blend IO`);
}
assert.match(blendIO, /asset_data->tags\.count\(\) == asset_data->tot_tags/);
const catalog = sourceByRole.get("CATALOG_MODEL");
for (const field of [...manifest.catalog.semanticFields, ...manifest.catalog.runtimeFlags]) {
assert.ok(catalog.includes(field.sourceToken), `catalog token for ${field.name} is missing`);
}
assert.deepEqual(manifest.catalog.definitionFile.recordFields, ["catalog_id", "path", "simple_name"]);
assert.deepEqual(manifest.catalog.definitionFile.writeOrder, ["path", "is_first_loaded", "catalog_id"]);
assert.deepEqual(manifest.catalog.identityRules.duplicatePathSelectionOrder, ["is_first_loaded", "catalog_id"]);
const catalogFile = sourceByRole.get("CATALOG_FILE_FORMAT");
assert.match(catalogFile, /SUPPORTED_VERSION = 1/);
assert.match(catalogFile, /VERSION_MARKER = "VERSION "/);
assert.match(catalogFile, /catalog->catalog_id << ":" << catalog->path << ":" << catalog->simple_name/);
const catalogPath = sourceByRole.get("CATALOG_PATH");
for (const statement of [
"Only slashes are used as path component separators",
"Paths are stored as byte sequences, and assumed to be UTF8",
"Empty components (caused by double slashes or leading/trailing slashes) are removed",
]) assert.ok(catalogPath.includes(statement));
const webPath = path.join(root, manifest.sources.find((source) => source.role === "CURRENT_WEB_V1_SNAPSHOT").path);
const webSource = ts.createSourceFile(webPath, fs.readFileSync(webPath, "utf8"), ts.ScriptTarget.Latest, true);
function interfaceFields(name) {
const declaration = webSource.statements.find((statement) => ts.isInterfaceDeclaration(statement) && statement.name.text === name);
assert.ok(declaration, `missing Web interface ${name}`);
return declaration.members.filter(ts.isPropertySignature).map((member) => member.name.getText(webSource));
}
assert.deepEqual(interfaceFields("AssetCatalogIR"), manifest.currentWebSnapshot.assetCatalogIRFields);
assert.deepEqual(interfaceFields("AssetEntryIR"), manifest.currentWebSnapshot.assetEntryIRFields);
assert.deepEqual(manifest.currentWebSnapshot.missingMetadata, [
"properties",
"description",
"copyright",
"catalog_simple_name",
"active_tag",
"use_preferred_import_method",
"preferred_import_method",
]);
assert.equal(manifest.currentWebSnapshot.semanticDrift.length, 5);
process.stdout.write(
`asset-catalog-field-inventory-ok dna=${expectedDNAFields.length} rna=${sourceRNAProperties.length} tag=${expectedTagFields.length} catalog=${manifest.catalog.semanticFields.length} gaps=${manifest.currentWebSnapshot.missingMetadata.length} next=${manifest.nextTask}\n`,
);