mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 02:31:45 +01:00
feat: collect timeout timing sessions for diagnostics (#814)
This commit is contained in:
@@ -96,12 +96,12 @@ def invoke_cli(
|
||||
set_current_locale(previous_locale)
|
||||
|
||||
|
||||
def _xdg_env_overrides(tmp_path:Path) -> dict[str, str]:
|
||||
"""Create temporary HOME/XDG environment overrides for isolated smoke test runs."""
|
||||
home = tmp_path / "home"
|
||||
xdg_config = tmp_path / "xdg" / "config"
|
||||
xdg_state = tmp_path / "xdg" / "state"
|
||||
xdg_cache = tmp_path / "xdg" / "cache"
|
||||
def _xdg_env_overrides(base_path:Path) -> dict[str, str]:
|
||||
"""Create temporary HOME/XDG environment overrides rooted at the provided base path."""
|
||||
home = base_path / "home"
|
||||
xdg_config = base_path / "xdg" / "config"
|
||||
xdg_state = base_path / "xdg" / "state"
|
||||
xdg_cache = base_path / "xdg" / "cache"
|
||||
for path in (home, xdg_config, xdg_state, xdg_cache):
|
||||
path.mkdir(parents = True, exist_ok = True)
|
||||
return {
|
||||
|
||||
204
tests/unit/test_timing_collector.py
Normal file
204
tests/unit/test_timing_collector.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from kleinanzeigen_bot.utils import misc
|
||||
from kleinanzeigen_bot.utils.timing_collector import RETENTION_DAYS, TimingCollector
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class TestTimingCollector:
|
||||
def test_output_dir_resolves_to_given_path(self, tmp_path:Path) -> None:
|
||||
collector = TimingCollector(tmp_path / "xdg-cache" / "timing", "publish")
|
||||
|
||||
assert collector.output_dir == (tmp_path / "xdg-cache" / "timing").resolve()
|
||||
|
||||
def test_flush_writes_session_data(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "publish")
|
||||
collector.record(
|
||||
key = "default",
|
||||
operation_type = "web_find",
|
||||
description = "web_find(ID, submit)",
|
||||
configured_timeout = 5.0,
|
||||
effective_timeout = 5.0,
|
||||
actual_duration = 0.4,
|
||||
attempt_index = 0,
|
||||
success = True,
|
||||
)
|
||||
|
||||
file_path = collector.flush()
|
||||
|
||||
assert file_path is not None
|
||||
assert file_path.exists()
|
||||
|
||||
data = json.loads(file_path.read_text(encoding = "utf-8"))
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 1
|
||||
assert data[0]["command"] == "publish"
|
||||
assert len(data[0]["records"]) == 1
|
||||
assert data[0]["records"][0]["operation_key"] == "default"
|
||||
|
||||
def test_flush_prunes_old_and_malformed_sessions(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
output_dir = tmp_path / ".temp" / "timing"
|
||||
output_dir.mkdir(parents = True, exist_ok = True)
|
||||
data_path = output_dir / "timing_data.json"
|
||||
|
||||
old_started = (misc.now() - timedelta(days = RETENTION_DAYS + 1)).isoformat()
|
||||
recent_started = (misc.now() - timedelta(days = 2)).isoformat()
|
||||
|
||||
existing_payload = [
|
||||
{
|
||||
"session_id": "old-session",
|
||||
"command": "publish",
|
||||
"started_at": old_started,
|
||||
"ended_at": old_started,
|
||||
"records": [],
|
||||
},
|
||||
{
|
||||
"session_id": "recent-session",
|
||||
"command": "publish",
|
||||
"started_at": recent_started,
|
||||
"ended_at": recent_started,
|
||||
"records": [],
|
||||
},
|
||||
{
|
||||
"session_id": "malformed-session",
|
||||
"command": "publish",
|
||||
"started_at": "not-a-datetime",
|
||||
"ended_at": "not-a-datetime",
|
||||
"records": [],
|
||||
},
|
||||
]
|
||||
data_path.write_text(json.dumps(existing_payload), encoding = "utf-8")
|
||||
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "verify")
|
||||
collector.record(
|
||||
key = "default",
|
||||
operation_type = "web_find",
|
||||
description = "web_find(ID, submit)",
|
||||
configured_timeout = 5.0,
|
||||
effective_timeout = 5.0,
|
||||
actual_duration = 0.2,
|
||||
attempt_index = 0,
|
||||
success = True,
|
||||
)
|
||||
|
||||
file_path = collector.flush()
|
||||
|
||||
assert file_path is not None
|
||||
data = json.loads(file_path.read_text(encoding = "utf-8"))
|
||||
session_ids = [session["session_id"] for session in data]
|
||||
assert "old-session" not in session_ids
|
||||
assert "malformed-session" not in session_ids
|
||||
assert "recent-session" in session_ids
|
||||
assert collector.session_id in session_ids
|
||||
|
||||
def test_flush_returns_none_when_already_flushed(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "publish")
|
||||
collector.record(
|
||||
key = "default",
|
||||
operation_type = "web_find",
|
||||
description = "web_find(ID, submit)",
|
||||
configured_timeout = 5.0,
|
||||
effective_timeout = 5.0,
|
||||
actual_duration = 0.1,
|
||||
attempt_index = 0,
|
||||
success = True,
|
||||
)
|
||||
|
||||
first = collector.flush()
|
||||
second = collector.flush()
|
||||
|
||||
assert first is not None
|
||||
assert second is None
|
||||
|
||||
def test_flush_returns_none_when_no_records(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "publish")
|
||||
|
||||
assert collector.flush() is None
|
||||
|
||||
def test_flush_recovers_from_corrupted_json(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
output_dir = tmp_path / ".temp" / "timing"
|
||||
output_dir.mkdir(parents = True, exist_ok = True)
|
||||
data_path = output_dir / "timing_data.json"
|
||||
data_path.write_text("{ this is invalid json", encoding = "utf-8")
|
||||
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "verify")
|
||||
collector.record(
|
||||
key = "default",
|
||||
operation_type = "web_find",
|
||||
description = "web_find(ID, submit)",
|
||||
configured_timeout = 5.0,
|
||||
effective_timeout = 5.0,
|
||||
actual_duration = 0.1,
|
||||
attempt_index = 0,
|
||||
success = True,
|
||||
)
|
||||
|
||||
file_path = collector.flush()
|
||||
|
||||
assert file_path is not None
|
||||
payload = json.loads(file_path.read_text(encoding = "utf-8"))
|
||||
assert isinstance(payload, list)
|
||||
assert len(payload) == 1
|
||||
assert payload[0]["session_id"] == collector.session_id
|
||||
|
||||
def test_flush_ignores_non_list_payload(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
output_dir = tmp_path / ".temp" / "timing"
|
||||
output_dir.mkdir(parents = True, exist_ok = True)
|
||||
data_path = output_dir / "timing_data.json"
|
||||
data_path.write_text(json.dumps({"unexpected": "shape"}), encoding = "utf-8")
|
||||
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "verify")
|
||||
collector.record(
|
||||
key = "default",
|
||||
operation_type = "web_find",
|
||||
description = "web_find(ID, submit)",
|
||||
configured_timeout = 5.0,
|
||||
effective_timeout = 5.0,
|
||||
actual_duration = 0.1,
|
||||
attempt_index = 0,
|
||||
success = True,
|
||||
)
|
||||
|
||||
file_path = collector.flush()
|
||||
|
||||
assert file_path is not None
|
||||
payload = json.loads(file_path.read_text(encoding = "utf-8"))
|
||||
assert isinstance(payload, list)
|
||||
assert len(payload) == 1
|
||||
assert payload[0]["session_id"] == collector.session_id
|
||||
|
||||
def test_flush_returns_none_when_write_raises(self, tmp_path:Path, monkeypatch:pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.chdir(tmp_path)
|
||||
collector = TimingCollector(tmp_path / ".temp" / "timing", "verify")
|
||||
collector.record(
|
||||
key = "default",
|
||||
operation_type = "web_find",
|
||||
description = "web_find(ID, submit)",
|
||||
configured_timeout = 5.0,
|
||||
effective_timeout = 5.0,
|
||||
actual_duration = 0.1,
|
||||
attempt_index = 0,
|
||||
success = True,
|
||||
)
|
||||
|
||||
with patch.object(Path, "mkdir", side_effect = OSError("cannot create dir")):
|
||||
assert collector.flush() is None
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user