项目文件夹

文件
wehub-resource-sync eec33d25b2
Build Wheel / build (3.11) (push) Failing after 1s
Build Wheel / build (3.12) (push) Failing after 0s
pre-commit / pre-commit (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:29:08 +08:00

384 行
15 KiB
Python

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