fec4c09ee0
CI / test (push) Successful in 8s
doc_parser_skill: - New: verify_flowchart.py (flowchart validation) - Updated: LLM.py (multi-provider: DeepSeek + DashScope) - Updated: image_parser.py (logic tree support, external prompts) - Updated: SKILL.md, prompts/image_prompt.md conflict_detection_skill: - Updated: LLM.py (multi-provider sync) - Updated: detect_conflicts.py (logic tree text conversion) ir_generation_skill: - Replaced old scripts/LLM.py + ir_generator.py with standalone project - New: main.py, config.py, step1-3_*.py, ensemble_merge.py - New: prompts/, tests/ subdirectories tests: - New: acceptance/ test suite with schema validation - Fixed: conftest no longer globally skips non-acceptance tests - Updated: test_sample.py for new ir_generation structure Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
187 lines
5.9 KiB
Python
187 lines
5.9 KiB
Python
"""Pytest configuration and shared fixtures for QE acceptance tests.
|
|
|
|
Usage::
|
|
|
|
pytest tests/acceptance/ -v --run-acceptance [--acceptance-runs=3]
|
|
|
|
Environment variables:
|
|
DASHSCOPE_API_KEY — LLM API key (required for Layers B/C)
|
|
TEST_IR_PATH — path to IR JSON to validate (default: ir_final.json sample)
|
|
TEST_PARSED_PATH — path to _parsed.json or _updated.json for coverage analysis
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
# ── Path setup ──────────────────────────────────────────────────────────────
|
|
|
|
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
|
|
|
|
def _skill_path(skill_name: str) -> str:
|
|
return str(_PROJECT_ROOT / "skills" / skill_name / "scripts")
|
|
|
|
|
|
# ── pytest configuration ────────────────────────────────────────────────────
|
|
|
|
|
|
def pytest_addoption(parser):
|
|
parser.addoption(
|
|
"--run-acceptance",
|
|
action="store_true",
|
|
default=False,
|
|
help="Run QE acceptance tests (requires DASHSCOPE_API_KEY)",
|
|
)
|
|
parser.addoption(
|
|
"--acceptance-runs",
|
|
type=int,
|
|
default=1,
|
|
help="Number of IR generation runs for Layer B stability testing (default: 1 = skip)",
|
|
)
|
|
parser.addoption(
|
|
"--ir-path",
|
|
type=str,
|
|
default=None,
|
|
help="Path to IR JSON file to validate",
|
|
)
|
|
parser.addoption(
|
|
"--parsed-path",
|
|
type=str,
|
|
default=None,
|
|
help="Path to _parsed.json or _updated.json for coverage analysis",
|
|
)
|
|
|
|
|
|
def pytest_configure(config):
|
|
config.addinivalue_line(
|
|
"markers",
|
|
"acceptance: QE acceptance test (requires --run-acceptance flag and DASHSCOPE_API_KEY)",
|
|
)
|
|
|
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
acceptance_dir = str(_PROJECT_ROOT / "tests" / "acceptance")
|
|
acceptance_items = [i for i in items if str(i.fspath).startswith(acceptance_dir)]
|
|
non_acceptance_items = [i for i in items if not str(i.fspath).startswith(acceptance_dir)]
|
|
|
|
if not config.getoption("--run-acceptance"):
|
|
skip_msg = pytest.mark.skip(reason="Need --run-acceptance flag to run")
|
|
for item in acceptance_items:
|
|
item.add_marker(skip_msg)
|
|
# Don't skip non-acceptance tests
|
|
return
|
|
|
|
if not os.environ.get("DASHSCOPE_API_KEY"):
|
|
skip_msg = pytest.mark.skip(reason="DASHSCOPE_API_KEY not set")
|
|
for item in acceptance_items:
|
|
item.add_marker(skip_msg)
|
|
|
|
|
|
# ── Shared fixtures ─────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def project_root() -> Path:
|
|
return _PROJECT_ROOT
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def ir_path(request) -> str:
|
|
"""Path to the IR JSON file under test."""
|
|
path = (
|
|
request.config.getoption("--ir-path")
|
|
or os.environ.get("TEST_IR_PATH")
|
|
or str(
|
|
Path.home()
|
|
/ ".openclaw/workspace/skills/doc_parser_skill/output/ir_final.json"
|
|
)
|
|
)
|
|
if not os.path.exists(path):
|
|
pytest.skip(f"IR file not found: {path}")
|
|
return path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def ir_data(ir_path: str) -> dict:
|
|
"""Load the IR JSON data."""
|
|
with open(ir_path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def parsed_path(request) -> str | None:
|
|
"""Path to the corresponding _parsed.json or _updated.json."""
|
|
path = (
|
|
request.config.getoption("--parsed-path")
|
|
or os.environ.get("TEST_PARSED_PATH")
|
|
or str(
|
|
_PROJECT_ROOT
|
|
/ "skills/ir_generation_skill/车机娱乐系统禁止功能文档_精简_updated.json"
|
|
)
|
|
)
|
|
if os.path.exists(path):
|
|
return path
|
|
return None
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def parsed_data(parsed_path: str | None) -> dict | None:
|
|
"""Load the parsed document JSON for coverage analysis."""
|
|
if parsed_path is None:
|
|
return None
|
|
with open(parsed_path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def llm_client():
|
|
"""Create an LLMClient instance for acceptance tests.
|
|
|
|
Uses the DashScope-compatible LLMClient from the project.
|
|
"""
|
|
sys.path.insert(0, _skill_path("doc_parser_skill"))
|
|
from LLM import LLMClient
|
|
|
|
return LLMClient()
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def acceptance_runs(request) -> int:
|
|
return request.config.getoption("--acceptance-runs", default=1)
|
|
|
|
|
|
# ── Pipeline runner ─────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def run_ir_pipeline():
|
|
"""Return a callable that runs the IR generation pipeline on a parsed JSON.
|
|
|
|
Usage::
|
|
|
|
ir_data, ir_path = run_ir_pipeline(parsed_json_path, output_dir)
|
|
"""
|
|
sys.path.insert(0, _skill_path("ir_generation_skill"))
|
|
from ir_generator import generate_ir
|
|
|
|
def _run(parsed_path: str, output_dir: str | None = None) -> tuple[dict, str]:
|
|
"""Run IR generation and return (ir_data, ir_path)."""
|
|
out = output_dir or tempfile.mkdtemp(prefix="qe_acceptance_")
|
|
result = generate_ir(parsed_path, out, dry_run=False)
|
|
ir_list = result.get("ir", [])
|
|
ir_path = result.get("path", "")
|
|
# ir_generator produces a list; wrap to match rich format expectations
|
|
# for schema validation we accept both formats
|
|
return ir_list, ir_path
|
|
|
|
return _run
|