Research-Stack/4-Infrastructure/NoDupeLabs/tests/core/test_logging.py

484 lines
16 KiB
Python

"""Test logging module functionality."""
import pytest
import tempfile
import logging
from pathlib import Path
from unittest.mock import patch, MagicMock
from nodupe.core.logging_system import (
Logging,
LoggingError,
get_logger,
setup_logging
)
class TestLoggingError:
"""Test LoggingError exception."""
def test_logging_error(self):
"""Test LoggingError exception."""
error = LoggingError("Test logging error")
assert str(error) == "Test logging error"
assert isinstance(error, Exception)
class TestLoggingSetup:
"""Test Logging setup functionality."""
def test_setup_logging_defaults(self):
"""Test setup_logging with default parameters."""
# Clear any existing configuration
Logging._configured = False
Logging._loggers.clear()
# Test with defaults
Logging.setup_logging()
# Verify configuration
assert Logging._configured is True
root_logger = logging.getLogger()
assert root_logger.level == logging.INFO
assert len(root_logger.handlers) == 1 # Console handler
def test_setup_logging_with_file(self):
"""Test setup_logging with file output."""
with tempfile.TemporaryDirectory() as temp_dir:
log_file = Path(temp_dir) / "test.log"
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
# Setup with file
Logging.setup_logging(
log_file=log_file,
log_level="DEBUG",
console_output=True
)
# Verify configuration
assert Logging._configured is True
root_logger = logging.getLogger()
assert root_logger.level == logging.DEBUG
assert len(root_logger.handlers) == 2 # Console + File
# Verify file was created
assert log_file.exists()
def test_setup_logging_invalid_level(self):
"""Test setup_logging with invalid log level."""
with pytest.raises(LoggingError):
Logging.setup_logging(log_level="INVALID")
def test_setup_logging_custom_format(self):
"""Test setup_logging with custom format."""
custom_format = "%(levelname)s: %(message)s"
Logging._configured = False
Logging._loggers.clear()
Logging.setup_logging(log_format=custom_format)
root_logger = logging.getLogger()
assert len(root_logger.handlers) == 1
handler = root_logger.handlers[0]
assert handler.formatter._fmt == custom_format
class TestLoggingGetLogger:
"""Test Logging get_logger functionality."""
def test_get_logger_auto_configure(self):
"""Test get_logger auto-configures when not configured."""
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
logger = Logging.get_logger("test_module")
# Should auto-configure
assert Logging._configured is True
assert logger.name == "test_module"
def test_get_logger_caching(self):
"""Test get_logger caching."""
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
# First call
logger1 = Logging.get_logger("test_module")
# Second call
logger2 = Logging.get_logger("test_module")
# Should return same instance
assert logger1 is logger2
assert len(Logging._loggers) == 1
def test_get_logger_different_names(self):
"""Test get_logger with different names."""
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
logger1 = Logging.get_logger("module1")
logger2 = Logging.get_logger("module2")
assert logger1 is not logger2
assert logger1.name == "module1"
assert logger2.name == "module2"
assert len(Logging._loggers) == 2
class TestLoggingMethods:
"""Test Logging utility methods."""
def test_log_exception(self):
"""Test log_exception method."""
# Setup logging
Logging.setup_logging()
logger = Logging.get_logger("test")
test_exception = ValueError("Test exception")
# This should not raise
Logging.log_exception(logger, "Test error", exc_info=True)
def test_log_with_context(self):
"""Test log_with_context method."""
# Setup logging
Logging.setup_logging()
logger = Logging.get_logger("test")
# This should not raise
Logging.log_with_context(
logger,
"info",
"Test message",
user_id=123,
action="test"
)
def test_configure_module_logger(self):
"""Test configure_module_logger method."""
# Setup logging
Logging.setup_logging()
logger = Logging.configure_module_logger("test_module", "DEBUG")
assert logger.name == "test_module"
assert logger.level == logging.DEBUG
def test_set_log_level(self):
"""Test set_log_level method."""
# Setup logging
Logging.setup_logging()
logger = Logging.get_logger("test")
# Set different levels
Logging.set_log_level(logger, "DEBUG")
assert logger.level == logging.DEBUG
Logging.set_log_level(logger, "WARNING")
assert logger.level == logging.WARNING
def test_set_log_level_invalid(self):
"""Test set_log_level with invalid level."""
logger = Logging.get_logger("test")
with pytest.raises(LoggingError):
Logging.set_log_level(logger, "INVALID")
def test_add_file_handler(self):
"""Test add_file_handler method."""
with tempfile.TemporaryDirectory() as temp_dir:
log_file = Path(temp_dir) / "handler_test.log"
logger = Logging.get_logger("test")
# Add file handler
Logging.add_file_handler(
logger,
log_file,
log_level="DEBUG",
max_file_size=1024,
backup_count=2
)
# Verify handler was added
assert len(logger.handlers) == 1
handler = logger.handlers[0]
assert isinstance(handler, logging.handlers.RotatingFileHandler)
assert handler.level == logging.DEBUG
# Verify file was created
assert log_file.exists()
class TestLoggingConvenienceFunctions:
"""Test convenience functions."""
def test_get_logger_convenience(self):
"""Test get_logger convenience function."""
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
logger = get_logger("test_module")
assert logger.name == "test_module"
assert Logging._configured is True
def test_setup_logging_convenience(self):
"""Test setup_logging convenience function."""
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
with tempfile.TemporaryDirectory() as temp_dir:
log_file = Path(temp_dir) / "convenience.log"
setup_logging(
log_file=log_file,
log_level="DEBUG",
console_output=False
)
assert Logging._configured is True
root_logger = logging.getLogger()
assert root_logger.level == logging.DEBUG
assert len(root_logger.handlers) == 1 # Only file handler
class TestLoggingEdgeCases:
"""Test logging edge cases."""
def test_setup_logging_twice(self):
"""Test setup_logging called twice."""
# First setup
Logging.setup_logging(log_level="INFO")
# Second setup should clear existing handlers
Logging.setup_logging(log_level="DEBUG")
root_logger = logging.getLogger()
assert root_logger.level == logging.DEBUG
assert len(root_logger.handlers) == 1 # Only one handler
def test_log_with_empty_context(self):
"""Test log_with_context with empty context."""
Logging.setup_logging()
logger = Logging.get_logger("test")
# Should not raise
Logging.log_with_context(logger, "info", "Test message")
def test_add_file_handler_invalid_path(self):
"""Test add_file_handler with invalid path."""
logger = Logging.get_logger("test")
# This should raise an exception
with pytest.raises(LoggingError):
Logging.add_file_handler(logger, "/invalid/path/test.log")
class TestLoggingIntegration:
"""Test logging integration scenarios."""
def test_complete_logging_workflow(self):
"""Test complete logging workflow."""
# Clear any existing configuration
Logging._configured = False
Logging._loggers.clear()
with tempfile.TemporaryDirectory() as temp_dir:
log_file = Path(temp_dir) / "workflow.log"
# Setup logging
Logging.setup_logging(
log_file=log_file,
log_level="DEBUG",
console_output=True,
max_file_size=1024,
backup_count=3
)
# Get logger
logger = Logging.get_logger("workflow_test")
# Test different log levels
logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message")
logger.critical("Critical message")
# Test exception logging
try:
raise ValueError("Test exception")
except ValueError:
Logging.log_exception(logger, "Exception occurred")
# Test context logging
Logging.log_with_context(
logger,
"info",
"User action",
user_id=123,
action="login",
status="success"
)
# Test module-specific logger
module_logger = Logging.configure_module_logger(
"special_module", "WARNING")
module_logger.warning("Module warning")
# Verify file was created and has content
assert log_file.exists()
with open(log_file, 'r') as f:
content = f.read()
assert "Debug message" in content
assert "Info message" in content
assert "Warning message" in content
assert "Error message" in content
assert "Critical message" in content
assert "Exception occurred" in content
assert "User action" in content
assert "Module warning" in content
def test_logging_with_rotation(self):
"""Test logging with file rotation."""
with tempfile.TemporaryDirectory() as temp_dir:
log_file = Path(temp_dir) / "rotation_test.log"
# Setup with small file size to trigger rotation
Logging.setup_logging(
log_file=log_file,
log_level="INFO",
console_output=False,
max_file_size=100, # Very small to trigger rotation
backup_count=2
)
logger = Logging.get_logger("rotation_test")
# Write enough log messages to trigger rotation
for i in range(100):
logger.info(f"Log message {i}: " + "x" * 50) # Large message
# Verify rotation files were created
rotation_files = list(Path(temp_dir).glob("rotation_test.log.*"))
assert len(rotation_files) > 0
# Verify main log file still exists
assert log_file.exists()
def test_logging_performance(self):
"""Test logging performance with many messages."""
import time
Logging.setup_logging(console_output=False)
logger = Logging.get_logger("performance_test")
# Time 1000 log messages
start_time = time.time()
for i in range(1000):
logger.info(f"Performance test message {i}")
end_time = time.time()
# Should complete in reasonable time
duration = end_time - start_time
assert duration < 1.0 # Less than 1 second for 1000 messages
class TestLoggingAdditional:
"""Additional tests for complete coverage."""
def test_setup_logging_with_string_path(self):
"""Test setup_logging with string path (not Path object)."""
with tempfile.TemporaryDirectory() as temp_dir:
log_file = temp_dir + "/test_string.log"
# Clear configuration
Logging._configured = False
Logging._loggers.clear()
# Setup with string path
Logging.setup_logging(
log_file=log_file,
log_level="INFO",
console_output=False
)
# Verify configuration
assert Logging._configured is True
root_logger = logging.getLogger()
assert root_logger.level == logging.INFO
# Verify file was created
assert Path(log_file).exists()
def test_configure_module_logger_no_level(self):
"""Test configure_module_logger without log_level (None)."""
# Setup logging
Logging.setup_logging()
# Configure module logger without log_level
logger = Logging.configure_module_logger("test_module_no_level")
# Should return logger with default level (NOTSET)
assert logger.name == "test_module_no_level"
def test_log_with_context_all_levels(self):
"""Test log_with_context with all log levels."""
Logging.setup_logging(console_output=False)
logger = Logging.get_logger("test_levels")
# Test all log levels with context
Logging.log_with_context(logger, "debug", "Debug with context", key="value")
Logging.log_with_context(logger, "info", "Info with context", key="value")
Logging.log_with_context(logger, "warning", "Warning with context", key="value")
Logging.log_with_context(logger, "error", "Error with context", key="value")
Logging.log_with_context(logger, "critical", "Critical with context", key="value")
def test_log_exception_without_exc_info(self):
"""Test log_exception without exc_info."""
Logging.setup_logging(console_output=False)
logger = Logging.get_logger("test")
# This should not raise
Logging.log_exception(logger, "Test error without exc_info", exc_info=False)
def test_add_file_handler_with_string_path(self):
"""Test add_file_handler with string path (not Path object)."""
with tempfile.TemporaryDirectory() as temp_dir:
log_file = temp_dir + "/handler_string.log"
logger = Logging.get_logger("test_string_handler")
# Add file handler with string path
Logging.add_file_handler(
logger,
log_file,
log_level="INFO"
)
# Verify handler was added
assert len(logger.handlers) == 1
assert isinstance(logger.handlers[0], logging.handlers.RotatingFileHandler)
# Verify file was created
assert Path(log_file).exists()
def test_add_file_handler_exception(self):
"""Test add_file_handler with invalid path that raises exception."""
logger = Logging.get_logger("test")
# Try to add handler to an invalid path
with pytest.raises(LoggingError):
Logging.add_file_handler(logger, "/root/invalid/test.log")