Files
logwatcher/tests/test_classifier.py
T
maurane 23afe3f185 feat(classifier): ✨ add classifier feature on log entries
add classifier on log entries, clean test functions and uniform fixtures in windows-1252 since all logs are encoded in windows-1252
2026-08-26 11:34:54 +02:00

139 lines
4.2 KiB
Python

import re
import warnings
from pathlib import Path
import pytest
from logwatcher.classifier import N2_PATTERNS, classify_log_entries
from logwatcher.config import FIXTURE_PATH
from logwatcher.parser import parse_log_file
def _pattern_name_to_filename(name: str) -> str:
"""
Returns the fixture corresponding to the error code name
"""
return f"CR_{name.lower()}.txt"
def _read_fixture(path: Path) -> str:
"""
Read a file with fallback Windows-1252.
"""
try:
content = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
content = path.read_text(encoding="windows-1252")
if "\ufffd" not in content:
return content
return path.read_text(encoding="windows-1252") # TODO: improve coverage score
@pytest.mark.parametrize("name, pattern", N2_PATTERNS.items())
def test_pattern_matches_fixture(valid_log_dir, name, pattern):
"""
Assert each N2 pattern must match at least one line in its fixture.
"""
fixture = valid_log_dir / _pattern_name_to_filename(name)
if not fixture.exists():
msg = f"No fixture named '{fixture}'"
warnings.warn(msg)
pytest.skip(msg)
lignes = [
l for l in _read_fixture(fixture).splitlines()
if l.strip()
]
assert lignes, f"empty fixture file : {fixture}"
match_count = 0
for ligne in lignes:
match = re.search(pattern, ligne)
if match:
match_count += 1
assert match_count > 0, (
f"{name} : no match in file {fixture.name}\n"
f"pattern: {pattern}"
)
def test_patterns_do_not_overlap():
"""
Assert that a log line can match only one N2 pattern.
"""
fixtures_dir = FIXTURE_PATH
for fixture in fixtures_dir.glob("CR_*.txt"):
line_list = [line for line in _read_fixture(fixture).splitlines() if line.strip()] # TODO: improve coverage score
for line in line_list: # TODO: improve coverage score
matching = [ # TODO: improve coverage score
name for name, raw in N2_PATTERNS.items()
if re.compile(raw).search(line)
]
assert len(matching) <= 1, ( # TODO: improve coverage score
f"Chevauchement détecté sur {fixture.name} : "
f"{matching} match line : {line[:80]}"
)
def test_no_orphan_fixtures():
"""
Assert that a fixture exist only if its associated pattern exists.
Aucun fichier de fixture ne doit exister sans pattern associé.
"""
expected = {f"CR_{name.lower()}.txt" for name in N2_PATTERNS}
actual = {f.name for f in FIXTURE_PATH.glob("CR_*.txt")}
orphan = actual - expected
assert not orphan, f"Fixtures orphelines (sans pattern) : {sorted(orphan)}"
def test_classify_log_entries_all_relevant(valid_log_dir: Path):
"""
Must return an empty irrelevant log entry list
"""
log_file = valid_log_dir / "only_relevant_logs.txt"
log_entries = parse_log_file(log_file)
# all entries are relevant
relevant, irrelevant = classify_log_entries(log_entries)
assert relevant and not irrelevant
def test_classify_log_entries_none_relevant(valid_log_dir: Path):
"""
Must return an empty relevant log entry list
"""
log_file = valid_log_dir / "no_relevant_error.txt"
log_entries = parse_log_file(log_file)
# all entries are relevant
relevant, irrelevant = classify_log_entries(log_entries)
assert not relevant and irrelevant
def test_classify_log_entries_mixed(valid_log_dir: Path):
"""
Test classification on relevant and irrelevant log entry list
"""
log_file = valid_log_dir / "mixed_logs.txt"
log_entries = parse_log_file(log_file)
relevant, irrelevant = classify_log_entries(log_entries)
assert len(relevant) and len(irrelevant)
assert len(relevant) + len(irrelevant) == len(log_entries)
def test_classify_log_entries_empty_log_entries(invalid_log_dir: Path):
"""
Test classification on an empty log entry list
"""
empty_file = invalid_log_dir / "empty_file.txt"
log_entries = parse_log_file(empty_file)
relevant, irrelevant = classify_log_entries(log_entries)
assert not relevant and not irrelevant