Files
grm/tests/unit/test_logging_config.py
T
Emil Simeonov 3b3e002f7f GRM-13: feat: replace print() with stdlib logging module
- Create logging_config.py with get_logger() providing dual handlers:
  - Console handler (stderr) controlled by GRM_LOG_LEVEL env var (default INFO)
  - File handler (~/.local/state/grm/logs/grm.log) capturing everything at DEBUG
- Replace all print() calls in executor.py and report.py with logger.info()/error()
- Add error logging before raising AnsibleError in executor.run()
- Add GRM_LOG_LEVEL to README configuration table and logging documentation
- Update all unit tests to mock logger instead of using capsys
- 114 tests, 100% coverage, pyright clean, ruff clean
2026-06-19 01:24:35 +02:00

89 lines
3.5 KiB
Python

"""Unit tests for logging configuration."""
from __future__ import annotations
import logging
from collections.abc import Generator
from pathlib import Path
from unittest.mock import patch
import pytest
from gitea_runner_manager.logging_config import _level_from_env, get_logger
@pytest.fixture(autouse=True)
def _cleanup_loggers() -> Generator[None, None, None]:
"""Remove test loggers after each test to prevent state leakage."""
yield
for name in list(logging.Logger.manager.loggerDict.keys()):
if name.startswith("test_"):
logger = logging.getLogger(name)
logger.handlers.clear()
del logging.Logger.manager.loggerDict[name]
class TestLevelFromEnv:
def test_none_defaults_to_info(self) -> None:
assert _level_from_env(None) == logging.INFO
def test_empty_string_defaults_to_info(self) -> None:
assert _level_from_env("") == logging.INFO
def test_valid_levels(self) -> None:
assert _level_from_env("debug") == logging.DEBUG
assert _level_from_env("DEBUG") == logging.DEBUG
assert _level_from_env("warning") == logging.WARNING
assert _level_from_env("error") == logging.ERROR
assert _level_from_env("critical") == logging.CRITICAL
def test_invalid_fallback_to_info(self) -> None:
assert _level_from_env("foo") == logging.INFO
assert _level_from_env("VERBOSE") == logging.INFO
class TestGetLogger:
def test_returns_logger(self, tmp_path: Path) -> None:
with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path):
logger = get_logger("test_returns_logger")
assert isinstance(logger, logging.Logger)
assert logger.name == "test_returns_logger"
assert len(logger.handlers) == 2 # console + file
def test_caches_same_instance(self, tmp_path: Path) -> None:
with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path):
logger1 = get_logger("test_caches_same")
logger2 = get_logger("test_caches_same")
assert logger1 is logger2
def test_file_handler_writes(self, tmp_path: Path) -> None:
with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path):
logger = get_logger("test_file_writes")
logger.info("hello from test")
log_file = tmp_path / ".local" / "state" / "grm" / "logs" / "grm.log"
assert log_file.exists()
content = log_file.read_text()
assert "hello from test" in content
assert "INFO" in content
def test_respects_grm_log_level(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
monkeypatch.setenv("GRM_LOG_LEVEL", "DEBUG")
with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path):
logger = get_logger("test_env_level")
console = [h for h in logger.handlers if type(h) is logging.StreamHandler]
assert len(console) == 1
assert console[0].level == logging.DEBUG
def test_grm_log_level_warning(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
monkeypatch.setenv("GRM_LOG_LEVEL", "WARNING")
with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path):
logger = get_logger("test_env_warning")
console = [h for h in logger.handlers if type(h) is logging.StreamHandler]
assert len(console) == 1
assert console[0].level == logging.WARNING