mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
484 lines
16 KiB
Python
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")
|