4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
183 lines
6.5 KiB
Python
183 lines
6.5 KiB
Python
"""Tests for platform.observability.errors.boundary helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from platform.observability.errors.boundary import (
|
|
OpenSRESilentFallback,
|
|
report_and_reraise,
|
|
report_and_swallow,
|
|
report_exception,
|
|
)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _disable_sentry(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("OPENSRE_NO_TELEMETRY", "1")
|
|
|
|
|
|
def _mock_logger() -> MagicMock:
|
|
return MagicMock(spec=logging.Logger)
|
|
|
|
|
|
def _raise(exc: BaseException) -> None:
|
|
raise exc
|
|
|
|
|
|
class TestReportException:
|
|
def test_logs_and_captures(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = ValueError("test error")
|
|
with patch("platform.observability.errors.boundary.capture_exception") as mock_cap:
|
|
report_exception(exc, logger=mock_log, message="Something failed")
|
|
mock_log.error.assert_called_once()
|
|
mock_cap.assert_called_once_with(exc, extra=None)
|
|
|
|
def test_warning_severity(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = RuntimeError("warn")
|
|
with patch("platform.observability.errors.boundary.capture_exception"):
|
|
report_exception(exc, logger=mock_log, message="m", severity="warning")
|
|
mock_log.warning.assert_called_once()
|
|
mock_log.error.assert_not_called()
|
|
|
|
def test_info_severity_skips_sentry(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = OSError("expected fallback")
|
|
with patch("platform.observability.errors.boundary.capture_exception") as mock_cap:
|
|
report_exception(exc, logger=mock_log, message="handled", severity="info")
|
|
mock_log.info.assert_called_once()
|
|
mock_cap.assert_not_called()
|
|
|
|
def test_tags_are_prefixed(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = RuntimeError("boom")
|
|
with patch("platform.observability.errors.boundary.capture_exception") as mock_cap:
|
|
report_exception(
|
|
exc,
|
|
logger=mock_log,
|
|
message="msg",
|
|
tags={"surface": "cli", "component": "cli"},
|
|
)
|
|
extra = mock_cap.call_args[1]["extra"]
|
|
assert extra["tag.surface"] == "cli"
|
|
assert extra["tag.component"] == "cli"
|
|
assert mock_cap.call_args[1]["tags"] == {"surface": "cli", "component": "cli"}
|
|
|
|
def test_extras_are_merged(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = RuntimeError("boom")
|
|
with patch("platform.observability.errors.boundary.capture_exception") as mock_cap:
|
|
report_exception(
|
|
exc,
|
|
logger=mock_log,
|
|
message="msg",
|
|
tags={"surface": "tool"},
|
|
extras={"detail": "x"},
|
|
)
|
|
extra = mock_cap.call_args[1]["extra"]
|
|
assert extra["tag.surface"] == "tool"
|
|
assert extra["detail"] == "x"
|
|
assert mock_cap.call_args[1]["tags"] == {"surface": "tool"}
|
|
|
|
def test_no_tags_or_extras_passes_none(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = ValueError("plain")
|
|
with patch("platform.observability.errors.boundary.capture_exception") as mock_cap:
|
|
report_exception(exc, logger=mock_log, message="msg")
|
|
mock_cap.assert_called_once_with(exc, extra=None)
|
|
|
|
|
|
class TestReportAndSwallow:
|
|
def test_swallows_matching_exception(self) -> None:
|
|
mock_log = _mock_logger()
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception"),
|
|
report_and_swallow(logger=mock_log, message="swallowed"),
|
|
):
|
|
raise ValueError("silent")
|
|
|
|
def test_does_not_swallow_other_types(self) -> None:
|
|
mock_log = _mock_logger()
|
|
with (
|
|
pytest.raises(TypeError),
|
|
report_and_swallow(logger=mock_log, message="only value", swallow=ValueError),
|
|
):
|
|
raise TypeError("not swallowed")
|
|
|
|
def test_calls_report_exception_on_match(self) -> None:
|
|
mock_log = _mock_logger()
|
|
exc = RuntimeError("reported")
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception") as mock_cap,
|
|
report_and_swallow(logger=mock_log, message="msg"),
|
|
):
|
|
_raise(exc)
|
|
mock_cap.assert_called_once_with(exc, extra=None)
|
|
|
|
def test_no_exception_passes_through(self) -> None:
|
|
mock_log = _mock_logger()
|
|
result: list[int] = []
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception") as mock_cap,
|
|
report_and_swallow(logger=mock_log, message="msg"),
|
|
):
|
|
result.append(1)
|
|
assert result == [1]
|
|
mock_cap.assert_not_called()
|
|
|
|
def test_tuple_of_swallow_types(self) -> None:
|
|
mock_log = _mock_logger()
|
|
for exc_type in (ValueError, KeyError):
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception"),
|
|
report_and_swallow(
|
|
logger=mock_log,
|
|
message="multi",
|
|
swallow=(ValueError, KeyError),
|
|
),
|
|
):
|
|
raise exc_type("x")
|
|
|
|
|
|
class TestReportAndReraise:
|
|
def test_propagates_exception(self) -> None:
|
|
mock_log = _mock_logger()
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception") as mock_cap,
|
|
pytest.raises(RuntimeError, match="propagated"),
|
|
report_and_reraise(logger=mock_log, message="reraised"),
|
|
):
|
|
_raise(RuntimeError("propagated"))
|
|
mock_cap.assert_called_once()
|
|
|
|
def test_no_exception_passes_through(self) -> None:
|
|
mock_log = _mock_logger()
|
|
result: list[int] = []
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception") as mock_cap,
|
|
report_and_reraise(logger=mock_log, message="no error"),
|
|
):
|
|
result.append(42)
|
|
assert result == [42]
|
|
mock_cap.assert_not_called()
|
|
|
|
def test_logs_before_reraising(self) -> None:
|
|
mock_log = _mock_logger()
|
|
with (
|
|
patch("platform.observability.errors.boundary.capture_exception"),
|
|
pytest.raises(ValueError),
|
|
report_and_reraise(logger=mock_log, message="logged"),
|
|
):
|
|
_raise(ValueError("x"))
|
|
mock_log.error.assert_called_once()
|
|
|
|
|
|
class TestOpenSRESilentFallback:
|
|
def test_is_a_warning(self) -> None:
|
|
assert issubclass(OpenSRESilentFallback, Warning)
|