first commit
This commit is contained in:
@@ -0,0 +1,655 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import ModuleType, SimpleNamespace
|
||||
|
||||
from tests.python.support.paths import add_repo_path, repo_path
|
||||
|
||||
add_repo_path("skills/cad/scripts")
|
||||
|
||||
import snapshot.__main__ as snapshot_main
|
||||
from snapshot.__main__ import (
|
||||
RENDER_HTML_PATH,
|
||||
RUNTIME_DIR,
|
||||
SnapshotError,
|
||||
load_job_from_options,
|
||||
parse_snapshot_args,
|
||||
resolve_render_job_packet,
|
||||
resolve_snapshot_route_file,
|
||||
timestamp_output_path,
|
||||
)
|
||||
|
||||
|
||||
class _TtyStringIO(io.StringIO):
|
||||
def isatty(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _selector_artifact(*occurrence_ids: str) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
selector_bundle=SimpleNamespace(
|
||||
manifest={
|
||||
"tables": {
|
||||
"occurrenceColumns": ["id"],
|
||||
"shapeColumns": ["id", "occurrenceId"],
|
||||
},
|
||||
"occurrences": [[occurrence_id] for occurrence_id in occurrence_ids],
|
||||
"shapes": [],
|
||||
},
|
||||
buffers={},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SnapshotCliTests(unittest.TestCase):
|
||||
def test_cli_import_does_not_import_heavy_cad_modules(self) -> None:
|
||||
skill_root = repo_path("skills/cad")
|
||||
code = (
|
||||
"import sys; sys.path.insert(0, 'scripts'); import snapshot.__main__; "
|
||||
"print('OCP.OCP' in sys.modules); "
|
||||
"print('cadpy.step_scene' in sys.modules)"
|
||||
)
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", code],
|
||||
cwd=skill_root,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual("", result.stderr)
|
||||
self.assertEqual(0, result.returncode)
|
||||
self.assertEqual(["False", "False"], result.stdout.strip().splitlines())
|
||||
|
||||
def test_shortcut_job_shape_stays_owned_by_python_cli(self) -> None:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--display",
|
||||
"wireframe",
|
||||
"--size-profile",
|
||||
"simple",
|
||||
]
|
||||
)
|
||||
|
||||
job = load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
|
||||
self.assertEqual(job["input"], "models/simple/cylindrical_cap.step")
|
||||
self.assertNotIn("workspaceRoot", job)
|
||||
self.assertNotIn("rootDir", job)
|
||||
self.assertEqual(job["outputs"][0]["path"], "tmp/cap.png")
|
||||
self.assertEqual(job["display"], {"mode": "wireframe"})
|
||||
self.assertEqual(job["render"]["sizeProfile"], "simple")
|
||||
|
||||
def test_shortcut_focus_and_hide_flags_are_mutually_exclusive(self) -> None:
|
||||
with self.assertRaisesRegex(SnapshotError, "--focus and --hide cannot be used"):
|
||||
parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/assembly.step",
|
||||
"--output",
|
||||
"tmp/assembly.png",
|
||||
"--focus",
|
||||
"#o1.2",
|
||||
"--hide=#o1.3.1",
|
||||
]
|
||||
)
|
||||
|
||||
def test_display_shortcut_accepts_cad_display_modes(self) -> None:
|
||||
for raw_mode, expected_display in [
|
||||
("edges", {"mode": "solid"}),
|
||||
("x-ray", {"mode": "transparent"}),
|
||||
("hidden edges visible", {"mode": "hidden_edges"}),
|
||||
("hidden-lines-removed", {"mode": "hidden_lines_removed"}),
|
||||
("flat", {"mode": "unshaded"}),
|
||||
("appearance", {"mode": "rendered"}),
|
||||
("wire", {"mode": "wireframe"}),
|
||||
]:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--display",
|
||||
raw_mode,
|
||||
]
|
||||
)
|
||||
job = load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
self.assertEqual(job["display"], expected_display)
|
||||
|
||||
def test_display_json_accepts_exploded_settings(self) -> None:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--display",
|
||||
'{"projection":"perspective","mode":"rendered","exploded":{"enabled":true,"axis":"radial","spacing":1.6}}',
|
||||
]
|
||||
)
|
||||
job = load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
self.assertEqual(
|
||||
job["display"],
|
||||
{
|
||||
"projection": "perspective",
|
||||
"mode": "rendered",
|
||||
"exploded": {"enabled": True, "axis": "radial", "spacing": 1.6},
|
||||
},
|
||||
)
|
||||
|
||||
def test_edge_settings_belong_to_display_json(self) -> None:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--display",
|
||||
'{"edges":{"enabled":false,"color":"#123456"}}',
|
||||
]
|
||||
)
|
||||
job = load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
self.assertEqual(job["display"], {"edges": {"enabled": False, "color": "#123456"}})
|
||||
|
||||
appearance_options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--appearance",
|
||||
'{"edges":{"enabled":false}}',
|
||||
]
|
||||
)
|
||||
with self.assertRaisesRegex(SnapshotError, "unsupported keys: edges"):
|
||||
load_job_from_options(appearance_options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
|
||||
def test_display_shortcut_rejects_unknown_modes(self) -> None:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--display",
|
||||
"mist",
|
||||
]
|
||||
)
|
||||
with self.assertRaisesRegex(SnapshotError, "Unsupported display mode"):
|
||||
load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
|
||||
def test_display_shortcut_rejects_exploded_mode_alias(self) -> None:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/simple/cylindrical_cap.step",
|
||||
"--output",
|
||||
"tmp/cap.png",
|
||||
"--display",
|
||||
"exploded",
|
||||
]
|
||||
)
|
||||
with self.assertRaisesRegex(SnapshotError, "Unsupported display mode"):
|
||||
load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
|
||||
def test_shortcut_focus_flags_apply_selection(self) -> None:
|
||||
options = parse_snapshot_args(
|
||||
[
|
||||
"--input",
|
||||
"models/assembly.step",
|
||||
"--output",
|
||||
"tmp/assembly.png",
|
||||
"--focus",
|
||||
"#o1.2",
|
||||
"#o1.3",
|
||||
]
|
||||
)
|
||||
|
||||
job = load_job_from_options(options, stdin=_TtyStringIO(), cwd=Path.cwd())
|
||||
|
||||
self.assertEqual(
|
||||
job["selection"],
|
||||
{
|
||||
"focus": ["#o1.2", "#o1.3"],
|
||||
},
|
||||
)
|
||||
|
||||
def test_output_paths_are_timestamped_when_jobs_are_resolved(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "part.step").write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".part.step.glb").write_bytes(b"glb")
|
||||
|
||||
original_timestamp = snapshot_main.snapshot_timestamp
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.snapshot_timestamp = lambda: "20260527T163012Z"
|
||||
snapshot_main.ensure_step_topology_artifact = lambda *args, **kwargs: None
|
||||
|
||||
packet = resolve_render_job_packet(
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"input": "models/part.step",
|
||||
"outputs": [
|
||||
{"path": "tmp/iso.png", "camera": "iso"},
|
||||
{"path": "tmp/front.png", "camera": "front"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"input": "models/part.step",
|
||||
"mode": "orbit",
|
||||
"outputs": [{"path": "tmp/orbit.gif"}],
|
||||
},
|
||||
]
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.snapshot_timestamp = original_timestamp
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
output_paths = [
|
||||
Path(output["path"]).relative_to(root).as_posix()
|
||||
for job in packet["jobs"]
|
||||
for output in job["outputs"]
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
output_paths,
|
||||
[
|
||||
"tmp/iso_20260527T163012Z.png",
|
||||
"tmp/front_20260527T163012Z.png",
|
||||
"tmp/orbit_20260527T163012Z.gif",
|
||||
],
|
||||
)
|
||||
|
||||
def test_render_job_derives_asset_root_from_input_path(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "part.step").write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".part.step.glb").write_bytes(b"glb")
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = lambda *args, **kwargs: None
|
||||
packet = resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/part.step",
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
job = packet["jobs"][0]
|
||||
self.assertNotIn("workspaceRoot", job)
|
||||
self.assertNotIn("rootDir", job)
|
||||
self.assertEqual(job["resolved"]["rootPath"], str(models))
|
||||
self.assertEqual(job["resolved"]["inputUrl"], "/__render_asset/part.step")
|
||||
self.assertEqual(job["resolved"]["glbUrl"], "/__render_asset/.part.step.glb")
|
||||
|
||||
def test_render_job_ensures_step_artifact_for_step_input(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
step_path = models / "part.step"
|
||||
step_path.write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".part.step.glb").write_bytes(b"glb")
|
||||
calls = []
|
||||
|
||||
def fake_ensure(target, **kwargs):
|
||||
calls.append((target, kwargs))
|
||||
return None
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = fake_ensure
|
||||
resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/part.step",
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
self.assertEqual(len(calls), 1)
|
||||
target, kwargs = calls[0]
|
||||
self.assertEqual(target.step_path, step_path)
|
||||
self.assertEqual(target.source_path, step_path)
|
||||
self.assertEqual(kwargs["owner"], "cad-snapshot")
|
||||
self.assertFalse(kwargs["require_selector"])
|
||||
|
||||
def test_render_job_rejects_non_step_input_without_artifact_generation(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "robot.urdf").write_text("<robot name=\"r\" />\n", encoding="utf-8")
|
||||
calls = []
|
||||
|
||||
def fake_ensure(target, **kwargs):
|
||||
calls.append((target, kwargs))
|
||||
return None
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = fake_ensure
|
||||
with self.assertRaisesRegex(
|
||||
SnapshotError,
|
||||
"Snapshot supports only STEP/STP inputs or same-stem Python generators",
|
||||
):
|
||||
resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/robot.urdf",
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_render_job_requires_selector_topology_for_cad_refs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
step_path = models / "assembly.step"
|
||||
step_path.write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".assembly.step.glb").write_bytes(b"glb")
|
||||
calls = []
|
||||
|
||||
def fake_ensure(target, **kwargs):
|
||||
calls.append((target, kwargs))
|
||||
return _selector_artifact("o1", "o1.2")
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = fake_ensure
|
||||
resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/assembly.step",
|
||||
"selection": {"focus": ["#o1.2"]},
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
self.assertEqual(len(calls), 1)
|
||||
target, kwargs = calls[0]
|
||||
self.assertEqual(target.step_path, step_path)
|
||||
self.assertTrue(kwargs["require_selector"])
|
||||
|
||||
def test_render_job_normalizes_focus_selector_refs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "assembly.step").write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".assembly.step.glb").write_bytes(b"glb")
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = lambda *args, **kwargs: _selector_artifact(
|
||||
"o1",
|
||||
"o1.2",
|
||||
"o1.2.1",
|
||||
"o1.3",
|
||||
)
|
||||
packet = resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/assembly.step",
|
||||
"selection": {
|
||||
"focus": ["#o1.2", "#o1.3"],
|
||||
},
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
selection = packet["jobs"][0]["selection"]
|
||||
self.assertEqual(selection["focus"], ["o1.2", "o1.3"])
|
||||
|
||||
def test_render_job_normalizes_hide_selector_refs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "assembly.step").write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".assembly.step.glb").write_bytes(b"glb")
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = lambda *args, **kwargs: _selector_artifact(
|
||||
"o1",
|
||||
"o1.2",
|
||||
"o1.2.1",
|
||||
"o1.3",
|
||||
)
|
||||
packet = resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/assembly.step",
|
||||
"selection": {"hide": ["#o1.2.1"]},
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
selection = packet["jobs"][0]["selection"]
|
||||
self.assertEqual(selection["hide"], ["o1.2.1"])
|
||||
|
||||
def test_render_job_rejects_face_focus_refs(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "assembly.step").write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".assembly.step.glb").write_bytes(b"glb")
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = lambda *args, **kwargs: _selector_artifact("o1", "o1.2")
|
||||
with self.assertRaisesRegex(SnapshotError, "part/subassembly occurrence refs"):
|
||||
resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/assembly.step",
|
||||
"selection": {"focus": ["#o1.2.f1"]},
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
def test_render_job_rejects_mixed_focus_and_hide_selection(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
root = Path(temporary_directory).resolve()
|
||||
models = root / "models"
|
||||
models.mkdir()
|
||||
(models / "assembly.step").write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
(models / ".assembly.step.glb").write_bytes(b"glb")
|
||||
|
||||
original_ensure = snapshot_main.ensure_step_topology_artifact
|
||||
try:
|
||||
snapshot_main.ensure_step_topology_artifact = lambda *args, **kwargs: _selector_artifact(
|
||||
"o1",
|
||||
"o1.2",
|
||||
"o1.3",
|
||||
)
|
||||
with self.assertRaisesRegex(SnapshotError, "selection.focus/refs and selection.hide cannot be used"):
|
||||
resolve_render_job_packet(
|
||||
{
|
||||
"input": "models/assembly.step",
|
||||
"selection": {
|
||||
"focus": ["#o1.2"],
|
||||
"hide": ["#o1.3"],
|
||||
},
|
||||
"outputs": [{"path": "tmp/iso.png", "camera": "iso"}],
|
||||
},
|
||||
cwd=root,
|
||||
)
|
||||
finally:
|
||||
snapshot_main.ensure_step_topology_artifact = original_ensure
|
||||
|
||||
def test_snapshot_root_flags_and_job_fields_are_removed(self) -> None:
|
||||
with self.assertRaisesRegex(SnapshotError, "Unknown argument: --workspace-root"):
|
||||
parse_snapshot_args(["--workspace-root", "/tmp"])
|
||||
with self.assertRaisesRegex(SnapshotError, "Unknown argument: --root-dir"):
|
||||
parse_snapshot_args(["--root-dir", "models"])
|
||||
with self.assertRaisesRegex(SnapshotError, "no longer accept workspaceRoot or rootDir"):
|
||||
resolve_render_job_packet(
|
||||
{
|
||||
"input": "part.step",
|
||||
"workspaceRoot": "/tmp",
|
||||
"outputs": [{"path": "tmp/iso.png"}],
|
||||
},
|
||||
cwd=Path.cwd(),
|
||||
)
|
||||
|
||||
def test_timestamp_output_path_preserves_extension(self) -> None:
|
||||
self.assertEqual(
|
||||
timestamp_output_path("snapshots/review.png", "20260527T163012Z"),
|
||||
"snapshots/review_20260527T163012Z.png",
|
||||
)
|
||||
|
||||
def test_removed_daemon_flags_stay_removed(self) -> None:
|
||||
with self.assertRaisesRegex(SnapshotError, "daemon commands have been removed"):
|
||||
parse_snapshot_args(["daemon"])
|
||||
with self.assertRaisesRegex(SnapshotError, "--socket has been removed"):
|
||||
parse_snapshot_args(["--socket", "snapshot.sock"])
|
||||
|
||||
def test_runtime_routes_are_self_contained(self) -> None:
|
||||
self.assertEqual(
|
||||
resolve_snapshot_route_file("http://snapshot.local/render.html"),
|
||||
RENDER_HTML_PATH,
|
||||
)
|
||||
self.assertEqual(
|
||||
resolve_snapshot_route_file("http://snapshot.local/snapshot-render.js"),
|
||||
RUNTIME_DIR / "snapshot-render.js",
|
||||
)
|
||||
|
||||
def test_snapshot_renderer_does_not_force_chromium_single_process(self) -> None:
|
||||
captured_launch_options = {}
|
||||
|
||||
class FakePage:
|
||||
async def route(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
async def goto(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
async def wait_for_function(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
class FakeContext:
|
||||
async def new_page(self):
|
||||
return FakePage()
|
||||
|
||||
async def close(self):
|
||||
pass
|
||||
|
||||
class FakeBrowser:
|
||||
async def new_context(self, *args, **kwargs):
|
||||
return FakeContext()
|
||||
|
||||
async def close(self):
|
||||
pass
|
||||
|
||||
class FakeChromium:
|
||||
async def launch(self, **kwargs):
|
||||
captured_launch_options.update(kwargs)
|
||||
return FakeBrowser()
|
||||
|
||||
class FakePlaywright:
|
||||
def __init__(self) -> None:
|
||||
self.chromium = FakeChromium()
|
||||
|
||||
async def stop(self):
|
||||
pass
|
||||
|
||||
fake_playwright = FakePlaywright()
|
||||
|
||||
class FakeAsyncPlaywright:
|
||||
async def start(self):
|
||||
return fake_playwright
|
||||
|
||||
async_api_module = ModuleType("playwright.async_api")
|
||||
async_api_module.async_playwright = FakeAsyncPlaywright
|
||||
playwright_module = ModuleType("playwright")
|
||||
playwright_module.__path__ = []
|
||||
|
||||
original_playwright = sys.modules.get("playwright")
|
||||
original_async_api = sys.modules.get("playwright.async_api")
|
||||
try:
|
||||
sys.modules["playwright"] = playwright_module
|
||||
sys.modules["playwright.async_api"] = async_api_module
|
||||
|
||||
async def start_renderer() -> None:
|
||||
renderer = snapshot_main.BatchSnapshotRenderer()
|
||||
try:
|
||||
await renderer.start()
|
||||
finally:
|
||||
await renderer.close()
|
||||
|
||||
asyncio.run(start_renderer())
|
||||
finally:
|
||||
if original_playwright is None:
|
||||
sys.modules.pop("playwright", None)
|
||||
else:
|
||||
sys.modules["playwright"] = original_playwright
|
||||
if original_async_api is None:
|
||||
sys.modules.pop("playwright.async_api", None)
|
||||
else:
|
||||
sys.modules["playwright.async_api"] = original_async_api
|
||||
|
||||
self.assertNotIn("--single-process", captured_launch_options.get("args") or [])
|
||||
|
||||
def test_snapshot_tool_has_no_sideways_runtime_dependencies(self) -> None:
|
||||
snapshot_root = repo_path("skills/cad/scripts/snapshot")
|
||||
checked_files = [
|
||||
snapshot_root / "__main__.py",
|
||||
snapshot_root / "runtime" / "render.html",
|
||||
snapshot_root / "runtime" / "snapshot-render.js",
|
||||
]
|
||||
forbidden = (
|
||||
"packages/cadjs",
|
||||
"skills/cad-viewer",
|
||||
"/node_modules/",
|
||||
"\\node_modules\\",
|
||||
"CADJS_NODE_MODULES_ROOT",
|
||||
)
|
||||
for checked_file in checked_files:
|
||||
text = checked_file.read_text(encoding="utf-8")
|
||||
for token in forbidden:
|
||||
self.assertNotIn(token, text, f"{checked_file} should not reference {token}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user