Files
tracer-cloud--opensre/tests/synthetic/rds_postgres/test_suite_axis2.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

139 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Axis 2 adversarial test suite for synthetic RDS RCA scenarios.
Differences from test_suite.py (Axis 1):
1. Uses SelectiveGrafanaBackend instead of FixtureGrafanaBackend.
- The backend records every metric_name the agent requested via
query_timeseries (audit trail).
- It returns only the metric series matching the requested metric_name
(case-insensitive substring), forcing the agent to query specifically
rather than receiving all data by default.
2. Asserts two additional dimensions from ReasoningScore:
- ruling_out_ok: the agent's output contains all ruling_out_keywords
declared in the scenario's answer.yml (proves it dismissed alternatives).
- queries_ok: the agent requested all required_queries metric names
(proves it checked the right evidence before concluding).
3. Runs all scenarios with ruling_out_keywords or required_queries declared
(currently 011013 plus 006, 007 which have forbidden_categories but no
Axis 2 fields yet). Scenarios without Axis 2 fields are skipped from
the Axis 2-specific assertions but still validate category + keywords.
Run with:
pytest -m axis2 tests/synthetic/rds_postgres/test_suite_axis2.py -v
"""
from __future__ import annotations
import pytest
from config.config import has_credentials_for_active_llm_provider
from tests.synthetic.mock_grafana_backend.selective_backend import SelectiveGrafanaBackend
from tests.synthetic.rds_postgres.run_suite import run_scenario
from tests.synthetic.rds_postgres.scenario_loader import load_all_scenarios
from tests.synthetic.rds_postgres.scoring import score_reasoning
_ALL_SCENARIOS = load_all_scenarios()
_LLM_ATTEMPTS = 2
_SYNTHETIC_SKIP_LLM = (
"SKIPPED: missing API key for LLM_PROVIDER "
"(suite uses mock Grafana/RDS backends; configure the key for your selected provider)"
)
# Difficulty threshold above which the LLM is expected to struggle.
# Failures at or above this difficulty are the gap signal —
# they should not gate CI (strict=False xfail).
_XFAIL_DIFFICULTY = 3
def _axis2_scenarios() -> list:
"""Return pytest params for all Axis 2 scenarios.
Scenarios at difficulty >= _XFAIL_DIFFICULTY are wrapped with
pytest.mark.xfail(strict=False) so that:
- Failures keep CI green (expected, part of the gap metric).
- Passes are recorded as bonuses (xpass).
"""
params = []
for f in _ALL_SCENARIOS:
if not (f.answer_key.ruling_out_keywords or f.answer_key.required_queries):
continue
if f.metadata.scenario_difficulty >= _XFAIL_DIFFICULTY:
params.append(
pytest.param(
f,
id=f.scenario_id,
marks=pytest.mark.xfail(
strict=False,
reason=(
f"difficulty={f.metadata.scenario_difficulty}: "
"expected to challenge real LLMs — failure is the gap signal"
),
),
)
)
else:
params.append(pytest.param(f, id=f.scenario_id))
return params
def _should_assert_trajectory(fixture, actual_category: str) -> bool:
"""Keep exact trajectory assertions for the lower-difficulty tiers only."""
return fixture.metadata.scenario_difficulty < _XFAIL_DIFFICULTY and actual_category != "healthy"
def _run_axis2_scenario_test(fixture) -> None:
"""Run Axis 2 scenario with real LLM, SelectiveGrafanaBackend, and assert reasoning."""
if not has_credentials_for_active_llm_provider():
pytest.skip(_SYNTHETIC_SKIP_LLM)
failures: list[str] = []
for attempt in range(1, _LLM_ATTEMPTS + 1):
backend = SelectiveGrafanaBackend(fixture)
final_state, score = run_scenario(fixture, use_mock_grafana=True, grafana_backend=backend)
reasoning = score_reasoning(fixture, final_state, queried_metrics=backend.queried_metrics)
try:
assert final_state["root_cause"], f"{fixture.scenario_id}: agent produced no root_cause"
assert score.passed is True, (
f"{fixture.scenario_id} FAILED: {score.failure_reason}\n"
f" actual_category={score.actual_category!r} "
f" missing_keywords={score.missing_keywords}"
)
if (
_should_assert_trajectory(fixture, score.actual_category)
and score.trajectory is not None
):
assert score.trajectory.sequencing_ok, (
f"{fixture.scenario_id} TRAJECTORY FAIL: "
f"sequencing={score.trajectory.sequencing_ok} "
f"calibration={score.trajectory.calibration_ok}\n"
f" expected={score.trajectory.expected_sequence}\n"
f" actual={score.trajectory.actual_sequence}"
)
if reasoning is not None:
# Keep query coverage as the hard gate. The ruling-out score is
# still computed and surfaced in reports, but exact phrasing in
# free-form model outputs is too variable to be CI-stable.
assert reasoning.queries_ok, (
f"{fixture.scenario_id} REASONING FAIL — agent never queried these metrics: "
f"{reasoning.missing_queries}\n"
f" queried_metrics audit log: {backend.unique_queried_metrics}"
)
return
except AssertionError as exc:
failures.append(f"attempt {attempt}/{_LLM_ATTEMPTS}: {exc}")
raise AssertionError("\n\n".join(failures))
@pytest.mark.axis2
@pytest.mark.parametrize("fixture", _axis2_scenarios(), ids=lambda f: f.scenario_id)
def test_axis2_scenario(fixture) -> None:
"""Axis 2 adversarial test: selective backend + reasoning quality checks."""
_run_axis2_scenario_test(fixture)