Skip to content

Commit 988c74e

Browse files
committed
test: test general configuration and log output
1 parent 7693314 commit 988c74e

File tree

9 files changed

+226
-3
lines changed

9 files changed

+226
-3
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ lint = [
3030
]
3131
test = [
3232
"pytest>=8.4.2",
33+
"watchdog>=6.0.0",
3334
]
3435

3536
[project.urls]

ruff.toml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
line-length = 108
2+
3+
[format]
4+
line-ending = "lf"
5+
docstring-code-format = true
6+
7+
[lint]
8+
preview = true
9+
select = [
10+
"B", # flake8-bugbear
11+
"C901", # mcabe
12+
"E", # pycodestyle errors
13+
"ERA", # eradicate (commented-out-code)
14+
"F", # pyflakes
15+
"FLY", # flynt
16+
"G", # flake8-logging-format
17+
"I", # isort
18+
"ICN", # flake8-import-conventions
19+
"INT", # flake8-gettext
20+
"ISC", # flake8-implicit-str-concat
21+
"LOG", # flake8-logging
22+
"N", # pep8-naming
23+
"PERF", # Perflint
24+
"PL", # pylint
25+
"Q", # quotes
26+
"RUF", # ruff-specific rules
27+
"SIM", # flake8-simplify
28+
"TC", # flake8-type-checking
29+
"TID251", # banned-api
30+
"TID253", # banned-module-level-imports
31+
"UP", # pyupgrade
32+
"W", # pycodestyle warnings
33+
]
34+
35+
[lint.per-file-ignores]
36+
"src/**/cli/__init__.py" = [
37+
"N813", # camelcase-imported-as-lowercase
38+
]
39+
"tests/test_general.py" = [
40+
"N813", # camelcase-imported-as-lowercase
41+
]
42+
"tests/test_log.py" = [
43+
"N813", # camelcase-imported-as-lowercase
44+
]
45+
"src/readylog/__init__.py" = [
46+
"PLR0913", # too-many-arguments
47+
"PLR0914", # too-many-local-variables
48+
"PLR0917", # too-many-positional-arguments
49+
]
50+
51+
extend-safe-fixes = [
52+
"TC001", # typing-only-first-party-import
53+
"TC002", # typing-only-third-party-import
54+
"TC003", # typing-only-standard-library-import
55+
]
56+
57+
[lint.mccabe]
58+
max-complexity = 5

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from pytest import FixtureRequest, fixture
2+
from watchdog.events import FileModifiedEvent, FileSystemEventHandler
3+
from watchdog.observers import Observer
4+
5+
6+
@fixture
7+
def tmp_log_file(tmp_path):
8+
path = tmp_path / "log" / "debug.log"
9+
path.parent.mkdir(parents=True, exist_ok=False)
10+
return path
11+
12+
13+
@fixture
14+
def app_name():
15+
return "test_app"
16+
17+
18+
@fixture
19+
def log_file_observer(tmp_log_file, request: FixtureRequest) -> Observer:
20+
expected_number_of_log_lines = request.param
21+
22+
class LogFileChangeHandler(FileSystemEventHandler):
23+
@staticmethod
24+
def on_modified(event: FileModifiedEvent):
25+
if event.src_path.endswith(tmp_log_file.name):
26+
pass
27+
28+
observer = Observer()
29+
handler = LogFileChangeHandler()
30+
tmp_log_file.touch()
31+
observer.schedule(handler, path=tmp_log_file, recursive=False)
32+
observer.start()
33+
yield observer
34+
35+
with open(tmp_log_file, encoding="utf-8") as f:
36+
lines = f.readlines()
37+
number_of_log_lines = len(lines)
38+
assert number_of_log_lines == expected_number_of_log_lines
39+
40+
observer.stop()
41+
observer.join()

tests/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from logging import getLevelNamesMapping
2+
3+
IGNORED_LEVEL = "NOTSET"
4+
5+
USABLE_LEVELS = {k: v for k, v in getLevelNamesMapping().items() if k != IGNORED_LEVEL}
6+
USABLE_LEVEL_NAMES = tuple(key for key in USABLE_LEVELS)

tests/test_general.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pytest import mark, param, raises
2+
3+
from readylog import create_dict_config
4+
5+
6+
def test_config_creation(tmp_log_file, app_name):
7+
_ = create_dict_config(tmp_log_file, app_name)
8+
9+
10+
@mark.parametrize(
11+
"kwargs",
12+
(
13+
param({"console_log_level": "TRACE"}, id="console"),
14+
param({"file_log_level": "TRACE"}, id="file"),
15+
param({"console_log_level": "TRACE", "file_log_level": "IMPORTANT"}, id="console, file"),
16+
),
17+
)
18+
def test_config_creation_with_wrong_level(tmp_log_file, app_name, kwargs):
19+
with raises(AssertionError):
20+
_ = create_dict_config(tmp_log_file, app_name, **kwargs)
21+
22+
23+
@mark.parametrize(
24+
"kwargs, expected",
25+
(
26+
param({"console_log_level": "DEBUG", "file_log_level": "INFO"}, "DEBUG", id="debug, info"),
27+
param({"console_log_level": "INFO", "file_log_level": "DEBUG"}, "DEBUG", id="info, debug"),
28+
),
29+
)
30+
def test_logger_level(tmp_log_file, app_name, kwargs, expected):
31+
d = create_dict_config(tmp_log_file, app_name, **kwargs)
32+
actual = d["loggers"][app_name]["level"]
33+
assert actual == expected

tests/test_log.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from logging import Logger
2+
from logging import getLogger as get_logger
3+
from logging.config import dictConfig as configure_logging
4+
from pathlib import Path
5+
6+
from pytest import CaptureFixture, mark, param
7+
from watchdog.observers import Observer
8+
9+
from readylog import create_dict_config
10+
from tests.constants import USABLE_LEVEL_NAMES, USABLE_LEVELS
11+
12+
13+
def produce_log_messages_on_all_levels(logger: Logger) -> None:
14+
for level_name, level in USABLE_LEVELS.items():
15+
message = f"this is a {level_name.lower()} message"
16+
logger.log(level, message)
17+
18+
19+
def expected_number_of_log_lines(limit: int) -> int:
20+
number_of_levels_at_or_above_limit = len(tuple(filter(lambda n: n >= limit, USABLE_LEVELS.values())))
21+
return number_of_levels_at_or_above_limit
22+
23+
24+
@mark.parametrize("log_level", USABLE_LEVEL_NAMES, ids=str.lower)
25+
def test_console_log(tmp_log_file: Path, app_name: str, log_level, capsys: CaptureFixture):
26+
config = create_dict_config(tmp_log_file, app_name, console_log_level=log_level)
27+
configure_logging(config)
28+
logger = get_logger(app_name)
29+
30+
produce_log_messages_on_all_levels(logger)
31+
32+
captured = capsys.readouterr()
33+
lines = captured.err.strip().split("\n")
34+
number_of_lines = len(lines)
35+
36+
limit = USABLE_LEVELS[log_level]
37+
assert number_of_lines == expected_number_of_log_lines(limit)
38+
39+
40+
@mark.parametrize(
41+
"log_file_observer, log_level_name",
42+
(
43+
*(
44+
param(expected_number_of_log_lines(level), level_name, id=level_name.lower())
45+
for level_name, level in USABLE_LEVELS.items()
46+
),
47+
),
48+
indirect=["log_file_observer"],
49+
)
50+
def test_file_log(tmp_log_file: Path, app_name: str, log_file_observer: Observer, log_level_name):
51+
config = create_dict_config(tmp_log_file, app_name, file_log_level=log_level_name)
52+
configure_logging(config)
53+
logger = get_logger(app_name)
54+
55+
produce_log_messages_on_all_levels(logger)

tests/test_readylog.py

Lines changed: 0 additions & 2 deletions
This file was deleted.

uv.lock

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)