| """Shared pytest fixtures and CLI flags."""
|
|
|
| import json
|
| import os
|
| import pathlib
|
| from typing import Any
|
|
|
| import pytest
|
|
|
| REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
|
| DEFAULT_MASTER_WORKFLOW = pathlib.Path(
|
| os.environ.get(
|
| "LTX23_MASTER_WORKFLOW",
|
| pathlib.Path.home()
|
| / "Projects/comfyui/user/default/workflows"
|
| / "1. LTX 2.3 All-In-One 260406-05.json",
|
| )
|
| )
|
|
|
|
|
| def pytest_addoption(parser: pytest.Parser) -> None:
|
| parser.addoption("--gpu", action="store_true", help="Run L4 GPU smoke tests.")
|
| parser.addoption(
|
| "--comfy-real",
|
| action="store_true",
|
| help="Use bundled ComfyUI for L2 graph validation (slower).",
|
| )
|
|
|
|
|
| def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
| if not config.getoption("--gpu"):
|
| skip_gpu = pytest.mark.skip(reason="GPU smoke tests skipped (use --gpu)")
|
| for item in items:
|
| if "gpu" in item.keywords:
|
| item.add_marker(skip_gpu)
|
|
|
|
|
| @pytest.fixture(scope="session")
|
| def master_workflow() -> dict[str, Any]:
|
| """The full LTX 2.3 All-In-One workflow JSON (loaded from user's ComfyUI)."""
|
| if not DEFAULT_MASTER_WORKFLOW.exists():
|
| pytest.skip(
|
| f"Master workflow not found at {DEFAULT_MASTER_WORKFLOW}. "
|
| "Set LTX23_MASTER_WORKFLOW env var to its path."
|
| )
|
| return json.loads(DEFAULT_MASTER_WORKFLOW.read_text())
|
|
|
|
|
| @pytest.fixture
|
| def canonical_inputs() -> dict[str, dict[str, Any]]:
|
| """Known-good Gradio input dicts per mode (used by L1/L2 tests)."""
|
| return {
|
| "t2v": {
|
| "prompt": "a tiger walking through a misty forest at dawn, cinematic",
|
| "negative_prompt": "",
|
| "preset": "balanced",
|
| "width": 512,
|
| "height": 768,
|
| "frames": 81,
|
| "fps": 24,
|
| "seed": 42,
|
| "camera_lora": "none",
|
| "camera_strength": 0.8,
|
| "detailer_on": False,
|
| "detailer_strength": 0.5,
|
| },
|
| "i2v": {
|
| "prompt": "the subject turns toward the camera and smiles",
|
| "image": "/tmp/portrait.png",
|
| "preset": "balanced",
|
| "width": 512,
|
| "height": 768,
|
| "frames": 81,
|
| "fps": 24,
|
| "seed": 42,
|
| "camera_lora": "none",
|
| "camera_strength": 0.8,
|
| "detailer_on": True,
|
| "detailer_strength": 0.5,
|
| "ic_lora": "union",
|
| "ic_strength": 0.5,
|
| "pose_on": False,
|
| },
|
| "a2v": {
|
| "prompt": "a dancer moves to the beat in a neon-lit studio",
|
| "audio": "/tmp/track.wav",
|
| "preset": "balanced",
|
| "width": 512,
|
| "height": 768,
|
| "frames": 81,
|
| "fps": 24,
|
| "seed": 42,
|
| "audio_cfg": 7.0,
|
| },
|
| "lipsync": {
|
| "prompt": "the person speaks the audio with natural mouth movement",
|
| "image": "/tmp/portrait.png",
|
| "audio": "/tmp/speech.wav",
|
| "preset": "balanced",
|
| "image_strength": 0.7,
|
| "frames": 81,
|
| "fps": 24,
|
| "seed": 42,
|
| },
|
| "keyframe": {
|
| "prompt": "smooth transition between the two frames",
|
| "first_frame": "/tmp/start.png",
|
| "last_frame": "/tmp/end.png",
|
| "preset": "balanced",
|
| "frames": 81,
|
| "fps": 24,
|
| "seed": 42,
|
| },
|
| "style": {
|
| "prompt": "in the style of a renaissance oil painting",
|
| "input_video": "/tmp/source.mp4",
|
| "preset": "balanced",
|
| "frames": 81,
|
| "fps": 24,
|
| "seed": 42,
|
| "ic_lora": "motion-track",
|
| "ic_strength": 0.5,
|
| },
|
| }
|
|
|
|
|
| @pytest.fixture
|
| def fake_hf_cache(tmp_path: pathlib.Path) -> pathlib.Path:
|
| """A fake ~/.cache/huggingface/hub layout with placeholder files."""
|
| hub = tmp_path / "huggingface" / "hub"
|
| layouts = {
|
| "models--Lightricks--LTX-2.3": [
|
| "ltx-2.3-22b-distilled.safetensors",
|
| "ltx-2.3-spatial-upscaler-x2-1.0.safetensors",
|
| "ltx-2.3-22b-distilled-lora-384.safetensors",
|
| ],
|
| "models--google--gemma-3-12b-it-qat-q4_0-unquantized": [
|
| "model-00001-of-00005.safetensors",
|
| "model-00002-of-00005.safetensors",
|
| "model-00003-of-00005.safetensors",
|
| "model-00004-of-00005.safetensors",
|
| "model-00005-of-00005.safetensors",
|
| "model.safetensors.index.json",
|
| "tokenizer.model",
|
| "preprocessor_config.json",
|
| ],
|
| "models--Kijai--LTX2.3_comfy": [
|
| "LTX23_video_vae_bf16.safetensors",
|
| "LTX23_audio_vae_bf16.safetensors",
|
| ],
|
| }
|
| for repo, files in layouts.items():
|
| snapshot_dir = hub / repo / "snapshots" / "deadbeef"
|
| snapshot_dir.mkdir(parents=True, exist_ok=True)
|
| for filename in files:
|
| (snapshot_dir / filename).write_text("")
|
| return hub
|
|
|