blob: 21032672d60753eb7d071a02727c943e5960095d [file]
# Copyright 2026 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.
"""Unit tests for logging_utils module."""
from __future__ import annotations
import io
import logging
import tempfile
import unittest
from unittest import mock
import logging_utils
class LoggingUtilsTest(unittest.TestCase):
"""Unit tests for logging_utils module."""
def test_supports_color_true_when_tty(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = True
with mock.patch.dict("os.environ", {"TERM": "xterm"}, clear=True):
self.assertTrue(logging_utils.supports_color(mock_stream))
def test_supports_color_false_when_no_tty(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = False
with mock.patch.dict("os.environ", {"TERM": "xterm"}, clear=True):
self.assertFalse(logging_utils.supports_color(mock_stream))
def test_supports_color_false_with_no_color_env(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = True
with mock.patch.dict("os.environ", {"NO_COLOR": "1"}):
self.assertFalse(logging_utils.supports_color(mock_stream))
def test_supports_color_false_with_dumb_term(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = True
with mock.patch.dict("os.environ", {"TERM": "dumb"}):
self.assertFalse(logging_utils.supports_color(mock_stream))
def test_supports_color_true_with_force_color(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = False
with mock.patch.dict("os.environ", {"FORCE_COLOR": "1"}, clear=True):
self.assertTrue(logging_utils.supports_color(mock_stream))
def test_supports_color_true_with_clicolor_force(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = False
with mock.patch.dict("os.environ", {"CLICOLOR_FORCE": "1"}, clear=True):
self.assertTrue(logging_utils.supports_color(mock_stream))
def test_supports_color_true_with_fuchsia_fx_test_run(self) -> None:
mock_stream = mock.MagicMock()
mock_stream.isatty.return_value = False
with mock.patch.dict(
"os.environ", {"FUCHSIA_FX_TEST_RUN": "1"}, clear=True
):
self.assertTrue(logging_utils.supports_color(mock_stream))
def test_colored_formatter_without_color(self) -> None:
formatter = logging_utils.ColoredFormatter(
"%(levelname)s %(message)s",
prefix="[MOCK_PREFIX] ",
use_color=False,
)
record = logging.LogRecord(
name="test",
level=logging.INFO,
pathname="test.py",
lineno=1,
msg="[Test] test_case PASS [ret: 0]",
args=(),
exc_info=None,
)
output = formatter.format(record)
self.assertEqual(
output, "[MOCK_PREFIX] INFO [Test] test_case PASS [ret: 0]"
)
def test_colored_formatter_with_color_results(self) -> None:
formatter = logging_utils.ColoredFormatter(
"[MOCK_PREFIX] %(levelname)s %(message)s",
use_color=True,
)
for result in ("PASS", "FAIL", "ERROR", "SKIP"):
record = logging.LogRecord(
name="test",
level=logging.INFO,
pathname="test.py",
lineno=1,
msg=f"[Test] test_case {result}",
args=(),
exc_info=None,
)
output = formatter.format(record)
self.assertIn("\033[", output)
self.assertIn(result, output)
# Ensure levelname was restored on the record
self.assertEqual(record.levelname, "INFO")
def test_colored_formatter_return_codes(self) -> None:
formatter = logging_utils.ColoredFormatter(
"%(message)s",
use_color=True,
)
# Success code (0)
record_pass = logging.LogRecord(
name="test",
level=logging.INFO,
pathname="test.py",
lineno=1,
msg="Completed: test_case PASS [ret: 0]",
args=(),
exc_info=None,
)
out_pass = formatter.format(record_pass)
self.assertIn("ret: 0", out_pass)
self.assertIn("\033[32mret: 0", out_pass) # Green
# Failure code (1)
record_fail = logging.LogRecord(
name="test",
level=logging.ERROR,
pathname="test.py",
lineno=1,
msg="Completed: test_case FAIL [ret: 1]",
args=(),
exc_info=None,
)
out_fail = formatter.format(record_fail)
self.assertIn("ret: 1", out_fail)
self.assertIn("\033[31mret: 1", out_fail) # Red
def test_colored_formatter_target_names(self) -> None:
formatter = logging_utils.ColoredFormatter(
"%(message)s",
use_color=True,
)
record = logging.LogRecord(
name="test",
level=logging.INFO,
pathname="test.py",
lineno=1,
msg="Connecting to fuchsia-1a7d-1303-a909 and fuchsia-emulator",
args=(),
exc_info=None,
)
out = formatter.format(record)
self.assertIn("\033[36mfuchsia-1a7d-1303-a909\033[0m", out)
self.assertIn("\033[36mfuchsia-emulator\033[0m", out)
def test_colorize_logger_preserves_file_handler(self) -> None:
test_logger = logging.Logger("test_logger")
stream = io.StringIO()
stream_handler = logging.StreamHandler(stream)
test_logger.addHandler(stream_handler)
with tempfile.NamedTemporaryFile() as tmp_file:
file_handler = logging.FileHandler(tmp_file.name)
original_file_formatter = logging.Formatter("%(message)s")
file_handler.setFormatter(original_file_formatter)
test_logger.addHandler(file_handler)
logging_utils.colorize_logger(test_logger, prefix="PREFIX")
# Stream handler should be colorized
self.assertIsInstance(
stream_handler.formatter, logging_utils.ColoredFormatter
)
# File handler should remain un-colorized
self.assertIs(file_handler.formatter, original_file_formatter)
self.assertNotIsInstance(
file_handler.formatter, logging_utils.ColoredFormatter
)
def test_colored_formatter_with_color_and_prefix(self) -> None:
formatter = logging_utils.ColoredFormatter(
"%(levelname)s %(message)s",
prefix="[MY_PREFIX] ",
use_color=True,
)
record = logging.LogRecord(
name="test",
level=logging.INFO,
pathname="test.py",
lineno=1,
msg="hello",
args=(),
exc_info=None,
)
output = formatter.format(record)
self.assertTrue(output.startswith("[MY_PREFIX] "))
self.assertIn("hello", output)
if __name__ == "__main__":
unittest.main()