import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const installer = path.join(repoRoot, "tools/web/install-web-engine-assets.sh"); const workspace = fs.mkdtempSync(path.join(os.tmpdir(), "engine-variant-install-test-")); function sha256(bytes) { return crypto.createHash("sha256").update(bytes).digest("hex"); } function writeBuild(directory, label, includeWasm = true) { fs.mkdirSync(directory, { recursive: true }); fs.writeFileSync(path.join(directory, "web_engine.js"), `export default ${JSON.stringify(label)};\n`); if (includeWasm) fs.writeFileSync(path.join(directory, "web_engine.wasm"), Buffer.from(`wasm:${label}`)); } function writeLegacyManifest(publicRoot) { fs.mkdirSync(publicRoot, { recursive: true }); const manifestPath = path.join(publicRoot, "engine-manifest.json"); fs.writeFileSync(manifestPath, `${JSON.stringify({ schemaVersion: 1, protocolVersion: 1, engineVersion: "blender-wasm-install-test", engine: "blender-wasm", memory: { initialPages: 256, maximumPages: 32768, shared: false }, wasm: [], }, null, 2)}\n`); return manifestPath; } function runInstaller(singleBuild, pthreadBuild, publicRoot) { return spawnSync("bash", [installer], { cwd: repoRoot, encoding: "utf8", env: { ...process.env, WEB_ENGINE_SINGLE_BUILD_DIR: singleBuild, WEB_ENGINE_PTHREAD_BUILD_DIR: pthreadBuild, WEB_ENGINE_PUBLIC_ROOT: publicRoot, }, }); } try { const positive = path.join(workspace, "positive"); const singleBuild = path.join(positive, "build-single"); const pthreadBuild = path.join(positive, "build-pthread"); const publicRoot = path.join(positive, "public"); writeBuild(singleBuild, "single-output"); writeBuild(pthreadBuild, "pthread-output"); writeLegacyManifest(publicRoot); fs.mkdirSync(path.join(publicRoot, "vendor/blender"), { recursive: true }); fs.writeFileSync(path.join(publicRoot, "vendor/blender/web_engine.js"), "legacy-loader\n"); const installed = runInstaller(singleBuild, pthreadBuild, publicRoot); assert.equal(installed.status, 0, installed.stderr || installed.stdout); const manifest = JSON.parse(fs.readFileSync(path.join(publicRoot, "engine-manifest.json"), "utf8")); assert.equal(manifest.schemaVersion, 2); assert.equal(manifest.releaseId, "blender-wasm-install-test"); assert.deepEqual(manifest.variants.map((variant) => variant.id), ["single", "pthread"]); for (const [index, buildDirectory] of [[0, singleBuild], [1, pthreadBuild]]) { const variant = manifest.variants[index]; for (const role of ["js", "wasm"]) { const resource = variant.resources[role]; const source = fs.readFileSync(path.join(buildDirectory, resource.fileName)); const target = fs.readFileSync(path.join(publicRoot, resource.url)); assert.deepEqual(target, source); assert.equal(resource.sha256, sha256(source)); } } assert.equal(fs.readFileSync(path.join(publicRoot, "vendor/blender/web_engine.js"), "utf8"), "legacy-loader\n"); assert.deepEqual(manifest.variants[1].resources.pthreadWorker, manifest.variants[1].resources.js); const sameBuildRoot = path.join(workspace, "same-build-negative"); const sameBuild = path.join(sameBuildRoot, "build"); const samePublic = path.join(sameBuildRoot, "public"); writeBuild(sameBuild, "same-output"); const sameManifest = writeLegacyManifest(samePublic); const beforeSame = fs.readFileSync(sameManifest); const sameResult = runInstaller(sameBuild, sameBuild, samePublic); assert.notEqual(sameResult.status, 0); assert.match(sameResult.stderr, /independent build directories/); assert.deepEqual(fs.readFileSync(sameManifest), beforeSame); assert.equal(fs.existsSync(path.join(samePublic, "vendor/blender/single")), false); const missingRoot = path.join(workspace, "missing-asset-negative"); const completeBuild = path.join(missingRoot, "build-single"); const incompleteBuild = path.join(missingRoot, "build-pthread"); const missingPublic = path.join(missingRoot, "public"); writeBuild(completeBuild, "complete-output"); writeBuild(incompleteBuild, "incomplete-output", false); const missingManifest = writeLegacyManifest(missingPublic); const beforeMissing = fs.readFileSync(missingManifest); const missingResult = runInstaller(completeBuild, incompleteBuild, missingPublic); assert.notEqual(missingResult.status, 0); assert.deepEqual(fs.readFileSync(missingManifest), beforeMissing); assert.equal(fs.existsSync(path.join(missingPublic, "vendor/blender/single")), false); process.stdout.write("engine-variant-install-ok variants=2 negativeCases=2\n"); } finally { fs.rmSync(workspace, { recursive: true, force: true }); }