"""Tests for debug flag behavior and environment variable processing."""
import io
import logging
import os
import pytest
from mcp_server_tree_sitter.bootstrap import update_log_levels
from mcp_server_tree_sitter.bootstrap.logging_bootstrap import get_log_level_from_env
def test_debug_flag_with_preexisting_env():
"""Test that debug flag works correctly with pre-existing environment variables.
This test simulates the real-world scenario where the logging is configured
at import time, but the debug flag is processed later. In this case, the
debug flag should still trigger a reconfiguration of logging levels.
"""
# Save original environment and logger state
original_env = os.environ.get("MCP_TS_LOG_LEVEL")
# Get the root package logger
pkg_logger = logging.getLogger("mcp_server_tree_sitter")
original_level = pkg_logger.level
# Create a clean test environment
if "MCP_TS_LOG_LEVEL" in os.environ:
del os.environ["MCP_TS_LOG_LEVEL"]
# Set logger level to INFO explicitly
pkg_logger.setLevel(logging.INFO)
# Create a test handler to verify levels change
test_handler = logging.StreamHandler()
test_handler.setLevel(logging.INFO)
pkg_logger.addHandler(test_handler)
try:
# Simulate the debug flag processing
# First verify we're starting at INFO level
assert pkg_logger.level == logging.INFO, "Logger should start at INFO level"
assert test_handler.level == logging.INFO, "Handler should start at INFO level"
# Now process the debug flag (this is what happens in main())
os.environ["MCP_TS_LOG_LEVEL"] = "DEBUG"
update_log_levels("DEBUG")
# Verify the change was applied
assert pkg_logger.level == logging.DEBUG, "Logger level should be changed to DEBUG"
assert test_handler.level == logging.DEBUG, "Handler level should be changed to DEBUG"
# Verify that new loggers created after updating will inherit the correct level
new_logger = logging.getLogger("mcp_server_tree_sitter.test.new_module")
assert new_logger.getEffectiveLevel() == logging.DEBUG, "New loggers should inherit DEBUG level"
finally:
# Cleanup
pkg_logger.removeHandler(test_handler)
# Restore original environment
if original_env is not None:
os.environ["MCP_TS_LOG_LEVEL"] = original_env
else:
if "MCP_TS_LOG_LEVEL" in os.environ:
del os.environ["MCP_TS_LOG_LEVEL"]
# Restore logger state
pkg_logger.setLevel(original_level)
def test_update_log_levels_reconfigures_root_logger():
"""Test that update_log_levels also updates the root logger.
This tests the enhanced implementation that reconfigures the root
logger in addition to the package logger, which helps with debug
flag handling when a module is already imported.
"""
# Save original logger states
root_logger = logging.getLogger()
pkg_logger = logging.getLogger("mcp_server_tree_sitter")
original_root_level = root_logger.level
original_pkg_level = pkg_logger.level
# Create handlers for testing
root_handler = logging.StreamHandler()
root_handler.setLevel(logging.INFO)
root_logger.addHandler(root_handler)
pkg_handler = logging.StreamHandler()
pkg_handler.setLevel(logging.INFO)
pkg_logger.addHandler(pkg_handler)
try:
# Set loggers to INFO level
root_logger.setLevel(logging.INFO)
pkg_logger.setLevel(logging.INFO)
# Verify initial levels
assert root_logger.level == logging.INFO, "Root logger should start at INFO level"
assert pkg_logger.level == logging.INFO, "Package logger should start at INFO level"
assert root_handler.level == logging.INFO, "Root handler should start at INFO level"
assert pkg_handler.level == logging.INFO, "Package handler should start at INFO level"
# Call update_log_levels with DEBUG
update_log_levels("DEBUG")
# Verify all loggers and handlers are updated
assert root_logger.level == logging.DEBUG, "Root logger should be updated to DEBUG level"
assert pkg_logger.level == logging.DEBUG, "Package logger should be updated to DEBUG level"
assert root_handler.level == logging.DEBUG, "Root handler should be updated to DEBUG level"
assert pkg_handler.level == logging.DEBUG, "Package handler should be updated to DEBUG level"
# Test with a new child logger
child_logger = logging.getLogger("mcp_server_tree_sitter.test.child")
assert child_logger.getEffectiveLevel() == logging.DEBUG, "Child logger should inherit DEBUG level from parent"
finally:
# Clean up
root_logger.removeHandler(root_handler)
pkg_logger.removeHandler(pkg_handler)
# Restore original levels
root_logger.setLevel(original_root_level)
pkg_logger.setLevel(original_pkg_level)
def test_environment_variable_updates_log_level():
"""Test that setting MCP_TS_LOG_LEVEL changes the logging level correctly."""
# Save original environment and logger state
original_env = os.environ.get("MCP_TS_LOG_LEVEL")
# Get the root package logger
pkg_logger = logging.getLogger("mcp_server_tree_sitter")
original_level = pkg_logger.level
try:
# First test with DEBUG level
os.environ["MCP_TS_LOG_LEVEL"] = "DEBUG"
# Verify the get_log_level_from_env function returns DEBUG
level = get_log_level_from_env()
assert level == logging.DEBUG, f"Expected DEBUG level but got {level}"
# Update log levels and verify the logger is set to DEBUG
update_log_levels("DEBUG")
assert pkg_logger.level == logging.DEBUG, f"Logger level should be DEBUG but was {pkg_logger.level}"
# Check handler levels are synchronized
for handler in pkg_logger.handlers:
assert handler.level == logging.DEBUG, f"Handler level should be DEBUG but was {handler.level}"
# Next test with INFO level
os.environ["MCP_TS_LOG_LEVEL"] = "INFO"
# Verify the get_log_level_from_env function returns INFO
level = get_log_level_from_env()
assert level == logging.INFO, f"Expected INFO level but got {level}"
# Update log levels and verify the logger is set to INFO
update_log_levels("INFO")
assert pkg_logger.level == logging.INFO, f"Logger level should be INFO but was {pkg_logger.level}"
# Check handler levels are synchronized
for handler in pkg_logger.handlers:
assert handler.level == logging.INFO, f"Handler level should be INFO but was {handler.level}"
finally:
# Restore original environment
if original_env is not None:
os.environ["MCP_TS_LOG_LEVEL"] = original_env
else:
if "MCP_TS_LOG_LEVEL" in os.environ:
del os.environ["MCP_TS_LOG_LEVEL"]
# Restore logger state
pkg_logger.setLevel(original_level)
def test_configure_root_logger_syncs_handlers():
"""Test that configure_root_logger synchronizes handler levels for existing loggers."""
from mcp_server_tree_sitter.bootstrap.logging_bootstrap import configure_root_logger
# Save original environment and logger state
original_env = os.environ.get("MCP_TS_LOG_LEVEL")
# Create a test logger in the package hierarchy
test_logger = logging.getLogger("mcp_server_tree_sitter.test.debug_flag")
original_test_level = test_logger.level
# Get the root package logger
pkg_logger = logging.getLogger("mcp_server_tree_sitter")
original_pkg_level = pkg_logger.level
# Create handlers with different levels
debug_handler = logging.StreamHandler()
debug_handler.setLevel(logging.DEBUG)
info_handler = logging.StreamHandler()
info_handler.setLevel(logging.INFO)
# Add handlers to the test logger
test_logger.addHandler(debug_handler)
test_logger.addHandler(info_handler)
try:
# Set environment variable to DEBUG
os.environ["MCP_TS_LOG_LEVEL"] = "DEBUG"
# Call configure_root_logger
configure_root_logger()
# Verify the root package logger is set to DEBUG
assert pkg_logger.level == logging.DEBUG, (
f"Root package logger level should be DEBUG but was {pkg_logger.level}"
)
# Verify child logger still has its original level (should not be explicitly set)
assert test_logger.level == original_test_level, (
"Child logger level should not be changed by configure_root_logger"
)
# Verify child logger's effective level is inherited from root package logger
assert test_logger.getEffectiveLevel() == logging.DEBUG, (
f"Child logger effective level should be DEBUG but was {test_logger.getEffectiveLevel()}"
)
# Verify all handlers of the test logger are synchronized to DEBUG
for handler in test_logger.handlers:
assert handler.level == logging.DEBUG, f"Handler level should be DEBUG but was {handler.level}"
finally:
# Clean up
test_logger.removeHandler(debug_handler)
test_logger.removeHandler(info_handler)
# Restore original environment
if original_env is not None:
os.environ["MCP_TS_LOG_LEVEL"] = original_env
else:
if "MCP_TS_LOG_LEVEL" in os.environ:
del os.environ["MCP_TS_LOG_LEVEL"]
# Restore logger state
test_logger.setLevel(original_test_level)
pkg_logger.setLevel(original_pkg_level)
def test_log_message_levels():
"""Test that log messages about environment variables use the DEBUG level."""
# Save original environment state
original_env = {}
for key in list(os.environ.keys()):
if key.startswith("MCP_TS_"):
original_env[key] = os.environ[key]
del os.environ[key]
try:
# Test variable for configuration
os.environ["MCP_TS_CACHE_MAX_SIZE_MB"] = "256"
# Create a StringIO to capture log output
log_output = io.StringIO()
# Create a handler that writes to our StringIO
handler = logging.StreamHandler(log_output)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(levelname)s:%(name)s:%(message)s")
handler.setFormatter(formatter)
# Add the handler to the root logger
root_logger = logging.getLogger()
root_logger.addHandler(handler)
# Save the original log level
original_level = root_logger.level
# Set the log level to DEBUG to capture all messages
root_logger.setLevel(logging.DEBUG)
try:
# Import config to trigger environment variable processing
from mcp_server_tree_sitter.config import ServerConfig
# Create a new config instance to trigger environment variable processing
# Variable is intentionally used to trigger processing
_ = ServerConfig()
# Get the output
log_content = log_output.getvalue()
# Check for environment variable application messages
env_messages = [line for line in log_content.splitlines() if "Applied environment variable" in line]
# Verify that these messages use DEBUG level, not INFO
for msg in env_messages:
assert msg.startswith("DEBUG:"), f"Environment variable message should use DEBUG level but found: {msg}"
# Check if there are any environment variable messages at INFO level
info_env_messages = [
line
for line in log_content.splitlines()
if "Applied environment variable" in line and line.startswith("INFO:")
]
assert not info_env_messages, (
f"No environment variable messages should use INFO level, but found: {info_env_messages}"
)
finally:
# Restore original log level
root_logger.setLevel(original_level)
# Remove our handler
root_logger.removeHandler(handler)
finally:
# Restore original environment
for key in list(os.environ.keys()):
if key.startswith("MCP_TS_"):
del os.environ[key]
for key, value in original_env.items():
os.environ[key] = value
if __name__ == "__main__":
pytest.main(["-v", __file__])