Files
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

519 lines
20 KiB
Python

from __future__ import annotations
import json
import textwrap
from pathlib import Path
import pytest
from config.config import has_credentials_for_active_llm_provider
from tests.synthetic.eks.run_suite import run_scenario, score_result
from tests.synthetic.eks.scenario_loader import (
SUITE_DIR,
load_all_scenarios,
load_scenario,
)
from tests.synthetic.k8s_schemas import VALID_K8S_EVIDENCE_SOURCES
from tests.synthetic.mock_datadog_backend.backend import FixtureDatadogBackend
from tests.synthetic.mock_eks_backend.backend import FixtureEKSBackend
# Synthetic E2E uses fixture EKS/Datadog backends; many tool "calls" hit mocks, not real APIs.
# This gate only reflects whether the *LLM* can authenticate (the reason we skip when keys are absent).
_SYNTHETIC_SKIP_LLM = (
"SKIPPED: missing API key for LLM_PROVIDER "
"(suite uses mock EKS/Datadog backends; configure the key for your selected provider)"
)
# ---------------------------------------------------------------------------
# Loader and fixture validation
# ---------------------------------------------------------------------------
def test_load_all_scenarios_reads_benchmark_cases() -> None:
fixtures = load_all_scenarios()
scenario_ids = [fixture.scenario_id for fixture in fixtures]
assert "000-healthy" in scenario_ids
def test_scenario_metadata_is_valid() -> None:
fixtures = load_all_scenarios()
for fixture in fixtures:
meta = fixture.metadata
assert meta.schema_version, f"{fixture.scenario_id}: schema_version must be set"
assert meta.engine, f"{fixture.scenario_id}: engine must be set"
assert meta.cluster_name, f"{fixture.scenario_id}: cluster_name must be set"
assert meta.namespace, f"{fixture.scenario_id}: namespace must be set"
assert meta.workload_type, f"{fixture.scenario_id}: workload_type must be set"
assert meta.workload_name, f"{fixture.scenario_id}: workload_name must be set"
assert meta.failure_mode, f"{fixture.scenario_id}: failure_mode must be set"
assert meta.region, f"{fixture.scenario_id}: region must be set"
assert meta.available_evidence, (
f"{fixture.scenario_id}: available_evidence must not be empty"
)
unknown = set(meta.available_evidence) - VALID_K8S_EVIDENCE_SOURCES
assert not unknown, f"{fixture.scenario_id}: unknown evidence sources {unknown}"
def test_scenario_evidence_matches_available_evidence() -> None:
fixtures = load_all_scenarios()
for fixture in fixtures:
evidence_dict = fixture.evidence.as_dict()
assert set(evidence_dict.keys()) == set(fixture.metadata.available_evidence), (
f"{fixture.scenario_id}: evidence keys {set(evidence_dict.keys())} "
f"do not match available_evidence {fixture.metadata.available_evidence}"
)
# ---------------------------------------------------------------------------
# Mock backend shape tests
# ---------------------------------------------------------------------------
class TestMockBackendShapes:
"""Verify each mock backend method returns the exact envelope the real tool would."""
@pytest.fixture
def placeholder(self):
return load_scenario(SUITE_DIR / "000-healthy")
def test_eks_list_pods_shape(self, placeholder) -> None:
backend = FixtureEKSBackend(placeholder)
result = backend.list_pods(cluster_name="override", namespace="override-ns")
assert result["source"] == "eks"
assert result["available"] is True
assert result["error"] is None
assert "total_pods" in result
assert "pods" in result
assert "failing_pods" in result
assert "high_restart_pods" in result
assert result["cluster_name"] == "override"
assert result["namespace"] == "override-ns"
def test_eks_list_pods_falls_back_to_metadata(self, placeholder) -> None:
backend = FixtureEKSBackend(placeholder)
result = backend.list_pods()
assert result["cluster_name"] == placeholder.metadata.cluster_name
assert result["namespace"] == placeholder.metadata.namespace
def test_eks_get_events_shape(self, placeholder) -> None:
backend = FixtureEKSBackend(placeholder)
result = backend.get_events()
assert result["source"] == "eks"
assert result["available"] is True
assert result["error"] is None
assert "warning_events" in result
assert "total_warning_count" in result
assert result["total_warning_count"] == len(result["warning_events"])
def test_eks_list_deployments_shape(self, placeholder) -> None:
backend = FixtureEKSBackend(placeholder)
result = backend.list_deployments()
assert result["source"] == "eks"
assert result["available"] is True
assert "deployments" in result
assert "degraded_deployments" in result
assert "total_deployments" in result
for deployment in result["deployments"]:
for field in (
"name",
"namespace",
"desired",
"ready",
"available",
"unavailable",
"degraded",
):
assert field in deployment, f"missing field {field}"
def test_eks_node_health_shape(self, placeholder) -> None:
backend = FixtureEKSBackend(placeholder)
result = backend.get_node_health()
assert result["source"] == "eks"
assert result["available"] is True
assert "nodes" in result
assert "not_ready_count" in result
assert "total_nodes" in result
assert result["not_ready_count"] == 0
def test_eks_missing_evidence_raises(self, placeholder) -> None:
"""Calling a method whose evidence source wasn't declared raises ValueError."""
backend = FixtureEKSBackend(placeholder)
# The placeholder deliberately omits eks_pod_logs; calling get_pod_logs must fail.
assert "eks_pod_logs" not in placeholder.metadata.available_evidence
with pytest.raises(ValueError, match="eks_pod_logs"):
backend.get_pod_logs(pod_name="payments-api-7f9dd-x7gr9")
def test_datadog_query_logs_shape(self, placeholder) -> None:
backend = FixtureDatadogBackend(placeholder)
result = backend.query_logs(query="service:payments-api")
assert result["source"] == "datadog_logs"
assert result["available"] is True
assert "logs" in result
assert "error_logs" in result
assert result["query"] == "service:payments-api"
def test_datadog_query_monitors_shape(self, placeholder) -> None:
backend = FixtureDatadogBackend(placeholder)
result = backend.query_monitors()
assert result["source"] == "datadog_monitors"
assert result["available"] is True
assert "monitors" in result
assert "total" in result
# ---------------------------------------------------------------------------
# Scorer unit tests (no agent run required)
# ---------------------------------------------------------------------------
class TestScorer:
"""Feed canned final_state dicts into the scorer and verify the result."""
@pytest.fixture
def placeholder(self):
return load_scenario(SUITE_DIR / "000-healthy")
def test_matching_root_cause_passes(self, placeholder) -> None:
final_state = {
"root_cause": "The Kubernetes workload is operating within normal parameters. "
"No failure detected across pods, events, deployments, or monitors.",
"root_cause_category": "healthy",
"validated_claims": [],
"non_validated_claims": [],
"causal_chain": [],
"evidence": {},
"executed_hypotheses": [],
"investigation_loop_count": 0,
}
score = score_result(placeholder, final_state)
assert score.passed is True, score.failure_reason
assert score.actual_category == "healthy"
assert not score.missing_keywords
def test_wrong_category_fails(self, placeholder) -> None:
final_state = {
"root_cause": "Some narrative.",
"root_cause_category": "crashloop_backoff",
"validated_claims": [],
"non_validated_claims": [],
"causal_chain": [],
"evidence": {},
"executed_hypotheses": [],
"investigation_loop_count": 0,
}
score = score_result(placeholder, final_state)
assert score.passed is False
assert "wrong category" in score.failure_reason
def test_missing_keyword_fails(self, placeholder) -> None:
final_state = {
"root_cause": "nothing important to report",
"root_cause_category": "healthy",
"validated_claims": [],
"non_validated_claims": [],
"causal_chain": [],
"evidence": {},
"executed_hypotheses": [],
"investigation_loop_count": 0,
}
score = score_result(placeholder, final_state)
assert score.passed is False
assert "missing required keywords" in score.failure_reason
# ---------------------------------------------------------------------------
# Parametrized LLM runs — gated behind scenarios existing at each difficulty level.
# The placeholder 000-healthy uses scenario_difficulty: 0, so these collections
# are empty until scenarios #261+ land with real difficulty-tiered content.
# ---------------------------------------------------------------------------
_ALL_SCENARIOS = load_all_scenarios()
_LLM_ATTEMPTS = 2
def _by_difficulty(level: int) -> list:
return [f for f in _ALL_SCENARIOS if f.metadata.scenario_difficulty == level]
def _should_assert_trajectory(fixture, actual_category: str) -> bool:
"""Keep trajectory assertions for lower-difficulty scenarios only.
The higher-difficulty suites intentionally exercise ambiguous prompts and
alternative evidence paths. In practice, real LLMs can still land on the
correct diagnosis while taking a different-but-reasonable route, so we only
gate trajectory exactness for the lower-difficulty benchmark tiers.
"""
return fixture.metadata.scenario_difficulty <= 2 and actual_category != "healthy"
def _run_scenario_test(fixture) -> None:
"""Run scenario with real LLM and mock backends, then assert scoring."""
if not has_credentials_for_active_llm_provider():
pytest.skip(_SYNTHETIC_SKIP_LLM)
failures: list[str] = []
for attempt in range(1, _LLM_ATTEMPTS + 1):
final_state, score = run_scenario(fixture, use_mock_backends=True)
try:
assert final_state["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}"
)
return
except AssertionError as exc:
failures.append(f"attempt {attempt}/{_LLM_ATTEMPTS}: {exc}")
raise AssertionError("\n\n".join(failures))
_LEVEL1_SCENARIOS = _by_difficulty(1)
_LEVEL2_SCENARIOS = _by_difficulty(2)
_LEVEL3_SCENARIOS = _by_difficulty(3)
_LEVEL4_SCENARIOS = _by_difficulty(4)
@pytest.mark.synthetic
@pytest.mark.skipif(not _LEVEL1_SCENARIOS, reason="no Level 1 K8s scenarios yet")
@pytest.mark.parametrize(
"fixture", _LEVEL1_SCENARIOS or [None], ids=lambda f: f.scenario_id if f else "none"
)
def test_level1_scenario(fixture) -> None:
"""Level 1 — single dominant signal, all evidence consistent."""
_run_scenario_test(fixture)
@pytest.mark.synthetic
@pytest.mark.skipif(not _LEVEL2_SCENARIOS, reason="no Level 2 K8s scenarios yet")
@pytest.mark.parametrize(
"fixture", _LEVEL2_SCENARIOS or [None], ids=lambda f: f.scenario_id if f else "none"
)
def test_level2_scenario(fixture) -> None:
"""Level 2 — one confounder present, second evidence source needed to rule it out."""
_run_scenario_test(fixture)
@pytest.mark.synthetic
@pytest.mark.skipif(not _LEVEL3_SCENARIOS, reason="no Level 3 K8s scenarios yet")
@pytest.mark.parametrize(
"fixture", _LEVEL3_SCENARIOS or [None], ids=lambda f: f.scenario_id if f else "none"
)
def test_level3_scenario(fixture) -> None:
"""Level 3 — absent or indirect evidence, key signal missing."""
_run_scenario_test(fixture)
@pytest.mark.synthetic
@pytest.mark.skipif(not _LEVEL4_SCENARIOS, reason="no Level 4 K8s scenarios yet")
@pytest.mark.parametrize(
"fixture", _LEVEL4_SCENARIOS or [None], ids=lambda f: f.scenario_id if f else "none"
)
def test_level4_scenario(fixture) -> None:
"""Level 4 — compositional fault, two failure modes causally linked."""
_run_scenario_test(fixture)
# ---------------------------------------------------------------------------
# Scenario inheritance unit tests
# ---------------------------------------------------------------------------
def _write_minimal_answer_yml(scenario_dir: Path) -> None:
(scenario_dir / "answer.yml").write_text(
textwrap.dedent("""\
root_cause_category: test_category
required_keywords:
- test_keyword
model_response: "Test model response."
""")
)
class TestScenarioInheritance:
"""Verify base-inheritance and evidence-file fallback in scenario_loader."""
def test_metadata_inherited_from_base(self) -> None:
"""Scenario with base: 000-healthy inherits metadata fields it omits."""
real_dir = SUITE_DIR / "999-test-inherit"
real_dir.mkdir(exist_ok=True)
try:
(real_dir / "scenario.yml").write_text(
textwrap.dedent("""\
base: 000-healthy
scenario_id: 999-test-inherit
failure_mode: crashloop_backoff
severity: critical
""")
)
_write_minimal_answer_yml(real_dir)
fixture = load_scenario(real_dir)
assert fixture.metadata.scenario_id == "999-test-inherit"
assert fixture.metadata.failure_mode == "crashloop_backoff"
assert fixture.metadata.severity == "critical"
assert fixture.metadata.engine == "eks"
assert fixture.metadata.cluster_name == "payments-prod-eks"
assert fixture.metadata.namespace == "payments"
assert fixture.metadata.workload_type == "deployment"
assert fixture.metadata.workload_name == "payments-api"
assert fixture.metadata.region == "us-east-1"
assert fixture.metadata.schema_version == "1.0"
assert "eks_pods" in fixture.metadata.available_evidence
finally:
for f in real_dir.iterdir():
f.unlink()
real_dir.rmdir()
def test_evidence_falls_back_to_base(self) -> None:
"""Scenario without evidence files loads them from the base."""
real_dir = SUITE_DIR / "999-test-fallback"
real_dir.mkdir(exist_ok=True)
try:
(real_dir / "scenario.yml").write_text(
textwrap.dedent("""\
base: 000-healthy
scenario_id: 999-test-fallback
failure_mode: healthy
severity: info
""")
)
_write_minimal_answer_yml(real_dir)
fixture = load_scenario(real_dir)
assert fixture.evidence.eks_pods is not None
assert fixture.evidence.eks_events is not None
assert fixture.evidence.datadog_logs is not None
assert fixture.alert["state"] == "normal"
assert "payments" in fixture.alert["title"].lower()
finally:
for f in real_dir.iterdir():
f.unlink()
real_dir.rmdir()
def test_local_evidence_overrides_base(self) -> None:
"""Scenario with its own evidence file uses it instead of the base's."""
real_dir = SUITE_DIR / "999-test-override"
real_dir.mkdir(exist_ok=True)
try:
(real_dir / "scenario.yml").write_text(
textwrap.dedent("""\
base: 000-healthy
scenario_id: 999-test-override
failure_mode: healthy
severity: info
""")
)
_write_minimal_answer_yml(real_dir)
custom_events = {
"warning_events": [
{
"namespace": "payments",
"reason": "TestEvent",
"message": "Custom test event",
"type": "Warning",
"count": 1,
"involved_object": "Pod/custom-test",
"first_time": "2026-04-01T00:00:00Z",
"last_time": "2026-04-01T00:00:00Z",
}
]
}
(real_dir / "eks_events.json").write_text(json.dumps(custom_events))
fixture = load_scenario(real_dir)
assert fixture.evidence.eks_events is not None
assert len(fixture.evidence.eks_events["warning_events"]) == 1
assert (
fixture.evidence.eks_events["warning_events"][0]["message"] == "Custom test event"
)
finally:
for f in real_dir.iterdir():
f.unlink()
real_dir.rmdir()
def test_chained_inheritance_rejected(self) -> None:
"""Declaring base on a scenario that itself has a base raises ValueError."""
real_dir_a = SUITE_DIR / "999-test-chain-a"
real_dir_b = SUITE_DIR / "999-test-chain-b"
real_dir_a.mkdir(exist_ok=True)
real_dir_b.mkdir(exist_ok=True)
try:
(real_dir_a / "scenario.yml").write_text(
textwrap.dedent("""\
base: 000-healthy
scenario_id: 999-test-chain-a
failure_mode: healthy
severity: info
""")
)
(real_dir_b / "scenario.yml").write_text(
textwrap.dedent("""\
base: 999-test-chain-a
scenario_id: 999-test-chain-b
failure_mode: healthy
severity: info
""")
)
_write_minimal_answer_yml(real_dir_b)
with pytest.raises(ValueError, match="Chained inheritance is not supported"):
load_scenario(real_dir_b)
finally:
for d in (real_dir_a, real_dir_b):
for f in d.iterdir():
f.unlink()
d.rmdir()
def test_missing_base_raises(self) -> None:
"""Referencing a non-existent base scenario raises ValueError."""
real_dir = SUITE_DIR / "999-test-missing-base"
real_dir.mkdir(exist_ok=True)
try:
(real_dir / "scenario.yml").write_text(
textwrap.dedent("""\
base: 999-nonexistent
scenario_id: 999-test-missing-base
failure_mode: healthy
severity: info
""")
)
_write_minimal_answer_yml(real_dir)
with pytest.raises(ValueError, match="Base scenario '999-nonexistent' not found"):
load_scenario(real_dir)
finally:
for f in real_dir.iterdir():
f.unlink()
real_dir.rmdir()
def test_no_base_works_unchanged(self) -> None:
"""Scenarios without a base field still load normally."""
fixture = load_scenario(SUITE_DIR / "000-healthy")
assert fixture.metadata.scenario_id == "000-healthy"
assert fixture.metadata.failure_mode == "healthy"
assert fixture.evidence.eks_pods is not None