tracer-cloud--opensre
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
113 行
4.2 KiB
Python
113 行
4.2 KiB
Python
"""Synthetic suite: run the Hermes classifier over each fixture log.
|
|
|
|
The suite is intentionally offline. It loads each scenario's
|
|
``errors.log``, parses every line into :class:`LogRecord` objects via
|
|
:func:`integrations.hermes.parser.parse_log_line`, and feeds them to a fresh
|
|
:class:`IncidentClassifier` configured by the scenario's YAML. The
|
|
emitted :class:`HermesIncident` list is then validated against
|
|
``answer.yml``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
|
|
import pytest
|
|
|
|
from integrations.hermes.classifier import IncidentClassifier
|
|
from integrations.hermes.incident import HermesIncident, LogLevel
|
|
from integrations.hermes.parser import parse_log_line
|
|
from tests.synthetic.hermes.scenario_loader import (
|
|
ExpectedIncident,
|
|
HermesScenarioFixture,
|
|
load_all_scenarios,
|
|
)
|
|
|
|
pytestmark = pytest.mark.synthetic
|
|
|
|
_SCENARIOS = load_all_scenarios()
|
|
|
|
|
|
def _classify(fixture: HermesScenarioFixture) -> list[HermesIncident]:
|
|
classifier = IncidentClassifier(
|
|
warning_burst_threshold=fixture.metadata.classifier.warning_burst_threshold,
|
|
warning_burst_window_s=fixture.metadata.classifier.warning_burst_window_s,
|
|
traceback_followup_s=fixture.metadata.classifier.traceback_followup_s,
|
|
)
|
|
incidents: list[HermesIncident] = []
|
|
prev_level: LogLevel | None = None
|
|
for line in fixture.log_lines:
|
|
record = parse_log_line(line, prev_level=prev_level)
|
|
if record is None:
|
|
continue
|
|
if not record.is_continuation:
|
|
prev_level = record.level
|
|
incidents.extend(classifier.observe(record))
|
|
incidents.extend(classifier.flush())
|
|
return incidents
|
|
|
|
|
|
def _matches(expected: ExpectedIncident, incident: HermesIncident) -> bool:
|
|
if incident.rule != expected.rule:
|
|
return False
|
|
if expected.severity is not None and incident.severity.value != expected.severity:
|
|
return False
|
|
if expected.logger is not None and incident.logger != expected.logger:
|
|
return False
|
|
if expected.title_contains is not None and expected.title_contains not in incident.title:
|
|
return False
|
|
if len(incident.records) < expected.min_records:
|
|
return False
|
|
return not (expected.run_id is not None and incident.run_id != expected.run_id)
|
|
|
|
|
|
def _assert_ordered_partial_match(
|
|
expected: tuple[ExpectedIncident, ...],
|
|
incidents: list[HermesIncident],
|
|
) -> None:
|
|
"""Each expected entry must match a later incident than the previous one.
|
|
|
|
This is intentionally tolerant: the suite asserts the *presence and
|
|
order* of expected incidents, not that the emitted stream contains
|
|
only those incidents. Use ``expected_incident_count`` for exhaustive
|
|
cardinality checks.
|
|
"""
|
|
cursor = 0
|
|
for entry_index, entry in enumerate(expected):
|
|
match_index: int | None = None
|
|
for offset, incident in enumerate(incidents[cursor:]):
|
|
if _matches(entry, incident):
|
|
match_index = cursor + offset
|
|
break
|
|
if match_index is None:
|
|
pytest.fail(
|
|
f"expected incident #{entry_index} {entry!r} not found in "
|
|
f"emitted stream {[(i.rule, i.logger, len(i.records)) for i in incidents]}"
|
|
)
|
|
cursor = match_index + 1
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"fixture",
|
|
_SCENARIOS,
|
|
ids=[fixture.scenario_id for fixture in _SCENARIOS],
|
|
)
|
|
def test_synthetic_hermes_scenario(fixture: HermesScenarioFixture) -> None:
|
|
incidents = _classify(fixture)
|
|
|
|
_assert_ordered_partial_match(fixture.answer_key.expected_incidents, incidents)
|
|
|
|
counts = Counter(incident.rule for incident in incidents)
|
|
for rule, assertion in fixture.answer_key.expected_incident_count.items():
|
|
observed = counts.get(rule, 0)
|
|
assert assertion.matches(observed), (
|
|
f"{fixture.scenario_id}: incident count for rule {rule!r} = "
|
|
f"{observed}, expected {assertion.op}{assertion.value}"
|
|
)
|
|
|
|
|
|
def test_suite_discovered_at_least_one_scenario() -> None:
|
|
# Guard against an empty discovery (e.g. someone moves the directory
|
|
# and the parametrize above silently degenerates to zero cases).
|
|
assert _SCENARIOS, "no Hermes synthetic scenarios discovered"
|