blob: 6a965351d8eef1db5fd9162548c4c1c0899cee72 [file] [edit]
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import unittest
from unittest.mock import patch
import logger
class TestLogger(unittest.TestCase):
def tearDown(self) -> None:
if logger._logger:
if logger._file_handler:
logger._logger.removeHandler(logger._file_handler)
logger._file_handler.close()
logger._file_handler = None
if logger._memory_handler:
logger._logger.removeHandler(logger._memory_handler)
logger._memory_handler.close()
logger._memory_handler = None
super().tearDown()
@patch("builtins.print")
def test_emit_status(self, mock_print: unittest.mock.MagicMock) -> None:
# Enable status updates and verify that emit_status() prints to stdout.
logger.init_logger(enable_status_updates=True)
logger.emit_status("test message")
mock_print.assert_called_once_with("STATUS_UPDATE:test message")
# Disable status updates and verify that emit_status() does not print.
mock_print.reset_mock()
logger.init_logger(enable_status_updates=False)
logger.emit_status("another message")
mock_print.assert_not_called()
# Re-enable status updates to ensure it still works.
mock_print.reset_mock()
logger.init_logger(enable_status_updates=True)
logger.emit_status("third message")
mock_print.assert_called_once_with("STATUS_UPDATE:third message")
def test_set_level(self) -> None:
import logging
logger.init_logger(log_level=logging.WARNING)
self.assertEqual(logger.get_log_level(), logging.WARNING)
with logger.set_level(logging.DEBUG):
self.assertEqual(logger.get_log_level(), logging.DEBUG)
self.assertEqual(logger.get_log_level(), logging.WARNING)
# Test with min
with logger.set_level(min(logger.get_log_level(), logging.INFO)):
self.assertEqual(logger.get_log_level(), logging.INFO)
logger.init_logger(log_level=logging.DEBUG)
with logger.set_level(min(logger.get_log_level(), logging.INFO)):
self.assertEqual(logger.get_log_level(), logging.DEBUG)
def test_log_exception_normal(self) -> None:
import logging
logger.init_logger(log_level=logging.WARNING)
with self.assertLogs("cog", level="ERROR") as cm:
try:
raise ValueError("test error")
except ValueError:
logger.log_exception("An error occurred")
self.assertEqual(len(cm.records), 2)
for record in cm.records:
self.assertEqual(record.funcName, "test_log_exception_normal")
self.assertEqual(record.filename, "test_logger.py")
self.assertEqual(cm.records[0].getMessage(), "An error occurred")
self.assertEqual(cm.records[1].getMessage(), "test error")
def test_log_exception_normal_debug(self) -> None:
import logging
logger.init_logger(log_level=logging.DEBUG)
with self.assertLogs("cog", level="DEBUG") as cm:
try:
raise ValueError("test error")
except ValueError:
logger.log_exception("An error occurred")
self.assertEqual(len(cm.records), 1)
record = cm.records[0]
self.assertEqual(record.funcName, "test_log_exception_normal_debug")
self.assertEqual(record.filename, "test_logger.py")
self.assertEqual(record.getMessage(), "An error occurred")
self.assertEqual(record.levelno, logging.ERROR)
def test_log_exception_called_process_error(self) -> None:
import logging
import subprocess
logger.init_logger(log_level=logging.WARNING)
e = subprocess.CalledProcessError(
1, ["ls", "dir"], output=b"out", stderr=b"err"
)
with self.assertLogs("cog", level="ERROR") as cm:
try:
raise e
except subprocess.CalledProcessError:
logger.log_exception("Command failed")
self.assertEqual(len(cm.records), 4)
for record in cm.records:
self.assertEqual(
record.funcName, "test_log_exception_called_process_error"
)
self.assertEqual(record.filename, "test_logger.py")
self.assertEqual(cm.records[0].getMessage(), "Command failed")
self.assertEqual(
cm.records[1].getMessage(), "Command `ls dir` exited with status 1"
)
self.assertEqual(cm.records[2].getMessage(), "stdout: out")
self.assertEqual(cm.records[3].getMessage(), "stderr: err")
def test_log_exception_called_process_error_debug(self) -> None:
import logging
import subprocess
logger.init_logger(log_level=logging.DEBUG)
e = subprocess.CalledProcessError(
1, ["ls", "dir"], output=b"out", stderr=b"err"
)
with self.assertLogs("cog", level="DEBUG") as cm:
try:
raise e
except subprocess.CalledProcessError:
logger.log_exception("Command failed")
self.assertEqual(len(cm.records), 4)
for record in cm.records:
self.assertEqual(
record.funcName, "test_log_exception_called_process_error_debug"
)
self.assertEqual(record.filename, "test_logger.py")
self.assertEqual(cm.records[0].getMessage(), "Command failed")
self.assertEqual(cm.records[0].levelno, logging.ERROR)
self.assertEqual(
cm.records[1].getMessage(), "Command `ls dir` exited with status 1"
)
self.assertEqual(cm.records[2].getMessage(), "stdout: out")
self.assertEqual(cm.records[3].getMessage(), "stderr: err")
def test_retroactive_file_logging(self) -> None:
import logging
import tempfile
from pathlib import Path
# Reset the logger and stream/file/memory handlers
logger.init_logger(log_level=logging.DEBUG)
# Log messages before file logging is set up
logger.log_info("Initial info message")
logger.log_debug("Initial debug message")
with tempfile.TemporaryDirectory() as tmpdir:
workspace_path = Path(tmpdir)
log_file = workspace_path / "workspace_setup.log"
# Verify file doesn't exist yet
self.assertFalse(log_file.exists())
# Initialize file logging, which should retroactively dump prior logs
logger.setup_file_logging(workspace_path)
# Log a message after file logging is set up
logger.log_info("Message after file setup")
# Verify file now exists
self.assertTrue(log_file.exists())
# Read log content
log_content = log_file.read_text()
self.assertIn("Initial info message", log_content)
self.assertIn("Initial debug message", log_content)
self.assertIn("Message after file setup", log_content)
def test_get_log_path_before_file_setup(self) -> None:
import logging
import os
logger.init_logger(log_level=logging.DEBUG)
logger.log_info("Test log entry before file setup")
temp_log_path = logger.get_log_path()
self.addCleanup(
lambda: os.remove(temp_log_path) if temp_log_path.exists() else None
)
self.assertTrue(temp_log_path.is_absolute())
self.assertTrue(temp_log_path.exists())
log_content = temp_log_path.read_text()
self.assertIn("Test log entry before file setup", log_content)
# Log another entry and call get_log_path again
logger.log_info("Another test log entry")
temp_log_path_2 = logger.get_log_path()
self.addCleanup(
lambda: os.remove(temp_log_path_2)
if temp_log_path_2.exists()
else None
)
self.assertTrue(temp_log_path_2.is_absolute())
self.assertTrue(temp_log_path_2.exists())
# The two paths should be different
self.assertNotEqual(temp_log_path, temp_log_path_2)
log_content_2 = temp_log_path_2.read_text()
self.assertIn("Test log entry before file setup", log_content_2)
self.assertIn("Another test log entry", log_content_2)
def test_get_log_path_after_file_setup(self) -> None:
import logging
import os
import tempfile
from pathlib import Path
logger.init_logger(log_level=logging.DEBUG)
with tempfile.TemporaryDirectory() as tmpdir:
workspace_path = Path(tmpdir)
logger.setup_file_logging(workspace_path)
log_path = logger.get_log_path()
# The path should be relative to CWD
self.assertFalse(log_path.is_absolute())
# Verify it matches the relative path of workspace_setup.log to CWD
expected_path = Path(
os.path.relpath(workspace_path / "workspace_setup.log")
)
self.assertEqual(log_path, expected_path)
if __name__ == "__main__":
unittest.main()