Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
131 lines
7.1 KiB
Python
131 lines
7.1 KiB
Python
"""Validation normalization tests; fixtures do not claim Blender coverage."""
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
from wbv2_validation import prepare_validation_command
|
|
|
|
|
|
class ValidationCommandTests(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(temporary.cleanup)
|
|
self.root = Path(temporary.name)
|
|
for relative in [".emscripten-web", ".emsdk/node/24.19.0_64bit/bin/node",
|
|
"build_blender_native/bin/blender", "web/node_modules/@playwright/test/cli.js",
|
|
"check.py", "check.mjs"]:
|
|
path = self.root / relative
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.touch()
|
|
(self.root / ".emsdk/upstream/bin").mkdir(parents=True)
|
|
|
|
def prepare(self, argv, cwd=".", **kwargs):
|
|
return prepare_validation_command(self.root, argv, cwd, **kwargs)
|
|
|
|
def test_actual_source_build_wrapper_normalizes(self):
|
|
entries = {
|
|
"EM_CONFIG": str(self.root / ".emscripten-web"),
|
|
"EM_LLVM_ROOT": str(self.root / ".emsdk/upstream/bin"),
|
|
"EM_BINARYEN_ROOT": str(self.root / ".emsdk/upstream"),
|
|
"EM_NODE_JS": str(self.root / ".emsdk/node/24.19.0_64bit/bin/node"),
|
|
"EM_LLVM_ADD_VERSION": "", "EM_CLANG_ADD_VERSION": "",
|
|
}
|
|
argv = ["env", *[f"{name}={value}" for name, value in entries.items()],
|
|
"cmake", "--build", "build_web_blender6", "--target", "web_engine", "--parallel", "4"]
|
|
command, directory, env, unset = self.prepare(argv)
|
|
self.assertEqual(["cmake", "--build", "build_web_blender6", "--target", "web_engine", "-j", "4"], command)
|
|
self.assertEqual(entries, env)
|
|
self.assertEqual(self.root, directory)
|
|
self.assertEqual([], unset)
|
|
|
|
def test_actual_chromium_wrapper_uses_supervisor_receipt_directory(self):
|
|
argv = ["env", "-u", "SERVER_OPERATOR_NATIVE_REPORT",
|
|
f"BLENDER_BIN={self.root}/build_blender_native/bin/blender", "WEB_TEST_PORT=5197",
|
|
f"WBV2_RECEIPT_DIR={self.root}/tests/golden/old-attempt/cases", "node",
|
|
"node_modules/@playwright/test/cli.js", "test", "--config", "playwright.config.ts",
|
|
"tests/e2e/server-operator-native.spec.ts", "--workers=1"]
|
|
command, directory, env, unset = self.prepare(argv, "web")
|
|
self.assertEqual("node", command[0])
|
|
self.assertEqual(self.root / "web", directory)
|
|
self.assertEqual({"BLENDER_BIN": str(self.root / "build_blender_native/bin/blender"), "WEB_TEST_PORT": "5197"}, env)
|
|
self.assertEqual(["SERVER_OPERATOR_NATIVE_REPORT"], unset)
|
|
self.assertNotIn("WBV2_RECEIPT_DIR", env)
|
|
|
|
def test_worker_cannot_override_supervisor_environment(self):
|
|
_, _, env, unset = self.prepare([
|
|
"env", "WBV2_RECEIPT_DIR=/tmp/stale", "WBV2_DEPLOYED_ORIGIN=http://wrong",
|
|
"WEB_BUILD_JOBS=1000", "CMAKE_BUILD_PARALLEL_LEVEL=1000", "node", "check.mjs",
|
|
])
|
|
self.assertEqual({}, env)
|
|
self.assertEqual([], unset)
|
|
|
|
def test_parallel_flags_respect_resource_budget(self):
|
|
for program in ["cmake", "ninja", "ctest"]:
|
|
for flags in [["--parallel", "999"], ["--parallel=999"], ["-j999"], ["-j=999"], ["-j", "0"], ["-j"]]:
|
|
with self.subTest(program=program, flags=flags):
|
|
command, _, _, _ = self.prepare([program, *flags], max_build_jobs=3)
|
|
self.assertEqual([program, "-j", "3"], command)
|
|
self.assertEqual(["ninja", "-j", "8"], self.prepare(["ninja", "-j100"], max_build_jobs=20)[0])
|
|
self.assertEqual(["ninja", "-j", "2"], self.prepare(["ninja", "-j2"])[0])
|
|
|
|
def test_default_build_jobs_are_explicitly_bounded(self):
|
|
self.assertEqual(["ninja", "-j", "3"], self.prepare(["ninja"], max_build_jobs=3)[0])
|
|
self.assertEqual(["ctest", "-j", "3"], self.prepare(["ctest"], max_build_jobs=3)[0])
|
|
self.assertEqual(["cmake", "--build", "build", "-j", "3", "--", "target"],
|
|
self.prepare(["cmake", "--build", "build", "--", "target"], max_build_jobs=3)[0])
|
|
self.assertEqual(["cmake", "--version"], self.prepare(["cmake", "--version"])[0])
|
|
|
|
def test_rejects_env_injection_options_and_unsets(self):
|
|
wrappers = [
|
|
["PATH=/tmp"], ["NODE_OPTIONS=--require=/tmp/evil.js"], ["PYTHONPATH=/tmp"],
|
|
["LD_PRELOAD=/tmp/evil.so"], ["-i"], ["-S", "node check.mjs"],
|
|
["--chdir=/tmp"], ["-u", "WBV2_RECEIPT_DIR"], ["-u", "PATH"],
|
|
["WEB_TEST_PORT=5197", "WEB_TEST_PORT=5198"], ["EM_CLANG_ADD_VERSION=evil"],
|
|
]
|
|
for wrapper in wrappers:
|
|
with self.subTest(wrapper=wrapper), self.assertRaises(ValueError):
|
|
self.prepare(["env", *wrapper, "node", "check.mjs"])
|
|
|
|
def test_invalid_ports_and_missing_paths(self):
|
|
for assignment in ["WEB_TEST_PORT=0", "WEB_TEST_PORT=65536", "WEB_TEST_PORT=abc",
|
|
"WEB_TEST_PORT=80", "EM_CONFIG=", "EM_CONFIG=check.py",
|
|
"BLENDER_BIN=/tmp/blender", "EM_NODE_JS=check.py"]:
|
|
with self.subTest(assignment=assignment), self.assertRaises(ValueError):
|
|
self.prepare(["env", assignment, "node", "check.mjs"])
|
|
|
|
def test_toolchain_symlink_escape_is_rejected(self):
|
|
(self.root / ".emscripten-web").unlink()
|
|
(self.root / ".emscripten-web").symlink_to("/etc/passwd")
|
|
with self.assertRaisesRegex(ValueError, "escapes"):
|
|
self.prepare(["env", f"EM_CONFIG={self.root}/.emscripten-web", "node", "check.mjs"])
|
|
|
|
def test_preserves_mandatory_validation_guards(self):
|
|
commands = [
|
|
["bash", "check.py"], ["env", "node", "-e", "check.mjs"],
|
|
["node", "--eval=1", "check.mjs"], ["node", "-eprocess.exit(0)", "check.mjs"],
|
|
["python3", "-cprint(0)", "check.py"], ["python3", "missing.py"],
|
|
["node", "check.mjs", "--project=firefox"], ["npm", "run", "test:webkit"],
|
|
["npm", "install"], ["git", "reset", "--hard"],
|
|
["env", "env", "node", "check.mjs"], ["env"],
|
|
]
|
|
for command in commands:
|
|
with self.subTest(command=command), self.assertRaises(ValueError):
|
|
self.prepare(command)
|
|
|
|
def test_accepts_plain_validation_and_env_option_terminator(self):
|
|
for command in [["git", "diff", "--check"], ["npm", "run", "test"], ["node", "check.mjs"], ["python3", "check.py"]]:
|
|
self.assertEqual((command, self.root, {}, []), self.prepare(command))
|
|
self.assertEqual((["node", "check.mjs"], self.root, {}, []), self.prepare(["env", "--", "node", "check.mjs"]))
|
|
|
|
def test_rejects_non_argv_input_and_repository_escape(self):
|
|
for command in ["node check.mjs", [], ["node", ""], ["node", "bad\0arg"]]:
|
|
with self.subTest(command=command), self.assertRaises(ValueError):
|
|
self.prepare(command)
|
|
for cwd in ["../outside", "/tmp", "missing"]:
|
|
with self.subTest(cwd=cwd), self.assertRaises(ValueError):
|
|
self.prepare(["npm", "run", "test"], cwd)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|