testsAndMisc-archive/python_pkg/praca_magisterska_video/tests/conftest.py
Krzysztof kuhy Rudnicki e5fd82c822 Reduce per-file-ignores by fixing lint violations across codebase
Fix ruff violations in ~15 source files and ~60+ test files to minimize
per-file-ignores in pyproject.toml. Remaining ignores are justified with
comments explaining why each suppression is necessary.

Source fixes: FBT003 (keyword args), S310 (URL validation), SLF001
(private access), T201 (print→logging), C901 (complexity), E501 (line
length), E402 (import order).

Test fixes: SIM117 (combined with), FBT (boolean args), PERF203 (try in
loop), S310/S607 (URLs/executables), E402/E501 (imports/lines), S108
(tmp paths), PLR0913 (too many args), ARG (unused args), ANN (type
annotations), RUF059 (unused unpacked vars), PT019 (fixture naming).

Remaining per-file-ignores (with justifications):
- Tests: ARG, D, PLC0415, PLR2004, S101, SLF001
- music_gen sources: PLC0415 (heavy ML lazy imports)
- moviepy_showcase: PLC0415 (circular dependency)
- generate_images: PLR0913 (matplotlib helpers need many params)
- praca_magisterska_video: E501, E402 (long paths, mpl.use)
2026-03-25 18:58:05 +01:00

274 lines
8.9 KiB
Python

"""Shared fixtures and moviepy mocking for praca_magisterska_video tests."""
from __future__ import annotations
import contextlib
import importlib
import importlib.util as _ilu
from pathlib import Path
import sys
from typing import TYPE_CHECKING
from unittest.mock import MagicMock
import numpy as np
import pytest
if TYPE_CHECKING:
from collections.abc import Callable
from types import ModuleType
# Add the source directory to sys.path so bare imports like
# ``from _q24_common import ...`` resolve correctly.
_SRC_DIR = str(Path(__file__).resolve().parent.parent)
if _SRC_DIR not in sys.path:
sys.path.insert(0, _SRC_DIR)
# Also add generate_images/ so bare imports like ``from _pubsub_common import ...``
# used by sub-modules within that directory resolve correctly.
_GEN_DIR = str(Path(__file__).resolve().parent.parent / "generate_images")
if _GEN_DIR not in sys.path:
sys.path.insert(0, _GEN_DIR)
def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
"""Build a mapping of module names to mocks for moviepy and heavy deps."""
mocks: dict[str, ModuleType | MagicMock] = {}
# Main moviepy module
moviepy_mod = MagicMock()
# VideoClip: needs to accept make_frame callable -> return mock with methods
def _video_clip_factory(
make_frame: Callable[[float], np.ndarray] | None = None,
duration: float | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.make_frame = make_frame
clip.duration = duration
clip.with_fps.return_value = clip
clip.with_duration.return_value = clip
clip.with_position.return_value = clip
clip.with_effects.return_value = clip
# If there is a make_frame callable, call it to exercise branches
if callable(make_frame) and duration is not None:
frame = make_frame(0.0)
assert isinstance(frame, np.ndarray)
# Call at ~40% progress to hit mid-range branches (e.g. for/else break)
make_frame(duration * 0.4)
# Also call at ~70% progress for branch coverage
make_frame(duration * 0.75)
# Also call near end
make_frame(duration * 0.99)
return clip
moviepy_mod.VideoClip = _video_clip_factory
def _color_clip_factory(
_size: tuple[int, int] | None = None,
_color: tuple[int, ...] | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.with_duration.return_value = clip
return clip
moviepy_mod.ColorClip = _color_clip_factory
def _text_clip_factory(**_kw: object) -> MagicMock:
clip = MagicMock()
clip.with_duration.return_value = clip
clip.with_position.return_value = clip
return clip
moviepy_mod.TextClip = _text_clip_factory
def _composite_factory(
_clips: list[MagicMock] | None = None,
_size: tuple[int, int] | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.with_effects.return_value = clip
clip.with_duration.return_value = clip
clip.write_videofile = MagicMock()
return clip
moviepy_mod.CompositeVideoClip = _composite_factory
def _concat_factory(
_clips: list[MagicMock] | None = None,
_method: str | None = None,
**_kw: object,
) -> MagicMock:
clip = MagicMock()
clip.write_videofile = MagicMock()
return clip
moviepy_mod.concatenate_videoclips = _concat_factory
mocks["moviepy"] = moviepy_mod
mocks["moviepy.video"] = MagicMock()
mocks["moviepy.video.fx"] = MagicMock()
return mocks
# Install mocks at import time so module-level code in source files works.
_MOVIEPY_MOCKS = _make_moviepy_mocks()
for _name, _mock in _MOVIEPY_MOCKS.items():
sys.modules[_name] = _mock
# ---------------------------------------------------------------------------
# Handle the _q24_common name collision.
# Both _SRC_DIR (top-level) and _GEN_DIR (generate_images/) contain a
# file called ``_q24_common.py`` with different contents.
# * top-level → moviepy video helpers (W, H, BG_COLOR, FONT_B, …)
# * gen_images → matplotlib draw helpers (draw_box, draw_arrow, …)
#
# Strategy:
# 1. Load the generate_images version and cache it as bare ``_q24_common``
# so generate_images sub-modules (imported in _BARE_MODULES below)
# find the right one when they do ``from _q24_common import draw_box``.
# 2. After _BARE_MODULES are all imported, swap ``_q24_common`` in
# sys.modules to the top-level version so that top-level source
# modules (``_q24_classical.py``, etc.) find ``BG_COLOR`` etc.
# 3. Register both under their full package paths for coverage.
# ---------------------------------------------------------------------------
# Load generate_images _q24_common first.
_gen_q24_spec = _ilu.spec_from_file_location(
"_q24_common",
str(Path(_GEN_DIR) / "_q24_common.py"),
)
assert _gen_q24_spec is not None
assert _gen_q24_spec.loader is not None
_q24_common_gen = _ilu.module_from_spec(_gen_q24_spec)
# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+.
sys.modules["_q24_common"] = _q24_common_gen
_gen_q24_spec.loader.exec_module(_q24_common_gen)
# Load top-level _q24_common.
_top_q24_spec = _ilu.spec_from_file_location(
"_q24_common_top",
str(Path(_SRC_DIR) / "_q24_common.py"),
)
assert _top_q24_spec is not None
assert _top_q24_spec.loader is not None
_q24_common_top = _ilu.module_from_spec(_top_q24_spec)
# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+.
sys.modules["_q24_common_top"] = _q24_common_top
_top_q24_spec.loader.exec_module(_q24_common_top)
# Register generate_images sub-modules under their full package paths so
# coverage can track them correctly. The bare names are resolved via
# _GEN_DIR added to sys.path above.
_GEN_PKG = "python_pkg.praca_magisterska_video.generate_images"
_BARE_MODULES = [
"_pubsub_common",
"_pubsub_qos",
"_pubsub_topic_content",
"_pubsub_type_hierarchical",
"_q20_common",
"_q20_batch_and_windows",
"_q20_time_monitoring_sessions",
"_q20_platforms",
"_q20_architectures",
"_q20_late_and_decisions",
"generate_pubsub_diagrams",
"generate_q20_diagrams",
"_q23_common",
"_q23_architectures",
"_q23_diy_unet",
"_q23_mean_shift_ncuts",
"_q23_mnemonics",
"_q23_nn_basics",
"_q23_otsu_watershed",
"_q23_receptive_transformer",
"_q23_region_diy",
"generate_q23_diagrams",
"_q24_fpn_tasks_cnn",
"_q24_haar_integral_svm",
"_q24_hog_classical",
"_q24_iou_nms_detector",
"_q24_modern_pipelines",
"_q24_rcnn_yolo",
"generate_q24_diagrams",
"_q31_common",
"_q31_criteria_comparison",
"_q31_ev_spectrum",
"_q31_hurwicz_mnemonic",
"_q31_regret_matrix",
"generate_q31_diagrams",
"_q9_common",
"_q9_basics",
"_q9_classic_sync",
"_q9_ipc",
"_q9_race_deadlock",
"generate_q9_all_diagrams",
"_q9q12_common",
"_q9q12_network_flow",
"_q9q12_network_graph",
"_q9q12_processes",
"generate_q9_q12_diagrams",
"generate_robot_lang_diagrams",
"_robot_movement_ros",
"_robot_pyramid_vendor",
"_robot_ros_rapid",
"_sched_common",
"_sched_complexity_edd",
"_sched_graham",
"_sched_johnson",
"_sched_spt_flow_job",
"generate_scheduling_diagrams",
]
for _bare in _BARE_MODULES:
with contextlib.suppress(ImportError):
_mod = importlib.import_module(_bare)
sys.modules.setdefault(f"{_GEN_PKG}.{_bare}", _mod)
# Now swap _q24_common to the top-level version so that top-level source
# modules (``_q24_classical.py`` etc.) find BG_COLOR, W, H, etc.
sys.modules["_q24_common"] = _q24_common_top
sys.modules.setdefault(
"python_pkg.praca_magisterska_video._q24_common", _q24_common_top
)
sys.modules.setdefault(f"{_GEN_PKG}._q24_common", _q24_common_gen)
def reload_module(module_name: str) -> ModuleType:
"""Force re-import of a module to re-execute its module-level code."""
mod = importlib.import_module(module_name)
return importlib.reload(mod)
@pytest.fixture
def _no_savefig(monkeypatch: pytest.MonkeyPatch) -> None:
"""Prevent matplotlib from writing files to disk."""
import matplotlib.figure
import matplotlib.pyplot as plt
import matplotlib.table
monkeypatch.setattr(matplotlib.figure.Figure, "savefig", lambda *_a, **_kw: None)
monkeypatch.setattr(plt, "savefig", lambda *_a, **_kw: None)
# Source files use auto_set_font_size(auto=False) but matplotlib 3.10+
# renamed the parameter to ``value``.
_orig = matplotlib.table.Table.auto_set_font_size
def _compat_auto_set_font_size(
self: matplotlib.table.Table,
*,
value: bool = True,
**_kw: object,
) -> None:
_orig(self, value)
monkeypatch.setattr(
matplotlib.table.Table,
"auto_set_font_size",
_compat_auto_set_font_size,
)