feat: v0.2.0 sprint — ground truth eval, crossover/mutation, checkpointing, similarity guards, dataset loader, CLI commands, extended test coverage
Aggregates all v0.2.0 sprint work (GARAA-30 through GARAA-40) and fixes 2 integration tests that broke when the codebase went async (DSPyLLMAdapter and full pipeline tests now properly await coroutines). 277 tests pass (260 unit + 17 integration). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
189
tests/unit/test_logging.py
Normal file
189
tests/unit/test_logging.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""Unit tests for structured logging configuration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from prometheus.cli.logging_setup import configure_logging, get_logger
|
||||
|
||||
|
||||
class TestConfigureLogging:
|
||||
def _count_handlers(self, name: str = "prometheus") -> int:
|
||||
return len(logging.getLogger(name).handlers)
|
||||
|
||||
def test_default_creates_console_handler(self) -> None:
|
||||
configure_logging(level=logging.INFO)
|
||||
prom = logging.getLogger("prometheus")
|
||||
assert len(prom.handlers) == 1
|
||||
assert isinstance(prom.handlers[0], logging.StreamHandler)
|
||||
prom.handlers.clear()
|
||||
|
||||
def test_json_format_produces_valid_json(self, capsys) -> None:
|
||||
configure_logging(level=logging.INFO, log_format="json")
|
||||
logger = get_logger("test_json")
|
||||
logger.info("hello", extra={"structured": {"key": "value"}})
|
||||
|
||||
captured = capsys.readouterr()
|
||||
# Output goes to stderr
|
||||
line = captured.err.strip().split("\n")[-1]
|
||||
data = json.loads(line)
|
||||
assert data["message"] == "hello"
|
||||
assert data["structured"]["key"] == "value"
|
||||
assert data["level"] == "INFO"
|
||||
assert "timestamp" in data
|
||||
|
||||
logging.getLogger("prometheus").handlers.clear()
|
||||
|
||||
def test_text_format_includes_structured_extras(self, capsys) -> None:
|
||||
configure_logging(level=logging.INFO, log_format="text")
|
||||
logger = get_logger("test_text")
|
||||
logger.info("msg", extra={"structured": {"foo": "bar"}})
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "foo=bar" in captured.err
|
||||
|
||||
logging.getLogger("prometheus").handlers.clear()
|
||||
|
||||
def test_debug_level_shows_debug_messages(self, capsys) -> None:
|
||||
configure_logging(level=logging.DEBUG)
|
||||
logger = get_logger("test_debug")
|
||||
logger.debug("debug msg")
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "debug msg" in captured.err
|
||||
|
||||
logging.getLogger("prometheus").handlers.clear()
|
||||
|
||||
def test_warning_level_hides_debug_messages(self, capsys) -> None:
|
||||
configure_logging(level=logging.WARNING)
|
||||
logger = get_logger("test_warn")
|
||||
logger.debug("should not appear")
|
||||
logger.info("also hidden")
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "should not appear" not in captured.err
|
||||
assert "also hidden" not in captured.err
|
||||
|
||||
logging.getLogger("prometheus").handlers.clear()
|
||||
|
||||
def test_file_handler_writes_to_file(self, tmp_path: Path) -> None:
|
||||
log_file = tmp_path / "test.log"
|
||||
configure_logging(level=logging.INFO, log_file=str(log_file))
|
||||
logger = get_logger("test_file")
|
||||
logger.info("file message")
|
||||
|
||||
prom = logging.getLogger("prometheus")
|
||||
# Flush handlers
|
||||
for h in prom.handlers:
|
||||
h.flush()
|
||||
prom.handlers.clear()
|
||||
|
||||
content = log_file.read_text()
|
||||
assert "file message" in content
|
||||
|
||||
def test_json_file_output(self, tmp_path: Path) -> None:
|
||||
log_file = tmp_path / "test.json.log"
|
||||
configure_logging(level=logging.INFO, log_format="json", log_file=str(log_file))
|
||||
logger = get_logger("test_json_file")
|
||||
logger.info("json file msg", extra={"structured": {"x": 1}})
|
||||
|
||||
prom = logging.getLogger("prometheus")
|
||||
for h in prom.handlers:
|
||||
h.flush()
|
||||
prom.handlers.clear()
|
||||
|
||||
content = log_file.read_text().strip()
|
||||
data = json.loads(content)
|
||||
assert data["message"] == "json file msg"
|
||||
assert data["structured"]["x"] == 1
|
||||
|
||||
def test_reconfigure_clears_old_handlers(self) -> None:
|
||||
configure_logging(level=logging.INFO)
|
||||
configure_logging(level=logging.DEBUG)
|
||||
prom = logging.getLogger("prometheus")
|
||||
assert len(prom.handlers) == 1
|
||||
prom.handlers.clear()
|
||||
|
||||
def test_propagate_false_prevents_duplicate_output(self, capsys) -> None:
|
||||
configure_logging(level=logging.INFO)
|
||||
prom = logging.getLogger("prometheus")
|
||||
assert prom.propagate is False
|
||||
prom.handlers.clear()
|
||||
|
||||
|
||||
class TestGetLogger:
|
||||
def test_returns_child_of_prometheus(self) -> None:
|
||||
logger = get_logger("mymodule")
|
||||
assert logger.name == "prometheus.mymodule"
|
||||
|
||||
def test_inherits_level_from_parent(self) -> None:
|
||||
configure_logging(level=logging.DEBUG)
|
||||
logger = get_logger("child")
|
||||
assert logger.getEffectiveLevel() <= logging.DEBUG
|
||||
logging.getLogger("prometheus").handlers.clear()
|
||||
|
||||
|
||||
class TestJsonFormatter:
|
||||
def test_exception_included(self, capsys) -> None:
|
||||
configure_logging(level=logging.ERROR, log_format="json")
|
||||
logger = get_logger("test_exc")
|
||||
try:
|
||||
raise ValueError("boom")
|
||||
except ValueError:
|
||||
logger.error("failed", exc_info=True)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
line = captured.err.strip().split("\n")[-1]
|
||||
data = json.loads(line)
|
||||
assert "ValueError: boom" in data["exception"]
|
||||
|
||||
logging.getLogger("prometheus").handlers.clear()
|
||||
|
||||
|
||||
class TestLoggingCLIIntegration:
|
||||
"""Tests for CLI flags that configure logging."""
|
||||
|
||||
def test_verbose_flag_enables_info(self, tmp_path: Path) -> None:
|
||||
"""Simulate what -v does — configure_logging at INFO level."""
|
||||
configure_logging(level=logging.INFO)
|
||||
logger = get_logger("evolution")
|
||||
logger.info("test message")
|
||||
|
||||
prom = logging.getLogger("prometheus")
|
||||
assert len(prom.handlers) == 1
|
||||
prom.handlers.clear()
|
||||
|
||||
def test_debug_flag_enables_debug(self) -> None:
|
||||
"""Simulate what --debug does — configure_logging at DEBUG level."""
|
||||
configure_logging(level=logging.DEBUG)
|
||||
logger = get_logger("evolution")
|
||||
logger.debug("debug message")
|
||||
|
||||
prom = logging.getLogger("prometheus")
|
||||
assert prom.level == logging.DEBUG
|
||||
prom.handlers.clear()
|
||||
|
||||
def test_log_format_invalid_rejected(self) -> None:
|
||||
"""Invalid log_format should be caught by OptimizationConfig validator."""
|
||||
from pydantic import ValidationError
|
||||
from prometheus.application.dto import OptimizationConfig
|
||||
|
||||
import pytest
|
||||
|
||||
with pytest.raises(ValidationError, match="log_format must be one of"):
|
||||
OptimizationConfig(
|
||||
seed_prompt="a",
|
||||
task_description="b",
|
||||
log_format="xml",
|
||||
)
|
||||
|
||||
def test_log_format_text_and_json_accepted(self) -> None:
|
||||
"""Both text and json log_format values should be valid."""
|
||||
from prometheus.application.dto import OptimizationConfig
|
||||
|
||||
for fmt in ("text", "json"):
|
||||
config = OptimizationConfig(
|
||||
seed_prompt="a", task_description="b", log_format=fmt,
|
||||
)
|
||||
assert config.log_format == fmt
|
||||
Reference in New Issue
Block a user