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
194 lines
7.1 KiB
Python
194 lines
7.1 KiB
Python
"""Tests for the pure scoring module.
|
|
|
|
The key invariant verified here: importing scoring.py must NOT pull in any
|
|
``app.*`` modules (and therefore not the full agent runtime or any heavy runtime deps).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from tests.synthetic.rds_postgres.scenario_loader import SUITE_DIR, load_all_scenarios
|
|
|
|
|
|
def test_scoring_module_imports_without_app_pipeline() -> None:
|
|
"""scoring.py must be importable without touching tools.investigation."""
|
|
existing_pipeline_modules = {k for k in sys.modules if k.startswith("tools.investigation")}
|
|
|
|
# Force the import to happen in this test's process; if orchestration modules
|
|
# were already imported by earlier tests, only fail on newly imported ones.
|
|
from tests.synthetic.rds_postgres.scoring import score_result # noqa: F401
|
|
|
|
app_pipeline_modules = [
|
|
k
|
|
for k in sys.modules
|
|
if k.startswith("tools.investigation") and k not in existing_pipeline_modules
|
|
]
|
|
assert app_pipeline_modules == [], (
|
|
f"scoring.py transitively imported tools.investigation modules: {app_pipeline_modules}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Keyword matching unit tests (relocated from test_suite.py)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _normalized(text: str) -> str:
|
|
from tests.synthetic.rds_postgres.scoring import _normalize_text
|
|
|
|
return _normalize_text(text)
|
|
|
|
|
|
def test_normalize_text_lowercases_and_collapses_whitespace() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _normalize_text
|
|
|
|
assert _normalize_text(" Hello World ") == "hello world"
|
|
assert _normalize_text("CPUUtilization") == "cpuutilization"
|
|
|
|
|
|
def test_normalize_query_token_replaces_separators() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _normalize_query_token
|
|
|
|
assert _normalize_query_token("write-heavy workload") == "write_heavy_workload"
|
|
assert _normalize_query_token("CPU Utilization") == "cpu_utilization"
|
|
|
|
|
|
def test_keyword_match_exact_phrase() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _keyword_match_details
|
|
|
|
matched, mode, alias = _keyword_match_details("replication lag detected", "replication lag")
|
|
assert matched is True
|
|
assert mode == "exact_phrase"
|
|
assert alias is None
|
|
|
|
|
|
def test_keyword_match_alias_lookup_replication_lag() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _keyword_match_details
|
|
|
|
matched, mode, alias = _keyword_match_details("replica lag is high", "replicationlag")
|
|
assert matched is True
|
|
assert mode == "alias_lookup"
|
|
assert alias == "replica lag"
|
|
|
|
|
|
def test_keyword_match_alias_lookup_replicalag_token() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _keyword_match_details
|
|
|
|
matched, mode, alias = _keyword_match_details("replicalag increased sharply", "replication lag")
|
|
assert matched is True
|
|
assert mode == "alias_lookup"
|
|
assert alias == "replicalag"
|
|
|
|
|
|
def test_keyword_match_alias_lookup_causally_independent() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _keyword_match_details
|
|
|
|
matched, mode, alias = _keyword_match_details(
|
|
"the cpu spike is a red herring", "causallyindependent"
|
|
)
|
|
assert matched is True
|
|
assert mode == "alias_lookup"
|
|
|
|
|
|
def test_keyword_match_token_subset() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _keyword_match_details
|
|
|
|
matched, mode, _ = _keyword_match_details("cpu saturation detected on host", "cpu saturation")
|
|
assert matched is True
|
|
assert mode in ("exact_phrase", "token_subset")
|
|
|
|
|
|
def test_keyword_match_no_match() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import _keyword_match_details
|
|
|
|
matched, mode, _ = _keyword_match_details("everything is fine", "replication lag")
|
|
assert matched is False
|
|
assert mode == "none"
|
|
|
|
|
|
def test_score_result_uses_semantic_keyword_matching_for_write_heavy_workload() -> None:
|
|
"""Semantic alias 'write heavy workload' must satisfy 'write-heavyworkload' keyword."""
|
|
from tests.synthetic.rds_postgres.scenario_loader import SUITE_DIR, load_all_scenarios
|
|
from tests.synthetic.rds_postgres.scoring import score_result
|
|
|
|
fixtures = load_all_scenarios(SUITE_DIR)
|
|
write_heavy_fixture = next((f for f in fixtures if "write" in f.scenario_id.lower()), None)
|
|
if write_heavy_fixture is None:
|
|
pytest.skip("no write-heavy fixture in current suite")
|
|
|
|
final_state: dict[str, Any] = {
|
|
"root_cause": "write heavy workload causing checkpoint pressure",
|
|
"root_cause_category": write_heavy_fixture.answer_key.root_cause_category,
|
|
"evidence": {
|
|
"aws_cloudwatch_metrics": {
|
|
"metrics": [{"metric_name": "WriteIOPS"}],
|
|
"observations": ["write heavy workload"],
|
|
}
|
|
},
|
|
"validated_claims": [],
|
|
"non_validated_claims": [],
|
|
"causal_chain": ["write heavy workload"],
|
|
"report": "",
|
|
"executed_hypotheses": [],
|
|
"investigation_loop_count": 0,
|
|
}
|
|
score = score_result(write_heavy_fixture, final_state)
|
|
assert "write-heavyworkload" not in score.semantic_missing_keywords, (
|
|
"write-heavyworkload alias should match via 'write heavy workload'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# score_result basic contract
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_score_result_returns_scenario_score_type() -> None:
|
|
from tests.synthetic.rds_postgres.scoring import ScenarioScore, score_result
|
|
|
|
fixtures = load_all_scenarios(SUITE_DIR)
|
|
fixture = fixtures[0]
|
|
final_state: dict[str, Any] = {
|
|
"root_cause": "",
|
|
"root_cause_category": "unknown",
|
|
"evidence": {},
|
|
"validated_claims": [],
|
|
"non_validated_claims": [],
|
|
"causal_chain": [],
|
|
"report": "",
|
|
"executed_hypotheses": [],
|
|
"investigation_loop_count": 0,
|
|
}
|
|
result = score_result(fixture, final_state)
|
|
assert isinstance(result, ScenarioScore)
|
|
assert result.scenario_id == fixture.scenario_id
|
|
|
|
|
|
def test_score_result_all_required_gates_present() -> None:
|
|
"""Every required gate name must be present in a scored result."""
|
|
from tests.synthetic.rds_postgres.scoring import _REQUIRED_GATE_NAMES, score_result
|
|
|
|
fixtures = load_all_scenarios(SUITE_DIR)
|
|
fixture = fixtures[0]
|
|
final_state: dict[str, Any] = {
|
|
"root_cause": "",
|
|
"root_cause_category": "unknown",
|
|
"evidence": {},
|
|
"validated_claims": [],
|
|
"non_validated_claims": [],
|
|
"causal_chain": [],
|
|
"report": "",
|
|
"executed_hypotheses": [],
|
|
"investigation_loop_count": 0,
|
|
}
|
|
result = score_result(fixture, final_state)
|
|
# trajectory_policy is set by _apply_trajectory_policy_to_score in run_suite,
|
|
# not by score_result itself — skip it here.
|
|
scoring_gate_names = _REQUIRED_GATE_NAMES - {"trajectory_policy"}
|
|
missing = scoring_gate_names - set(result.gates)
|
|
assert not missing, f"Missing required gates in score_result output: {missing}"
|