import logging from datetime import UTC, datetime from io import StringIO from EvoScientist.logging_config import ( DailyLogFileHandler, configure_console_logging, configure_daily_file_logging, configure_logging, default_log_dir, resolve_log_level, ) def test_daily_log_file_handler_uses_dated_active_file(tmp_path): handler = DailyLogFileHandler(tmp_path, prefix="evoscientist", retention_days=30) logger = logging.getLogger("tests.daily_log_file_handler") logger.handlers.clear() logger.propagate = False logger.setLevel(logging.INFO) logger.addHandler(handler) logger.info("hello") handler.close() today = datetime.now().strftime("%Y-%m-%d") assert (tmp_path / f"evoscientist-{today}.log").read_text(encoding="utf-8").strip() def test_daily_log_file_handler_keeps_latest_retention_days(tmp_path): for day in range(1, 33): (tmp_path / f"evoscientist-2026-01-{day:02d}.log").write_text( "x", encoding="utf-8" ) handler = DailyLogFileHandler(tmp_path, prefix="evoscientist", retention_days=30) handler._delete_expired_logs() handler.close() remaining = sorted(path.name for path in tmp_path.glob("evoscientist-*.log")) assert len(remaining) == 30 assert remaining[0] == "evoscientist-2026-01-03.log" def test_configure_daily_file_logging_replaces_matching_handler(tmp_path): logger = logging.getLogger("tests.configure_daily_file_logging") logger.handlers.clear() logger.propagate = False first = configure_daily_file_logging(logger, log_dir=tmp_path) second = configure_daily_file_logging(logger, log_dir=tmp_path) try: handlers = [h for h in logger.handlers if isinstance(h, DailyLogFileHandler)] assert handlers == [second] assert first.stream is None finally: for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() def test_configure_logging_replaces_only_managed_handlers(tmp_path): logger = logging.getLogger("tests.configure_logging") logger.handlers.clear() logger.propagate = False external = logging.NullHandler() logger.addHandler(external) configure_logging(logger, log_dir=tmp_path, level="debug") configure_logging(logger, log_dir=tmp_path, level="info") try: daily_handlers = [h for h in logger.handlers if isinstance(h, DailyLogFileHandler)] stream_handlers = [ h for h in logger.handlers if isinstance(h, logging.StreamHandler) and not isinstance(h, DailyLogFileHandler) ] assert external in logger.handlers assert len(daily_handlers) == 1 assert len(stream_handlers) == 1 assert logger.level == logging.INFO finally: for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() def test_configure_console_logging_emits_to_stream(): logger = logging.getLogger("tests.configure_console_logging") logger.handlers.clear() logger.propagate = False stream = StringIO() configure_console_logging(logger, level="INFO", stream=stream) try: logger.info("hello") assert "tests.configure_console_logging: hello" in stream.getvalue() finally: for handler in logger.handlers[:]: logger.removeHandler(handler) handler.close() def test_resolve_log_level_accepts_alias_numeric_and_fallback(): assert resolve_log_level("warn") == logging.WARNING assert resolve_log_level("10") == logging.DEBUG assert resolve_log_level("", default=logging.ERROR) == logging.ERROR assert resolve_log_level("not-a-level", default=logging.CRITICAL) == logging.CRITICAL def test_daily_log_file_handler_supports_utc(tmp_path): handler = DailyLogFileHandler(tmp_path, utc=True) try: today_utc = datetime.now(UTC).strftime("%Y-%m-%d") assert handler.active_log_path.name == f"evoscientist-{today_utc}.log" finally: handler.close() def test_default_log_dir_uses_current_data_dir(monkeypatch, tmp_path): import EvoScientist.paths as paths monkeypatch.delenv("EVOSCIENTIST_LOG_DIR", raising=False) monkeypatch.setattr(paths, "DATA_DIR", tmp_path / "data") assert default_log_dir() == tmp_path / "data" / "logs"