""" Shared fixtures, helpers, and path constants for tests/examples/. """ import json import os import re import shlex import subprocess import sys import tempfile from collections import defaultdict from collections.abc import Callable from pathlib import Path from typing import Any, NamedTuple, cast import pytest import torch from safetensors.torch import save_file # --------------------------------------------------------------------------- # Path constants and fixtures # --------------------------------------------------------------------------- REPO_ROOT = Path(__file__).resolve().parents[2] EXAMPLES = REPO_ROOT / "examples" # Use Python tempfile instead of pytest's tmp_path_factory because # OUTPUT_DIR is needed in test collection time, but tmp_path_factory is only available in test running time. # It is needed during test collection because extract_readme_snippets replaces LoRA path with a generated one under OUTPUT_DIR, # and extract_readme_snippets is called at collection time to generate separate test cases for each README code block. OUTPUT_DIR = ( REPO_ROOT / prefix if (prefix := os.environ.get("OUTPUT_DIR")) else Path(tempfile.mkdtemp(prefix="vllm_omni_test_examples_")) ) # --------------------------------------------------------------------------- # Code snippet extraction and asset file helpers # --------------------------------------------------------------------------- # parameters: language, code, h2_title ReadmeSnippetExtractionSkipPredicate = Callable[[str, str, str], tuple[bool, str]] class ReadmeSnippet(NamedTuple): language: str code: str h2_title: str index_in_section: int output_file_path: Path | None = None skip: tuple[bool, str] = (False, "") @property def test_id(self) -> str: return f"{ReadmeSnippet._slug(self.h2_title)}_{self.index_in_section:03d}" @staticmethod def extract_readme_snippets( readme_path: Path, skipif: ReadmeSnippetExtractionSkipPredicate | None = None, ) -> list["ReadmeSnippet"]: import mistune markdown = mistune.create_markdown(renderer="ast") tokens = markdown(readme_path.read_text(encoding="utf-8")) tokens = cast(list[dict[str, Any]], tokens) # mistune's AST renderer always produces a list, not a str h2_title = "" section_counts: defaultdict[str, int] = defaultdict(int) snippets: list[ReadmeSnippet] = [] for token in tokens: token_type = token.get("type") if token_type == "heading": level = (token.get("attrs") or {}).get("level") title = ReadmeSnippet._heading_text(token) if level == 2: h2_title = title continue if token_type != "block_code": continue try: info = token.get("attrs").get("info") # type: ignore[reportOptionalMemberAccess] language = info.strip().split()[0].lower() # type: ignore[reportOptionalMemberAccess] # Common shell aliases to "bash" in several markdown renderers. if language in {"shell", "sh", "ksh", "zsh"}: language = "bash" if language not in {"bash", "python"}: continue except AttributeError: # The fence is missing explicit language info; skip it. continue key = h2_title section_counts[key] += 1 code = token.get("raw", "") output_file_path = None if language == "bash": argv = ReadmeSnippet._normalize_bash_command(code, Path(readme_path.parent)) code = shlex.join(argv) output_file_path = ReadmeSnippet._output_file_path_from_argv(argv) if skipif is not None: skip_config = skipif(language, code, h2_title) else: skip_config = (False, "") snippet = ReadmeSnippet( language=language, code=code, h2_title=h2_title, index_in_section=section_counts[key], output_file_path=output_file_path, skip=skip_config, ) snippets.append(snippet) return snippets @staticmethod def _normalize_bash_command(command: str, readme_dir: Path) -> list[str]: line_joined_command = re.sub(r"\\\s*\n", " ", command).strip() argv = shlex.split(line_joined_command, comments=True) assert argv, "README bash fence produced an empty command" # Normalize python directory and example script location if argv[0] in {"python", "python3"}: argv[0] = sys.executable if len(argv) > 1 and argv[1].endswith(".py"): script_arg = argv[1] script_path = Path(script_arg) if script_path.is_absolute(): resolved_script = script_path else: # Take the file name only, and append script_dir to its front resolved_script = readme_dir / script_path.name assert resolved_script.exists(), ( f"README bash snippet references a script that does not exist: {script_arg} (resolved to {resolved_script})" ) argv[1] = str(resolved_script) # Normalize LoRA adapter path and ensure README LoRA assets exist. try: lora_arg_idx = argv.index("--lora-path") # Raise ValueError if not found assert len(argv) > lora_arg_idx + 1, "README bash snippet uses --lora-path without a following value" lora_dir = OUTPUT_DIR / "lora" adapter_model = lora_dir / "adapter_model.safetensors" adapter_config = lora_dir / "adapter_config.json" if not adapter_model.exists() or not adapter_config.exists(): write_zimage_lora(lora_dir, v_scale=8.0) argv[lora_arg_idx + 1] = str(lora_dir) except ValueError: pass return argv @staticmethod def _output_file_path_from_argv(argv: list[str]) -> Path | None: if "--output" not in argv: return None output_param_idx = argv.index("--output") assert len(argv) > output_param_idx + 1, "README bash snippet uses --output without a following value" output_arg = argv[output_param_idx + 1] return Path(output_arg) @staticmethod def _slug(text: str) -> str: return "".join(ch.lower() if ch.isalnum() else "_" for ch in text).strip("_") @staticmethod def _heading_text(token: dict) -> str: return "".join(child.get("raw", "") for child in token.get("children", [])).strip() # [TODO] Duplicate `_write_zimage_lora` in tests/e2e/online_serving/test_images_generations_lora.py. Combine these helpers and tests/e2e/offline_inference/test_diffusion_lora.py to test/utils later def write_zimage_lora(adapter_dir: Path, *, q_scale: float = 0.0, k_scale: float = 0.0, v_scale: float = 0.0): adapter_dir.mkdir(parents=True, exist_ok=True) # Z-Image transformer uses dim=3840 by default. dim = 3840 module_name = "transformer.layers.0.attention.to_qkv" rank = 1 lora_a = torch.zeros((rank, dim), dtype=torch.float32) lora_a[0, 0] = 1.0 # QKVParallelLinear packs (Q, K, V) => out dim is 3 * dim (tp=1). lora_b = torch.zeros((3 * dim, rank), dtype=torch.float32) if q_scale: lora_b[:dim, 0] = q_scale if k_scale: lora_b[dim : 2 * dim, 0] = k_scale if v_scale: lora_b[2 * dim :, 0] = v_scale save_file( { f"base_model.model.{module_name}.lora_A.weight": lora_a, f"base_model.model.{module_name}.lora_B.weight": lora_b, }, str(adapter_dir / "adapter_model.safetensors"), ) (adapter_dir / "adapter_config.json").write_text( json.dumps( { "r": rank, "lora_alpha": rank, "target_modules": [module_name], } ), encoding="utf-8", ) # --------------------------------------------------------------------------- # Code runner and subprocess helpers # --------------------------------------------------------------------------- class ExampleRunResult(NamedTuple): run_dir: Path assets: list[Path] class ExampleRunner: """Run extracted README snippets and return generated assets. The output materials are organized in a three-level directory structure: - Set at init: `self.output_root` for all tests (from env OUTPUT_DIR) - Set at `self.run(...)`: `output_subfolder` for a specific example page (e.g., `example_offline_t2i`) - Generated by `extract_readme_snippets`: `snippet.test_id` for a specific code block (matching H2 titles, e.g., `basic_usage_001`) """ IMAGE_SUFFIXES = {".png", ".jpg", ".jpeg", ".webp"} def __init__(self, output_root: Path) -> None: self.output_root = output_root def run( self, snippet: ReadmeSnippet, *, output_subfolder: Path = Path("."), env: dict[str, str] | None = None ) -> ExampleRunResult: run_dir = self.output_root / output_subfolder / snippet.test_id run_dir.mkdir(parents=True, exist_ok=True) if snippet.language == "python": assets = self._run_python_snippet(snippet, run_dir, env) return ExampleRunResult(run_dir=run_dir, assets=assets) if snippet.language == "bash": asset = self._run_bash_snippet(snippet, run_dir, env) return ExampleRunResult(run_dir=run_dir, assets=[asset]) raise AssertionError(f"Unsupported snippet language: {snippet.language}") def _run_python_snippet( self, snippet: ReadmeSnippet, run_dir: Path, env: dict[str, str] | None = None ) -> list[Path]: # Saving the script to a temporary file and `run_cmd` it. # Not using `exec(snippet.code)` because the output is lost. script_path = run_dir / "snippet.py" script_path.write_text(snippet.code, encoding="utf-8") before = self._collect_images(run_dir) run_cmd([sys.executable, str(script_path)], cwd=run_dir, env=env) after = self._collect_images(run_dir) assets = sorted(after - before) return assets def _run_bash_snippet(self, snippet: ReadmeSnippet, run_dir: Path, env: dict[str, str] | None = None) -> Path: run_cmd(snippet.code, shell=True, cwd=run_dir, env=env) assert snippet.output_file_path is not None, ( f"README bash snippet is missing --output argument: {snippet.test_id}. " "The test script cannot guess the output file path." ) # If the code snippet declares a relative path for the output file, append this path to the parent output collection directory. # If the code snippet declares an absolute path (not likely but just in case), the return value resolution removes `run_dir`, also correctly pointing to this file. return run_dir / snippet.output_file_path def _collect_images(self, root: Path) -> set[Path]: return {path for path in root.rglob("*") if path.suffix.lower() in self.IMAGE_SUFFIXES} @pytest.fixture def example_runner() -> ExampleRunner: return ExampleRunner(output_root=OUTPUT_DIR) def run_cmd( command: list[str] | str, *, shell: bool = False, env: dict[str, str] | None = None, cwd: Path | str | None = None, ) -> str: """Run a command as a subprocess; assert zero exit code and return stdout. Output is fully captured and returned as a string so callers can parse it (e.g. with :func:`extract_content_after_keyword`). Use this for scripts whose printed output is part of the test assertion. """ if env is not None: env = {**os.environ.copy(), **env} result = subprocess.run(command, capture_output=True, text=True, shell=shell, env=env, cwd=cwd) if result.returncode != 0: print(f"STDERR: {result.stderr}") raise subprocess.CalledProcessError(result.returncode, command) all_output = result.stdout print(f"All output:\n{all_output}") return all_output # --------------------------------------------------------------------------- # Output validation helpers # --------------------------------------------------------------------------- def extract_content_after_keyword(keywords: str, text: str) -> str: """Return the text that follows *keywords* in *text* (regex match). Raises ``AssertionError`` if the keyword is not found, so test failures produce a clear message pointing at the missing keyword. """ matches = re.findall(rf"{keywords}\s*(.+)", text, re.DOTALL) if not matches: raise AssertionError(f"Keywords {keywords} not found in provided text output") return matches[0] def strip_trailing_audio_saved_line(text: str) -> str: """Drop trailing ``Audio saved to ...`` lines from captured client stdout. ``openai_chat_completion_client_for_multimodal_generation.py`` may print ``Chat completion output from text:`` for one choice and ``Audio saved to`` for another; :func:`extract_content_after_keyword` uses ``re.DOTALL`` and would otherwise keep the audio progress line inside the *text* segment. """ lines = text.splitlines() while lines and lines[-1].strip().startswith("Audio saved to"): lines.pop() return "\n".join(lines).strip() def strip_audio_saved_to_lines(text: str) -> str: """Remove every line starting with ``Audio saved to`` (streaming prints one per chunk). Without this, :func:`extract_content_after_keyword` with ``content:`` and ``DOTALL`` keeps those lines inside the captured text segment. """ lines = [ln for ln in text.splitlines() if not ln.strip().startswith("Audio saved to")] return "\n".join(lines).strip() def extract_last_audio_saved_path(text: str) -> str: """Return the filesystem path from the last ``Audio saved to`` line. Non-streaming output has a single line; streaming prints one path per chunk. Do not use :func:`extract_content_after_keyword` with ``Audio saved to`` for streaming: greedy ``.+`` under ``DOTALL`` concatenates every path and body into one invalid string (Linux ``File name too long`` when opening it as a path). """ last_path: str | None = None for line in text.splitlines(): stripped = line.strip() if stripped.startswith("Audio saved to"): rest = stripped[len("Audio saved to") :].strip() if rest: last_path = rest if last_path is None: raise AssertionError("'Audio saved to' line with a path not found in command output") return last_path