137 lines
7.0 KiB
JavaScript
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`,
|
|
);
|