Files
talkscore-asr/tests/test_console.py
T
Vladimir BryzgalovandClaude Opus 5 6812220d8f Единый формат логов и понятное сообщение о занятом порте
Uvicorn заводил свои обработчики с propagate=False, поэтому его строки шли
мимо нашего формата и рвались посреди слова. log_config=None отдаёт всё
общему обработчику.

Занятый порт сервис теперь замечает до запуска и объясняет, что делать,
вместо строки uvicorn про Errno 10048.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 17:05:38 +05:00

114 lines
4.6 KiB
Python

"""Проверки читаемости консольного вывода."""
import logging
import sys
from unittest.mock import patch
from app.console import ConsoleFormatter, enable_ansi, setup_logging
def record(level: int, message: str) -> logging.LogRecord:
return logging.LogRecord("talkscore-asr", level, __file__, 1, message, None, None)
class TestWrapping:
def test_short_message_stays_on_one_line(self):
out = ConsoleFormatter(colored=False).format(record(logging.INFO, "готово"))
assert out.count("\n") == 0
assert out.endswith("готово")
def test_long_message_wraps(self):
out = ConsoleFormatter(colored=False, width=lambda: 80).format(
record(logging.INFO, "слово " * 60))
assert out.count("\n") >= 1
assert all(len(line) <= 80 for line in out.splitlines())
def test_continuation_aligned_under_message(self):
"""Перенос идёт под текст, а не под начало строки - так видно границу сообщений."""
out = ConsoleFormatter(colored=False, width=lambda: 70).format(
record(logging.WARNING, "слово " * 40))
first, second = out.splitlines()[:2]
head = len(first) - len(first.lstrip()) + first.index("слово")
assert second.startswith(" " * head)
assert second.lstrip()[0] != " "
def test_existing_newlines_are_kept(self):
out = ConsoleFormatter(colored=False).format(
record(logging.INFO, "первая\nвторая"))
assert "первая" in out.splitlines()[0]
assert "вторая" in out.splitlines()[1]
class TestColour:
def test_no_escape_codes_when_colour_off(self):
"""Главное требование: без цвета в выводе не должно быть ANSI-мусора."""
for level in (logging.INFO, logging.WARNING, logging.ERROR):
out = ConsoleFormatter(colored=False).format(record(level, "текст"))
assert "\033" not in out
def test_warning_and_error_are_painted(self):
for level in (logging.WARNING, logging.ERROR):
out = ConsoleFormatter(colored=True).format(record(level, "текст"))
assert "\033[" in out
def test_info_level_is_not_painted(self):
"""Обычные сообщения остаются неокрашенными, иначе цвет перестаёт что-то значить."""
out = ConsoleFormatter(colored=True).format(record(logging.INFO, "текст"))
assert "\033[3" not in out # ни жёлтого, ни красного
class TestAnsiDetection:
def test_disabled_when_output_is_redirected(self):
"""Перенаправленный в файл вывод красить нельзя - в файл попадут коды."""
with patch.object(sys.stdout, "isatty", return_value=False):
assert enable_ansi() is False
class TestSetup:
def test_setup_replaces_handlers(self):
root = logging.getLogger()
before = root.handlers[:]
try:
setup_logging(logging.INFO)
assert len(root.handlers) == 1
assert isinstance(root.handlers[0].formatter, ConsoleFormatter)
finally:
root.handlers[:] = before
class TestPortCheck:
"""Занятый порт должен диагностироваться до запуска, а не строкой Errno 10048."""
def test_free_port_is_not_busy(self):
import socket as sk
from app.main import _port_busy
with sk.socket() as probe:
probe.bind(("127.0.0.1", 0))
free = probe.getsockname()[1]
# Сокет закрыт, порт снова свободен.
assert _port_busy("127.0.0.1", free) is False
def test_listening_port_is_busy(self):
import socket as sk
from app.main import _port_busy
with sk.socket() as server:
server.bind(("127.0.0.1", 0))
server.listen(1)
port = server.getsockname()[1]
assert _port_busy("127.0.0.1", port) is True
def test_wildcard_host_is_probed_via_loopback(self):
"""При host=0.0.0.0 подключаться к 0.0.0.0 нельзя, проверяем петлю."""
import socket as sk
from app.main import _port_busy
with sk.socket() as server:
server.bind(("127.0.0.1", 0))
server.listen(1)
port = server.getsockname()[1]
assert _port_busy("0.0.0.0", port) is True