| """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 |
|
|