Files
MeshTerm/tests/test_logging.py
T
Jean-Pierre MartineauandClaude Fable 5.1 c9f5a71ce7 Every Python file names its licence with an SPDX identifier
Apache-2.0 does not require per-file headers, but a file lifted out of the tree carried no marking at all. One comment line, first (or right after a shebang), on every .py under meshterm/, tests/, packaging/ and scripts/, plus the extensionless SPI bridge; tests/test_spdx.py keeps a new file from landing without one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 13:09:18 -04:00

111 lines
4.2 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Tests for the log file: plain text, bounded, and holding the things that went wrong.
The log's one real job is being opened by a person who has hit a problem — often the
person reporting it. Every assertion here is about that reader.
"""
from __future__ import annotations
import logging
from pathlib import Path
from rich.console import Console
from meshterm.persistence.logging import (
LOG_FILENAME,
configure_logging,
get_logger,
level_from_name,
log_path,
)
def _read(log_dir: Path) -> str:
for handler in get_logger().handlers:
handler.flush()
return log_path(log_dir).read_text(encoding="utf-8")
def test_the_log_is_plain_text(tmp_path: Path) -> None:
"""One readable line per record: when, how bad, who said it, and what."""
configure_logging(Console(quiet=True), tmp_path, file_level=logging.WARNING, quiet=True)
get_logger("core.connection").warning("link lost, retrying")
line = _read(tmp_path).splitlines()[0]
assert "WARNING" in line
assert "meshterm.core.connection" in line
assert "link lost, retrying" in line
# A date as well as a time. "it happened at 14:40" is useless a week later.
assert line.startswith("20")
def test_the_file_is_named_plainly(tmp_path: Path) -> None:
"""``meshterm.log`` — an extension every text editor already opens."""
assert log_path(tmp_path).name == LOG_FILENAME == "meshterm.log"
def test_the_level_keeps_the_quiet_records_out(tmp_path: Path) -> None:
"""At the default, the file holds problems rather than a narration of a working run.
This is the whole point of the default being WARNING: a log where the dozen useful
lines sit under twenty thousand dull ones is one nobody reads.
"""
configure_logging(Console(quiet=True), tmp_path, file_level=logging.WARNING, quiet=True)
log = get_logger()
log.debug("opened a screen")
log.info("connected to a radio")
log.warning("something went wrong")
written = _read(tmp_path)
assert "something went wrong" in written
assert "opened a screen" not in written
assert "connected to a radio" not in written
def test_turning_it_up_lets_everything_through(tmp_path: Path) -> None:
"""DEBUG is there for chasing something, and keeps what WARNING drops."""
configure_logging(Console(quiet=True), tmp_path, file_level=logging.DEBUG, quiet=True)
get_logger().debug("opened a screen")
assert "opened a screen" in _read(tmp_path)
def test_a_traceback_goes_in_with_the_record(tmp_path: Path) -> None:
"""An exception's traceback is written under the record that reports it.
This is most of why the file is worth attaching to a bug report: the terminal scrolls
away and gets closed, and this copy does not.
"""
configure_logging(Console(quiet=True), tmp_path, file_level=logging.WARNING, quiet=True)
try:
raise ValueError("the device said something impossible")
except ValueError:
get_logger().exception("tool raised")
written = _read(tmp_path)
assert "tool raised" in written
assert "ValueError: the device said something impossible" in written
assert "Traceback (most recent call last)" in written
def test_the_file_is_bounded(tmp_path: Path) -> None:
"""It rotates. An unbounded log reached 11MB in ten weeks on a real install."""
configure_logging(Console(quiet=True), tmp_path, file_level=logging.WARNING, quiet=True)
handler = next(h for h in get_logger().handlers if hasattr(h, "maxBytes"))
assert handler.maxBytes > 0
assert handler.backupCount > 0
def test_a_level_name_becomes_a_level(tmp_path: Path) -> None:
"""The preference stores a name; the handler needs a number."""
assert level_from_name("DEBUG") == logging.DEBUG
assert level_from_name("warning") == logging.WARNING
assert level_from_name(" Error ") == logging.ERROR
def test_a_nonsense_level_still_starts_the_app() -> None:
"""A typo in a hand-edited preferences file loses log detail, not the app.
Someone editing the preferences file by hand and writing ``WARN`` should get a slightly quieter log,
not a program that refuses to launch.
"""
assert level_from_name("WARN") == logging.WARNING
assert level_from_name("") == logging.WARNING