Coverage for tests / unit / utils / test_console_output_writer.py: 100%
98 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for ThreadSafeConsoleLogger class message tracking."""
3from __future__ import annotations
5from pathlib import Path
6from unittest.mock import patch
8import pytest
9from assertpy import assert_that
11from lintro.utils.console import ThreadSafeConsoleLogger
14@pytest.fixture
15def logger() -> ThreadSafeConsoleLogger:
16 """Create a ThreadSafeConsoleLogger instance for testing.
18 Returns:
19 ThreadSafeConsoleLogger instance for testing.
20 """
21 return ThreadSafeConsoleLogger()
24def test_initialization(logger: ThreadSafeConsoleLogger) -> None:
25 """Test that logger initializes with empty messages list.
27 Args:
28 logger: ThreadSafeConsoleLogger instance for testing.
29 """
30 assert_that(logger._messages).is_empty()
33def test_console_output_no_color(logger: ThreadSafeConsoleLogger) -> None:
34 """Test console_output without color.
36 Args:
37 logger: ThreadSafeConsoleLogger instance for testing.
38 """
39 with patch("lintro.utils.console.logger.click.echo") as mock_echo:
40 logger.console_output("Test message")
42 mock_echo.assert_called_once_with("Test message")
43 assert_that(logger._messages).contains("Test message")
46def test_console_output_with_color(logger: ThreadSafeConsoleLogger) -> None:
47 """Test console_output with color.
49 Args:
50 logger: ThreadSafeConsoleLogger instance for testing.
51 """
52 with (
53 patch("lintro.utils.console.logger.click.echo") as mock_echo,
54 patch("lintro.utils.console.logger.click.style") as mock_style,
55 ):
56 mock_style.return_value = "styled text"
57 logger.console_output("Test message", color="green")
59 mock_style.assert_called_once_with("Test message", fg="green")
60 mock_echo.assert_called_once_with("styled text")
61 assert_that(logger._messages).contains("Test message")
64def test_info(logger: ThreadSafeConsoleLogger) -> None:
65 """Test info method logs message.
67 Args:
68 logger: ThreadSafeConsoleLogger instance for testing.
69 """
70 with (
71 patch("lintro.utils.console.logger.click.echo") as mock_echo,
72 patch("lintro.utils.console.logger.logger") as mock_logger,
73 ):
74 logger.info("Info message")
76 mock_echo.assert_called_once_with("Info message")
77 mock_logger.info.assert_called_once()
78 assert_that(logger._messages).contains("Info message")
81def test_info_blue(logger: ThreadSafeConsoleLogger) -> None:
82 """Test info_blue method logs in cyan.
84 Args:
85 logger: ThreadSafeConsoleLogger instance for testing.
86 """
87 with (
88 patch("lintro.utils.console.logger.click.echo") as mock_echo,
89 patch("lintro.utils.console.logger.click.style") as mock_style,
90 patch("lintro.utils.console.logger.logger") as mock_logger,
91 ):
92 mock_style.return_value = "styled"
93 logger.info_blue("Blue message")
95 mock_style.assert_called_once_with("Blue message", fg="cyan")
96 mock_echo.assert_called_once_with("styled")
97 mock_logger.info.assert_called_once()
98 assert_that(logger._messages).contains("Blue message")
101def test_success(logger: ThreadSafeConsoleLogger) -> None:
102 """Test success method logs in green with emoji.
104 Args:
105 logger: ThreadSafeConsoleLogger instance for testing.
106 """
107 with (
108 patch("lintro.utils.console.logger.click.echo"),
109 patch("lintro.utils.console.logger.click.style") as mock_style,
110 patch("lintro.utils.console.logger.logger") as mock_logger,
111 ):
112 mock_style.return_value = "styled"
113 logger.success("Success message")
115 mock_style.assert_called_once_with("✅ Success message", fg="green")
116 mock_logger.info.assert_called_once()
117 assert_that(logger._messages).contains("✅ Success message")
120def test_warning(logger: ThreadSafeConsoleLogger) -> None:
121 """Test warning method prefixes with WARNING.
123 Args:
124 logger: ThreadSafeConsoleLogger instance for testing.
125 """
126 with (
127 patch("lintro.utils.console.logger.click.echo"),
128 patch("lintro.utils.console.logger.click.style") as mock_style,
129 patch("lintro.utils.console.logger.logger") as mock_logger,
130 ):
131 mock_style.return_value = "styled"
132 logger.warning("Warning message")
134 mock_style.assert_called_once_with("WARNING: Warning message", fg="yellow")
135 mock_logger.warning.assert_called_once()
136 assert_that(logger._messages).contains("WARNING: Warning message")
139def test_error(logger: ThreadSafeConsoleLogger) -> None:
140 """Test error method prefixes with ERROR.
142 Args:
143 logger: ThreadSafeConsoleLogger instance for testing.
144 """
145 with (
146 patch("lintro.utils.console.logger.click.echo"),
147 patch("lintro.utils.console.logger.click.style") as mock_style,
148 patch("lintro.utils.console.logger.logger") as mock_logger,
149 ):
150 mock_style.return_value = "styled"
151 logger.error("Error message")
153 mock_style.assert_called_once_with("ERROR: Error message", fg="red", bold=True)
154 mock_logger.error.assert_called_once()
155 assert_that(logger._messages).contains("ERROR: Error message")
158def test_save_console_log(logger: ThreadSafeConsoleLogger, tmp_path: Path) -> None:
159 """Test saving console messages to file.
161 Args:
162 logger: ThreadSafeConsoleLogger instance for testing.
163 tmp_path: Temporary directory path for testing.
164 """
165 logger._messages = ["Message 1", "Message 2", "Message 3"]
167 with patch("lintro.utils.console.logger.logger"):
168 logger.save_console_log(tmp_path)
170 log_path = tmp_path / "console.log"
171 assert_that(log_path.exists()).is_true()
172 content = log_path.read_text()
173 assert_that(content).contains("Message 1")
174 assert_that(content).contains("Message 2")
175 assert_that(content).contains("Message 3")
178def test_save_console_log_handles_error(logger: ThreadSafeConsoleLogger) -> None:
179 """Test save_console_log handles write errors gracefully.
181 Args:
182 logger: ThreadSafeConsoleLogger instance for testing.
183 """
184 logger._messages = ["Message"]
186 with (
187 patch("builtins.open", side_effect=OSError("Permission denied")),
188 patch("lintro.utils.console.logger.logger") as mock_logger,
189 ):
190 logger.save_console_log("/invalid/path")
191 mock_logger.error.assert_called_once()
194def test_multiple_messages_tracked(logger: ThreadSafeConsoleLogger) -> None:
195 """Test that multiple messages are tracked in order.
197 Args:
198 logger: ThreadSafeConsoleLogger instance for testing.
199 """
200 with (
201 patch("lintro.utils.console.logger.click.echo"),
202 patch("lintro.utils.console.logger.click.style", return_value="s"),
203 patch("lintro.utils.console.logger.logger"),
204 ):
205 logger.info("First")
206 logger.success("Second")
207 logger.warning("Third")
208 logger.error("Fourth")
210 assert_that(len(logger._messages)).is_equal_to(4)
211 assert_that(logger._messages[0]).is_equal_to("First")
212 assert_that(logger._messages[1]).is_equal_to("✅ Second")
213 assert_that(logger._messages[2]).is_equal_to("WARNING: Third")
214 assert_that(logger._messages[3]).is_equal_to("ERROR: Fourth")
217def test_thread_safe_message_tracking(logger: ThreadSafeConsoleLogger) -> None:
218 """Test that message tracking is thread-safe.
220 Args:
221 logger: ThreadSafeConsoleLogger instance for testing.
222 """
223 import threading
225 messages_to_add = 100
226 threads = []
228 with (
229 patch("lintro.utils.console.logger.click.echo"),
230 patch("lintro.utils.console.logger.click.style", return_value="s"),
231 ):
233 def add_message(i: int) -> None:
234 logger.console_output(f"Message {i}")
236 for i in range(messages_to_add):
237 t = threading.Thread(target=add_message, args=(i,))
238 threads.append(t)
239 t.start()
241 for t in threads:
242 t.join()
244 # All messages should be tracked without loss
245 assert_that(len(logger._messages)).is_equal_to(messages_to_add)