mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-07-04 15:43:11 +02:00
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)
This commit is contained in:
parent
996617d4a0
commit
e5fd82c822
@ -9,12 +9,14 @@ import geopandas as gpd
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import LineString, Point, Polygon
|
from shapely.geometry import LineString, Point, Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from python_pkg.anki_decks.polish_coastal_features import (
|
from python_pkg.anki_decks.polish_coastal_features import (
|
||||||
polish_coastal_features_anki as _mod,
|
polish_coastal_features_anki as _mod,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_init_worker = _mod._init_worker
|
_init_worker = _mod._init_worker
|
||||||
@ -62,17 +64,26 @@ def _line_feature() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_forests.polish_forests_anki import (
|
from python_pkg.anki_decks.polish_forests.polish_forests_anki import (
|
||||||
@ -58,17 +63,26 @@ def _forests() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_gminy.polish_gminy_anki import (
|
from python_pkg.anki_decks.polish_gminy.polish_gminy_anki import (
|
||||||
@ -59,17 +64,26 @@ def _gminy() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_islands.polish_islands_anki import (
|
from python_pkg.anki_decks.polish_islands.polish_islands_anki import (
|
||||||
@ -73,17 +78,26 @@ def _island_outside() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_lakes.polish_lakes_anki import (
|
from python_pkg.anki_decks.polish_lakes.polish_lakes_anki import (
|
||||||
@ -58,17 +63,26 @@ def _lakes() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import geopandas as gpd
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
import python_pkg.anki_decks.polish_landscape_parks.polish_landscape_parks_anki as _mod
|
import python_pkg.anki_decks.polish_landscape_parks.polish_landscape_parks_anki as _mod
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_init_worker = _mod._init_worker
|
_init_worker = _mod._init_worker
|
||||||
@ -47,17 +49,26 @@ def _parks() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
import importlib
|
import importlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -34,7 +33,7 @@ class TestImportError:
|
|||||||
|
|
||||||
original_import = builtins.__import__
|
original_import = builtins.__import__
|
||||||
|
|
||||||
def mock_import(name: str, *args: Any, **kwargs: Any) -> Any:
|
def mock_import(name: str, *args: object, **kwargs: object) -> object:
|
||||||
if name in ("bs4", "requests"):
|
if name in ("bs4", "requests"):
|
||||||
msg = f"No module named '{name}'"
|
msg = f"No module named '{name}'"
|
||||||
raise ImportError(msg)
|
raise ImportError(msg)
|
||||||
@ -119,7 +118,7 @@ class TestFetchWikipediaHtml:
|
|||||||
)
|
)
|
||||||
def test_returns_cached_data_when_valid(
|
def test_returns_cached_data_when_valid(
|
||||||
self,
|
self,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -144,7 +143,7 @@ class TestFetchWikipediaHtml:
|
|||||||
def test_fetches_fresh_when_cache_read_fails(
|
def test_fetches_fresh_when_cache_read_fails(
|
||||||
self,
|
self,
|
||||||
mock_get: MagicMock,
|
mock_get: MagicMock,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -181,7 +180,7 @@ class TestFetchWikipediaHtml:
|
|||||||
def test_fetches_from_wikipedia_when_cache_invalid(
|
def test_fetches_from_wikipedia_when_cache_invalid(
|
||||||
self,
|
self,
|
||||||
mock_get: MagicMock,
|
mock_get: MagicMock,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -211,7 +210,7 @@ class TestFetchWikipediaHtml:
|
|||||||
def test_force_refresh_ignores_cache(
|
def test_force_refresh_ignores_cache(
|
||||||
self,
|
self,
|
||||||
mock_get: MagicMock,
|
mock_get: MagicMock,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -239,7 +238,7 @@ class TestFetchWikipediaHtml:
|
|||||||
def test_force_refresh_skips_valid_cache(
|
def test_force_refresh_skips_valid_cache(
|
||||||
self,
|
self,
|
||||||
mock_get: MagicMock,
|
mock_get: MagicMock,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -268,7 +267,7 @@ class TestFetchWikipediaHtml:
|
|||||||
def test_raises_runtime_error_on_request_exception(
|
def test_raises_runtime_error_on_request_exception(
|
||||||
self,
|
self,
|
||||||
mock_get: MagicMock,
|
mock_get: MagicMock,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -295,7 +294,7 @@ class TestFetchWikipediaHtml:
|
|||||||
def test_continues_when_cache_write_fails(
|
def test_continues_when_cache_write_fails(
|
||||||
self,
|
self,
|
||||||
mock_get: MagicMock,
|
mock_get: MagicMock,
|
||||||
_mock_valid: MagicMock,
|
mock_valid: MagicMock,
|
||||||
mock_cache_path: MagicMock,
|
mock_cache_path: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Should return data even when cache write fails."""
|
"""Should return data even when cache write fails."""
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from python_pkg.anki_decks.polish_license_plates.fetch_license_plates import (
|
from python_pkg.anki_decks.polish_license_plates.fetch_license_plates import (
|
||||||
@ -34,7 +35,7 @@ class TestFetchWikipediaLicensePlates:
|
|||||||
@patch(f"{MOD}.parse_license_plates_from_html", return_value={"KR": "Kraków"})
|
@patch(f"{MOD}.parse_license_plates_from_html", return_value={"KR": "Kraków"})
|
||||||
@patch(f"{MOD}.fetch_wikipedia_html", return_value="<html></html>")
|
@patch(f"{MOD}.fetch_wikipedia_html", return_value="<html></html>")
|
||||||
def test_force_refresh_passed(
|
def test_force_refresh_passed(
|
||||||
self, mock_fetch: MagicMock, _mock_parse: MagicMock
|
self, mock_fetch: MagicMock, mock_parse: MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
fetch_wikipedia_license_plates(force_refresh=True)
|
fetch_wikipedia_license_plates(force_refresh=True)
|
||||||
mock_fetch.assert_called_once_with(force_refresh=True)
|
mock_fetch.assert_called_once_with(force_refresh=True)
|
||||||
@ -99,7 +100,7 @@ class TestGenerateLicensePlateDataFile:
|
|||||||
class TestMain:
|
class TestMain:
|
||||||
"""Tests for main entry point."""
|
"""Tests for main entry point."""
|
||||||
|
|
||||||
@patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache"))
|
@patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache"))
|
||||||
@patch(f"{MOD}.generate_license_plate_data_file")
|
@patch(f"{MOD}.generate_license_plate_data_file")
|
||||||
@patch(
|
@patch(
|
||||||
f"{MOD}.fetch_wikipedia_license_plates",
|
f"{MOD}.fetch_wikipedia_license_plates",
|
||||||
@ -109,9 +110,9 @@ class TestMain:
|
|||||||
def test_success(
|
def test_success(
|
||||||
self,
|
self,
|
||||||
mock_args: MagicMock,
|
mock_args: MagicMock,
|
||||||
_mock_fetch: MagicMock,
|
mock_fetch: MagicMock,
|
||||||
mock_gen: MagicMock,
|
mock_gen: MagicMock,
|
||||||
_mock_cache: MagicMock,
|
mock_cache: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
mock_args.return_value = MagicMock(force=False)
|
mock_args.return_value = MagicMock(force=False)
|
||||||
with patch("sys.stdout", new_callable=StringIO):
|
with patch("sys.stdout", new_callable=StringIO):
|
||||||
@ -127,14 +128,14 @@ class TestMain:
|
|||||||
def test_runtime_error(
|
def test_runtime_error(
|
||||||
self,
|
self,
|
||||||
mock_args: MagicMock,
|
mock_args: MagicMock,
|
||||||
_mock_fetch: MagicMock,
|
mock_fetch: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
mock_args.return_value = MagicMock(force=False)
|
mock_args.return_value = MagicMock(force=False)
|
||||||
with patch("sys.stderr", new_callable=StringIO):
|
with patch("sys.stderr", new_callable=StringIO):
|
||||||
result = main()
|
result = main()
|
||||||
assert result == 1
|
assert result == 1
|
||||||
|
|
||||||
@patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache"))
|
@patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache"))
|
||||||
@patch(f"{MOD}.generate_license_plate_data_file")
|
@patch(f"{MOD}.generate_license_plate_data_file")
|
||||||
@patch(
|
@patch(
|
||||||
f"{MOD}.fetch_wikipedia_license_plates",
|
f"{MOD}.fetch_wikipedia_license_plates",
|
||||||
@ -145,8 +146,8 @@ class TestMain:
|
|||||||
self,
|
self,
|
||||||
mock_args: MagicMock,
|
mock_args: MagicMock,
|
||||||
mock_fetch: MagicMock,
|
mock_fetch: MagicMock,
|
||||||
_mock_gen: MagicMock,
|
mock_gen: MagicMock,
|
||||||
_mock_cache: MagicMock,
|
mock_cache: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
mock_args.return_value = MagicMock(force=True)
|
mock_args.return_value = MagicMock(force=True)
|
||||||
with patch("sys.stdout", new_callable=StringIO):
|
with patch("sys.stdout", new_callable=StringIO):
|
||||||
@ -154,7 +155,7 @@ class TestMain:
|
|||||||
assert result == 0
|
assert result == 0
|
||||||
mock_fetch.assert_called_once_with(force_refresh=True)
|
mock_fetch.assert_called_once_with(force_refresh=True)
|
||||||
|
|
||||||
@patch(f"{MOD}.get_cache_path", return_value=Path("/tmp/cache"))
|
@patch(f"{MOD}.get_cache_path", return_value=Path(tempfile.gettempdir(), "cache"))
|
||||||
@patch(f"{MOD}.generate_license_plate_data_file")
|
@patch(f"{MOD}.generate_license_plate_data_file")
|
||||||
@patch(
|
@patch(
|
||||||
f"{MOD}.fetch_wikipedia_license_plates",
|
f"{MOD}.fetch_wikipedia_license_plates",
|
||||||
@ -164,9 +165,9 @@ class TestMain:
|
|||||||
def test_prints_summary(
|
def test_prints_summary(
|
||||||
self,
|
self,
|
||||||
mock_args: MagicMock,
|
mock_args: MagicMock,
|
||||||
_mock_fetch: MagicMock,
|
mock_fetch: MagicMock,
|
||||||
_mock_gen: MagicMock,
|
mock_gen: MagicMock,
|
||||||
_mock_cache: MagicMock,
|
mock_cache: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
mock_args.return_value = MagicMock(force=False)
|
mock_args.return_value = MagicMock(force=False)
|
||||||
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
|
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
|
||||||
|
|||||||
@ -3,12 +3,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Point, Polygon
|
from shapely.geometry import Point, Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_mountain_peaks.polish_mountain_peaks_anki import (
|
from python_pkg.anki_decks.polish_mountain_peaks.polish_mountain_peaks_anki import (
|
||||||
@ -58,17 +63,26 @@ def _peaks() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import geopandas as gpd
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
import python_pkg.anki_decks.polish_mountain_ranges.polish_mountain_ranges_anki as _mod
|
import python_pkg.anki_decks.polish_mountain_ranges.polish_mountain_ranges_anki as _mod
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_init_worker = _mod._init_worker
|
_init_worker = _mod._init_worker
|
||||||
@ -49,17 +51,26 @@ def _ranges() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
@ -10,6 +11,11 @@ import matplotlib.pyplot as plt
|
|||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_national_parks.polish_national_parks_anki import (
|
from python_pkg.anki_decks.polish_national_parks.polish_national_parks_anki import (
|
||||||
_init_worker,
|
_init_worker,
|
||||||
@ -73,17 +79,26 @@ def _small_park() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import geopandas as gpd
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import Polygon
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
import python_pkg.anki_decks.polish_nature_reserves.polish_nature_reserves_anki as _mod
|
import python_pkg.anki_decks.polish_nature_reserves.polish_nature_reserves_anki as _mod
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_init_worker = _mod._init_worker
|
_init_worker = _mod._init_worker
|
||||||
@ -47,17 +49,26 @@ def _reserves() -> gpd.GeoDataFrame:
|
|||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(self, processes=None, initializer=None, initargs=()) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
_processes: int | None = None,
|
||||||
|
initializer: Callable[..., object] | None = None,
|
||||||
|
initargs: tuple[object, ...] = (),
|
||||||
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(self, func, items):
|
def imap_unordered(
|
||||||
|
self,
|
||||||
|
func: Callable[[object], object],
|
||||||
|
items: Iterable[object],
|
||||||
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *_args: object) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
@ -12,6 +12,9 @@ import pytest
|
|||||||
from shapely.geometry import LineString, Polygon
|
from shapely.geometry import LineString, Polygon
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_rivers.polish_rivers_anki import (
|
from python_pkg.anki_decks.polish_rivers.polish_rivers_anki import (
|
||||||
_init_worker,
|
_init_worker,
|
||||||
@ -77,18 +80,18 @@ def _river_outside() -> gpd.GeoDataFrame:
|
|||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
processes: int | None = None,
|
_processes: int | None = None,
|
||||||
initializer: Any = None,
|
initializer: Callable[..., object] | None = None,
|
||||||
initargs: tuple[Any, ...] = (),
|
initargs: tuple[object, ...] = (),
|
||||||
) -> None:
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(
|
def imap_unordered(
|
||||||
self,
|
self,
|
||||||
func: Any,
|
func: Callable[[object], object],
|
||||||
items: Any,
|
items: Iterable[object],
|
||||||
) -> list[Any]:
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self) -> Self:
|
def __enter__(self) -> Self:
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
@ -12,6 +12,9 @@ import pytest
|
|||||||
from shapely.geometry import Point, Polygon
|
from shapely.geometry import Point, Polygon
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_pkg.anki_decks.polish_unesco_sites.polish_unesco_sites_anki import (
|
from python_pkg.anki_decks.polish_unesco_sites.polish_unesco_sites_anki import (
|
||||||
_init_worker,
|
_init_worker,
|
||||||
@ -79,18 +82,18 @@ def _site_polygon() -> gpd.GeoDataFrame:
|
|||||||
class _FakePool:
|
class _FakePool:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
processes: int | None = None,
|
_processes: int | None = None,
|
||||||
initializer: Any = None,
|
initializer: Callable[..., object] | None = None,
|
||||||
initargs: tuple[Any, ...] = (),
|
initargs: tuple[object, ...] = (),
|
||||||
) -> None:
|
) -> None:
|
||||||
if initializer:
|
if initializer:
|
||||||
initializer(*initargs)
|
initializer(*initargs)
|
||||||
|
|
||||||
def imap_unordered(
|
def imap_unordered(
|
||||||
self,
|
self,
|
||||||
func: Any,
|
func: Callable[[object], object],
|
||||||
items: Any,
|
items: Iterable[object],
|
||||||
) -> list[Any]:
|
) -> list[object]:
|
||||||
return [func(item) for item in items]
|
return [func(item) for item in items]
|
||||||
|
|
||||||
def __enter__(self) -> Self:
|
def __enter__(self) -> Self:
|
||||||
|
|||||||
@ -167,7 +167,7 @@ class TestLoadStreetData:
|
|||||||
patch.object(_mod_ref, "__file__", str(fake_file)),
|
patch.object(_mod_ref, "__file__", str(fake_file)),
|
||||||
patch(f"{_MOD}.get_warsaw_streets", return_value=_street_segments_gdf()),
|
patch(f"{_MOD}.get_warsaw_streets", return_value=_street_segments_gdf()),
|
||||||
):
|
):
|
||||||
streets, boundary = load_street_data()
|
_, boundary = load_street_data()
|
||||||
assert len(boundary) == 1
|
assert len(boundary) == 1
|
||||||
|
|
||||||
def test_file_not_found(self, tmp_path: Path) -> None:
|
def test_file_not_found(self, tmp_path: Path) -> None:
|
||||||
|
|||||||
@ -1,37 +1,78 @@
|
|||||||
"""Integration tests for the articles C server API."""
|
"""Integration tests for the articles C server API."""
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
import http.client
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import urllib.error
|
import urllib.parse
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
class _HTTPError(Exception):
|
||||||
|
"""HTTP error with status code."""
|
||||||
|
|
||||||
|
def __init__(self, code: int) -> None:
|
||||||
|
super().__init__(f"HTTP {code}")
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
def _req(
|
def _req(
|
||||||
url: str, method: str = "GET", data: dict[str, Any] | bytes | None = None
|
url: str, method: str = "GET", data: dict[str, Any] | bytes | None = None
|
||||||
) -> tuple[int, bytes]:
|
) -> tuple[int, bytes]:
|
||||||
"""Send an HTTP request and return status code and body."""
|
"""Send an HTTP request and return status code and body."""
|
||||||
if data is not None and not isinstance(data, bytes | bytearray):
|
if data is not None and not isinstance(data, bytes | bytearray):
|
||||||
data = json.dumps(data).encode("utf-8")
|
data = json.dumps(data).encode("utf-8")
|
||||||
req = urllib.request.Request(url, data=data, method=method)
|
parsed = urllib.parse.urlparse(url)
|
||||||
req.add_header("Content-Type", "application/json")
|
conn = http.client.HTTPConnection(parsed.hostname, parsed.port, timeout=5)
|
||||||
with urllib.request.urlopen(req, timeout=5) as resp:
|
try:
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
conn.request(method, parsed.path or "/", body=data, headers=headers)
|
||||||
|
resp = conn.getresponse()
|
||||||
body = resp.read()
|
body = resp.read()
|
||||||
return resp.getcode(), body
|
status = resp.status
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
if status >= 400:
|
||||||
|
raise _HTTPError(status)
|
||||||
|
return status, body
|
||||||
|
|
||||||
|
|
||||||
|
def _probe_server(host: str, port: int) -> bool:
|
||||||
|
"""Try a single GET to the server. Return True if it responded."""
|
||||||
|
try:
|
||||||
|
conn = http.client.HTTPConnection(host, port, timeout=0.2)
|
||||||
|
try:
|
||||||
|
conn.request("GET", "/api/articles")
|
||||||
|
conn.getresponse().read()
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_server(host: str, port: int, attempts: int = 30) -> None:
|
||||||
|
"""Poll the server until it responds or attempts are exhausted."""
|
||||||
|
for _ in range(attempts):
|
||||||
|
if _probe_server(host, port):
|
||||||
|
return
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
def test_crud_roundtrip(tmp_path: Path) -> None:
|
def test_crud_roundtrip(tmp_path: Path) -> None:
|
||||||
"""Test full CRUD lifecycle for articles API."""
|
"""Test full CRUD lifecycle for articles API."""
|
||||||
# Build C server
|
# Build C server
|
||||||
here = Path(__file__).resolve().parent.parent
|
here = Path(__file__).resolve().parent.parent
|
||||||
subprocess.run(["make", "-s", "server_c"], check=True, cwd=str(here))
|
make_path = shutil.which("make")
|
||||||
|
assert make_path is not None, "make not found in PATH"
|
||||||
|
subprocess.run([make_path, "-s", "server_c"], check=True, cwd=str(here))
|
||||||
|
|
||||||
# Find a free port
|
# Find a free port
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
@ -47,16 +88,7 @@ def test_crud_roundtrip(tmp_path: Path) -> None:
|
|||||||
env["PORT"] = str(port)
|
env["PORT"] = str(port)
|
||||||
srv = subprocess.Popen(["./server_c"], cwd=str(here), env=env)
|
srv = subprocess.Popen(["./server_c"], cwd=str(here), env=env)
|
||||||
try:
|
try:
|
||||||
# wait briefly for server to be ready
|
_wait_for_server(host, port)
|
||||||
for _ in range(30):
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(
|
|
||||||
base + "/api/articles", timeout=0.2
|
|
||||||
) as resp:
|
|
||||||
resp.read()
|
|
||||||
break
|
|
||||||
except (OSError, urllib.error.URLError):
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
# Create
|
# Create
|
||||||
code, body = _req(
|
code, body = _req(
|
||||||
@ -97,10 +129,9 @@ def test_crud_roundtrip(tmp_path: Path) -> None:
|
|||||||
assert code == HTTPStatus.NO_CONTENT
|
assert code == HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
# Ensure gone
|
# Ensure gone
|
||||||
with pytest.raises(urllib.error.HTTPError) as exc_info:
|
with pytest.raises(_HTTPError) as exc_info:
|
||||||
_req(base + f"/api/articles/{art_id}")
|
_req(base + f"/api/articles/{art_id}")
|
||||||
assert exc_info.value.code == HTTPStatus.NOT_FOUND
|
assert exc_info.value.code == HTTPStatus.NOT_FOUND
|
||||||
exc_info.value.close()
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
srv.terminate()
|
srv.terminate()
|
||||||
|
|||||||
@ -20,12 +20,12 @@ class TestFindAlsDevice:
|
|||||||
"glob",
|
"glob",
|
||||||
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
|
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
|
||||||
)
|
)
|
||||||
def test_found(self, _mock_glob: MagicMock) -> None:
|
def test_found(self, mock_glob: MagicMock) -> None:
|
||||||
result = auto_brightness_daemon._find_als_device()
|
result = auto_brightness_daemon._find_als_device()
|
||||||
assert result == Path("/sys/bus/iio/devices/iio0")
|
assert result == Path("/sys/bus/iio/devices/iio0")
|
||||||
|
|
||||||
@patch.object(Path, "glob", return_value=[])
|
@patch.object(Path, "glob", return_value=[])
|
||||||
def test_not_found(self, _mock_glob: MagicMock) -> None:
|
def test_not_found(self, mock_glob: MagicMock) -> None:
|
||||||
assert auto_brightness_daemon._find_als_device() is None
|
assert auto_brightness_daemon._find_als_device() is None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ class TestMainNoAls:
|
|||||||
"""Tests for main() when no ALS device is found."""
|
"""Tests for main() when no ALS device is found."""
|
||||||
|
|
||||||
@patch(f"{MOD}._find_als_device", return_value=None)
|
@patch(f"{MOD}._find_als_device", return_value=None)
|
||||||
def test_exits_when_no_als(self, _mock_find: MagicMock) -> None:
|
def test_exits_when_no_als(self, mock_find: MagicMock) -> None:
|
||||||
with pytest.raises(SystemExit, match="1"):
|
with pytest.raises(SystemExit, match="1"):
|
||||||
auto_brightness_daemon.main()
|
auto_brightness_daemon.main()
|
||||||
|
|
||||||
@ -62,10 +62,10 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}._lux_to_brightness", return_value=75),
|
patch(f"{MOD}._lux_to_brightness", return_value=75),
|
||||||
patch(f"{MOD}._get_brightness", return_value=current_brightness),
|
patch(f"{MOD}._get_brightness", return_value=current_brightness),
|
||||||
patch(f"{MOD}._set_brightness", mock_set_brightness),
|
patch(f"{MOD}._set_brightness", mock_set_brightness),
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
# Simulate SIGINT by raising KeyboardInterrupt in sleep
|
# Simulate SIGINT by raising KeyboardInterrupt in sleep
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
|
|
||||||
return mock_set_brightness, mock_lux
|
return mock_set_brightness, mock_lux
|
||||||
|
|
||||||
@ -96,9 +96,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}._lux_to_brightness", return_value=74),
|
patch(f"{MOD}._lux_to_brightness", return_value=74),
|
||||||
patch(f"{MOD}._get_brightness", return_value=74),
|
patch(f"{MOD}._get_brightness", return_value=74),
|
||||||
patch(f"{MOD}._set_brightness") as mock_set,
|
patch(f"{MOD}._set_brightness") as mock_set,
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
mock_set.assert_not_called()
|
mock_set.assert_not_called()
|
||||||
|
|
||||||
def test_skips_when_brightness_negative(self) -> None:
|
def test_skips_when_brightness_negative(self) -> None:
|
||||||
@ -116,9 +116,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}._lux_to_brightness", return_value=75),
|
patch(f"{MOD}._lux_to_brightness", return_value=75),
|
||||||
patch(f"{MOD}._get_brightness", return_value=-1),
|
patch(f"{MOD}._get_brightness", return_value=-1),
|
||||||
patch(f"{MOD}._set_brightness") as mock_set,
|
patch(f"{MOD}._set_brightness") as mock_set,
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
mock_set.assert_not_called()
|
mock_set.assert_not_called()
|
||||||
|
|
||||||
def test_creates_control_file_when_missing(self) -> None:
|
def test_creates_control_file_when_missing(self) -> None:
|
||||||
@ -133,9 +133,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}.signal.signal"),
|
patch(f"{MOD}.signal.signal"),
|
||||||
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
|
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
|
||||||
patch(f"{MOD}._is_enabled", return_value=False),
|
patch(f"{MOD}._is_enabled", return_value=False),
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
mock_set_enabled.assert_called_once_with(enabled=True)
|
mock_set_enabled.assert_called_once_with(enabled=True)
|
||||||
|
|
||||||
def test_does_not_create_file_when_exists(self) -> None:
|
def test_does_not_create_file_when_exists(self) -> None:
|
||||||
@ -150,9 +150,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}.signal.signal"),
|
patch(f"{MOD}.signal.signal"),
|
||||||
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
|
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
|
||||||
patch(f"{MOD}._is_enabled", return_value=False),
|
patch(f"{MOD}._is_enabled", return_value=False),
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
mock_set_enabled.assert_not_called()
|
mock_set_enabled.assert_not_called()
|
||||||
|
|
||||||
def test_handles_exception_in_loop_gracefully(self) -> None:
|
def test_handles_exception_in_loop_gracefully(self) -> None:
|
||||||
@ -166,9 +166,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}.signal.signal"),
|
patch(f"{MOD}.signal.signal"),
|
||||||
patch(f"{MOD}.time.sleep", side_effect=[None, KeyboardInterrupt]),
|
patch(f"{MOD}.time.sleep", side_effect=[None, KeyboardInterrupt]),
|
||||||
patch(f"{MOD}._is_enabled", side_effect=OSError("disk fail")),
|
patch(f"{MOD}._is_enabled", side_effect=OSError("disk fail")),
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
# No crash = exception was handled
|
# No crash = exception was handled
|
||||||
|
|
||||||
def test_signal_handler_stops_loop(self) -> None:
|
def test_signal_handler_stops_loop(self) -> None:
|
||||||
@ -189,9 +189,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}.signal.signal", side_effect=capture_signal),
|
patch(f"{MOD}.signal.signal", side_effect=capture_signal),
|
||||||
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
|
patch(f"{MOD}.time.sleep", side_effect=KeyboardInterrupt),
|
||||||
patch(f"{MOD}._is_enabled", return_value=False),
|
patch(f"{MOD}._is_enabled", return_value=False),
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
|
|
||||||
# Verify we captured a SIGTERM handler
|
# Verify we captured a SIGTERM handler
|
||||||
assert signal.SIGTERM in captured_handler
|
assert signal.SIGTERM in captured_handler
|
||||||
@ -217,9 +217,9 @@ class TestMainDaemonLoop:
|
|||||||
patch(f"{MOD}._lux_to_brightness", return_value=10),
|
patch(f"{MOD}._lux_to_brightness", return_value=10),
|
||||||
patch(f"{MOD}._get_brightness", return_value=90),
|
patch(f"{MOD}._get_brightness", return_value=90),
|
||||||
patch(f"{MOD}._set_brightness") as mock_set,
|
patch(f"{MOD}._set_brightness") as mock_set,
|
||||||
|
contextlib.suppress(KeyboardInterrupt),
|
||||||
):
|
):
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
auto_brightness_daemon.main()
|
||||||
auto_brightness_daemon.main()
|
|
||||||
# delta=-80, step=-5, new_val=85
|
# delta=-80, step=-5, new_val=85
|
||||||
mock_set.assert_called_with(85)
|
mock_set.assert_called_with(85)
|
||||||
|
|
||||||
|
|||||||
@ -20,12 +20,12 @@ class TestFindAlsDevice:
|
|||||||
"glob",
|
"glob",
|
||||||
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
|
return_value=[Path("/sys/bus/iio/devices/iio0/in_illuminance_raw")],
|
||||||
)
|
)
|
||||||
def test_found(self, _mock_glob: MagicMock) -> None:
|
def test_found(self, mock_glob: MagicMock) -> None:
|
||||||
result = brightness_controller._find_als_device()
|
result = brightness_controller._find_als_device()
|
||||||
assert result == Path("/sys/bus/iio/devices/iio0")
|
assert result == Path("/sys/bus/iio/devices/iio0")
|
||||||
|
|
||||||
@patch.object(Path, "glob", return_value=[])
|
@patch.object(Path, "glob", return_value=[])
|
||||||
def test_not_found(self, _mock_glob: MagicMock) -> None:
|
def test_not_found(self, mock_glob: MagicMock) -> None:
|
||||||
assert brightness_controller._find_als_device() is None
|
assert brightness_controller._find_als_device() is None
|
||||||
|
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ class TestRefreshBrightness:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=75,
|
return_value=75,
|
||||||
)
|
)
|
||||||
def test_updates_ui(self, _mock_get: MagicMock) -> None:
|
def test_updates_ui(self, mock_get: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl.slider_var = MagicMock()
|
ctrl.slider_var = MagicMock()
|
||||||
@ -339,7 +339,7 @@ class TestRefreshBrightness:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=-1,
|
return_value=-1,
|
||||||
)
|
)
|
||||||
def test_error(self, _mock_get: MagicMock) -> None:
|
def test_error(self, mock_get: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl._refresh_brightness()
|
ctrl._refresh_brightness()
|
||||||
@ -378,7 +378,7 @@ class TestOnSliderMove:
|
|||||||
ctrl.pct_var.set.assert_not_called()
|
ctrl.pct_var.set.assert_not_called()
|
||||||
|
|
||||||
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
|
@patch("python_pkg.brightness_controller.brightness_controller._set_brightness")
|
||||||
def test_disables_auto_mode(self, _mock_set: MagicMock) -> None:
|
def test_disables_auto_mode(self, mock_set: MagicMock) -> None:
|
||||||
ctrl = _make_controller(daemon_state=True)
|
ctrl = _make_controller(daemon_state=True)
|
||||||
ctrl.auto_mode = True
|
ctrl.auto_mode = True
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
@ -396,7 +396,7 @@ class TestSetPct:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=25,
|
return_value=25,
|
||||||
)
|
)
|
||||||
def test_sets_brightness(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
|
def test_sets_brightness(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl.slider_var = MagicMock()
|
ctrl.slider_var = MagicMock()
|
||||||
@ -417,7 +417,7 @@ class TestDecrease:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=50,
|
return_value=50,
|
||||||
)
|
)
|
||||||
def test_decrease(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
|
def test_decrease(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl.slider_var = MagicMock()
|
ctrl.slider_var = MagicMock()
|
||||||
@ -429,7 +429,7 @@ class TestDecrease:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=2,
|
return_value=2,
|
||||||
)
|
)
|
||||||
def test_clamps_to_zero(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
|
def test_clamps_to_zero(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl.slider_var = MagicMock()
|
ctrl.slider_var = MagicMock()
|
||||||
@ -449,7 +449,7 @@ class TestIncrease:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=50,
|
return_value=50,
|
||||||
)
|
)
|
||||||
def test_increase(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
|
def test_increase(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl.slider_var = MagicMock()
|
ctrl.slider_var = MagicMock()
|
||||||
@ -461,7 +461,7 @@ class TestIncrease:
|
|||||||
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
"python_pkg.brightness_controller.brightness_controller._get_brightness",
|
||||||
return_value=98,
|
return_value=98,
|
||||||
)
|
)
|
||||||
def test_clamps_to_100(self, _mock_get: MagicMock, mock_set: MagicMock) -> None:
|
def test_clamps_to_100(self, mock_get: MagicMock, mock_set: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
ctrl.slider_var = MagicMock()
|
ctrl.slider_var = MagicMock()
|
||||||
|
|||||||
@ -91,7 +91,7 @@ class TestPollAls:
|
|||||||
"""Tests for _poll_als."""
|
"""Tests for _poll_als."""
|
||||||
|
|
||||||
@patch(f"{MOD}._read_lux", return_value=42.5)
|
@patch(f"{MOD}._read_lux", return_value=42.5)
|
||||||
def test_updates_lux_display(self, _mock_lux: MagicMock) -> None:
|
def test_updates_lux_display(self, mock_lux: MagicMock) -> None:
|
||||||
ctrl = _make_controller(als_path=Path("/fake"))
|
ctrl = _make_controller(als_path=Path("/fake"))
|
||||||
ctrl.lux_var = MagicMock()
|
ctrl.lux_var = MagicMock()
|
||||||
ctrl.root = MagicMock()
|
ctrl.root = MagicMock()
|
||||||
@ -105,7 +105,7 @@ class TestPollAls:
|
|||||||
ctrl.root.after.assert_called_once()
|
ctrl.root.after.assert_called_once()
|
||||||
|
|
||||||
@patch(f"{MOD}._read_lux", side_effect=OSError("sensor fail"))
|
@patch(f"{MOD}._read_lux", side_effect=OSError("sensor fail"))
|
||||||
def test_sensor_error(self, _mock_lux: MagicMock) -> None:
|
def test_sensor_error(self, mock_lux: MagicMock) -> None:
|
||||||
ctrl = _make_controller(als_path=Path("/fake"))
|
ctrl = _make_controller(als_path=Path("/fake"))
|
||||||
ctrl.lux_var = MagicMock()
|
ctrl.lux_var = MagicMock()
|
||||||
ctrl.root = MagicMock()
|
ctrl.root = MagicMock()
|
||||||
@ -118,7 +118,7 @@ class TestPollAls:
|
|||||||
ctrl.lux_var.set.assert_called_with("sensor error")
|
ctrl.lux_var.set.assert_called_with("sensor error")
|
||||||
|
|
||||||
@patch(f"{MOD}._read_lux", side_effect=ValueError("bad value"))
|
@patch(f"{MOD}._read_lux", side_effect=ValueError("bad value"))
|
||||||
def test_sensor_value_error(self, _mock_lux: MagicMock) -> None:
|
def test_sensor_value_error(self, mock_lux: MagicMock) -> None:
|
||||||
ctrl = _make_controller(als_path=Path("/fake"))
|
ctrl = _make_controller(als_path=Path("/fake"))
|
||||||
ctrl.lux_var = MagicMock()
|
ctrl.lux_var = MagicMock()
|
||||||
ctrl.root = MagicMock()
|
ctrl.root = MagicMock()
|
||||||
@ -131,7 +131,7 @@ class TestPollAls:
|
|||||||
ctrl.lux_var.set.assert_called_with("sensor error")
|
ctrl.lux_var.set.assert_called_with("sensor error")
|
||||||
|
|
||||||
@patch(f"{MOD}._read_lux", return_value=10.0)
|
@patch(f"{MOD}._read_lux", return_value=10.0)
|
||||||
def test_syncs_daemon_state_change(self, _mock_lux: MagicMock) -> None:
|
def test_syncs_daemon_state_change(self, mock_lux: MagicMock) -> None:
|
||||||
"""When daemon state differs from auto_mode, syncs it."""
|
"""When daemon state differs from auto_mode, syncs it."""
|
||||||
ctrl = _make_controller(als_path=Path("/fake"))
|
ctrl = _make_controller(als_path=Path("/fake"))
|
||||||
ctrl.auto_mode = False
|
ctrl.auto_mode = False
|
||||||
@ -148,7 +148,7 @@ class TestPollAls:
|
|||||||
assert ctrl.auto_mode is True
|
assert ctrl.auto_mode is True
|
||||||
|
|
||||||
@patch(f"{MOD}._read_lux", return_value=10.0)
|
@patch(f"{MOD}._read_lux", return_value=10.0)
|
||||||
def test_no_sync_when_same(self, _mock_lux: MagicMock) -> None:
|
def test_no_sync_when_same(self, mock_lux: MagicMock) -> None:
|
||||||
"""When daemon state matches auto_mode, no sync needed."""
|
"""When daemon state matches auto_mode, no sync needed."""
|
||||||
ctrl = _make_controller(als_path=Path("/fake"))
|
ctrl = _make_controller(als_path=Path("/fake"))
|
||||||
ctrl.auto_mode = False
|
ctrl.auto_mode = False
|
||||||
@ -177,7 +177,7 @@ class TestPollBrightness:
|
|||||||
"""Tests for _poll_brightness."""
|
"""Tests for _poll_brightness."""
|
||||||
|
|
||||||
@patch(f"{MOD}._get_brightness", return_value=60)
|
@patch(f"{MOD}._get_brightness", return_value=60)
|
||||||
def test_refreshes_when_not_auto(self, _mock_get: MagicMock) -> None:
|
def test_refreshes_when_not_auto(self, mock_get: MagicMock) -> None:
|
||||||
ctrl = _make_controller()
|
ctrl = _make_controller()
|
||||||
ctrl.auto_mode = False
|
ctrl.auto_mode = False
|
||||||
ctrl.pct_var = MagicMock()
|
ctrl.pct_var = MagicMock()
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, mock_open, patch
|
from unittest.mock import MagicMock, mock_open, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -24,6 +24,9 @@ from python_pkg.cinema_planner._cinema_parsing import (
|
|||||||
parse_time,
|
parse_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
|
||||||
class TestParseTime:
|
class TestParseTime:
|
||||||
"""Tests for parse_time."""
|
"""Tests for parse_time."""
|
||||||
@ -208,13 +211,13 @@ class TestParseCinemaCityHtml:
|
|||||||
f"{times_html}"
|
f"{times_html}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _patch_open(self, html: str) -> Any:
|
def _patch_open(self, html: str) -> contextlib.AbstractContextManager[MagicMock]:
|
||||||
return patch.object(Path, "open", mock_open(read_data=html))
|
return patch.object(Path, "open", mock_open(read_data=html))
|
||||||
|
|
||||||
def test_parse_single_movie(self) -> None:
|
def test_parse_single_movie(self) -> None:
|
||||||
html = "header" + self._make_html_section("Movie A", 120, ["10:00", "14:00"])
|
html = "header" + self._make_html_section("Movie A", 120, ["10:00", "14:00"])
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
movies, _ = parse_cinema_city_html("test.html")
|
||||||
assert len(movies) == 1
|
assert len(movies) == 1
|
||||||
assert movies[0].name == "Movie A"
|
assert movies[0].name == "Movie A"
|
||||||
assert movies[0].duration == 120
|
assert movies[0].duration == 120
|
||||||
@ -223,7 +226,7 @@ class TestParseCinemaCityHtml:
|
|||||||
def test_parse_with_date(self) -> None:
|
def test_parse_with_date(self) -> None:
|
||||||
html = "2025-01-25 stuff" + self._make_html_section("Movie A", 90, ["18:00"])
|
html = "2025-01-25 stuff" + self._make_html_section("Movie A", 90, ["18:00"])
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
_, date = parse_cinema_city_html("test.html")
|
||||||
assert date == "2025-01-25"
|
assert date == "2025-01-25"
|
||||||
|
|
||||||
def test_parse_with_genres(self) -> None:
|
def test_parse_with_genres(self) -> None:
|
||||||
@ -231,7 +234,7 @@ class TestParseCinemaCityHtml:
|
|||||||
"Horror Film", 100, ["20:00"], genre="Horror, Thriller"
|
"Horror Film", 100, ["20:00"], genre="Horror, Thriller"
|
||||||
)
|
)
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
movies, _ = parse_cinema_city_html("test.html")
|
||||||
assert len(movies) == 1
|
assert len(movies) == 1
|
||||||
assert "Horror" in movies[0].genres
|
assert "Horror" in movies[0].genres
|
||||||
assert "Thriller" in movies[0].genres
|
assert "Thriller" in movies[0].genres
|
||||||
@ -239,7 +242,7 @@ class TestParseCinemaCityHtml:
|
|||||||
def test_no_name_match(self) -> None:
|
def test_no_name_match(self) -> None:
|
||||||
html = 'header class="row movie-row"> no name here'
|
html = 'header class="row movie-row"> no name here'
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
movies, _ = parse_cinema_city_html("test.html")
|
||||||
assert len(movies) == 0
|
assert len(movies) == 0
|
||||||
|
|
||||||
def test_no_duration_match(self) -> None:
|
def test_no_duration_match(self) -> None:
|
||||||
@ -250,7 +253,7 @@ class TestParseCinemaCityHtml:
|
|||||||
'<button class="btn btn-primary btn-lg">10:00</button>'
|
'<button class="btn btn-primary btn-lg">10:00</button>'
|
||||||
)
|
)
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
movies, _ = parse_cinema_city_html("test.html")
|
||||||
assert len(movies) == 0
|
assert len(movies) == 0
|
||||||
|
|
||||||
def test_no_times_match(self) -> None:
|
def test_no_times_match(self) -> None:
|
||||||
@ -260,7 +263,7 @@ class TestParseCinemaCityHtml:
|
|||||||
"<span>100 min</span>"
|
"<span>100 min</span>"
|
||||||
)
|
)
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
movies, _ = parse_cinema_city_html("test.html")
|
||||||
assert len(movies) == 0
|
assert len(movies) == 0
|
||||||
|
|
||||||
def test_alternate_time_pattern(self) -> None:
|
def test_alternate_time_pattern(self) -> None:
|
||||||
@ -271,7 +274,7 @@ class TestParseCinemaCityHtml:
|
|||||||
"> 10:00 (HTTPS://something"
|
"> 10:00 (HTTPS://something"
|
||||||
)
|
)
|
||||||
with self._patch_open(html):
|
with self._patch_open(html):
|
||||||
movies, date = parse_cinema_city_html("test.html")
|
movies, _ = parse_cinema_city_html("test.html")
|
||||||
assert len(movies) == 1
|
assert len(movies) == 1
|
||||||
|
|
||||||
def test_deduplicate_movies(self) -> None:
|
def test_deduplicate_movies(self) -> None:
|
||||||
@ -467,7 +470,7 @@ class TestParseCinemaCityText:
|
|||||||
text = "MOVIE TITLE\n110 min\n10:00\n"
|
text = "MOVIE TITLE\n110 min\n10:00\n"
|
||||||
with patch(
|
with patch(
|
||||||
"python_pkg.cinema_planner._cinema_parsing._try_parse_time",
|
"python_pkg.cinema_planner._cinema_parsing._try_parse_time",
|
||||||
side_effect=lambda t: None,
|
side_effect=lambda _t: None,
|
||||||
):
|
):
|
||||||
result = parse_cinema_city_text(text)
|
result = parse_cinema_city_text(text)
|
||||||
assert len(result) == 0
|
assert len(result) == 0
|
||||||
|
|||||||
@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
import argparse
|
import argparse
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import MagicMock, mock_open, patch
|
from unittest.mock import MagicMock, mock_open, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -94,7 +93,7 @@ class TestLoadMoviesInteractive:
|
|||||||
"""Tests for _load_movies_interactive."""
|
"""Tests for _load_movies_interactive."""
|
||||||
|
|
||||||
@patch("builtins.input", side_effect=["Movie A, 10:00, 90min", ""])
|
@patch("builtins.input", side_effect=["Movie A, 10:00, 90min", ""])
|
||||||
def test_single_movie(self, _mock: MagicMock) -> None:
|
def test_single_movie(self, mock: MagicMock) -> None:
|
||||||
result = _load_movies_interactive()
|
result = _load_movies_interactive()
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0].name == "Movie A"
|
assert result[0].name == "Movie A"
|
||||||
@ -107,17 +106,17 @@ class TestLoadMoviesInteractive:
|
|||||||
"",
|
"",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_multiple_movies(self, _mock: MagicMock) -> None:
|
def test_multiple_movies(self, mock: MagicMock) -> None:
|
||||||
result = _load_movies_interactive()
|
result = _load_movies_interactive()
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
|
|
||||||
@patch("builtins.input", side_effect=EOFError)
|
@patch("builtins.input", side_effect=EOFError)
|
||||||
def test_eof(self, _mock: MagicMock) -> None:
|
def test_eof(self, mock: MagicMock) -> None:
|
||||||
result = _load_movies_interactive()
|
result = _load_movies_interactive()
|
||||||
assert result == []
|
assert result == []
|
||||||
|
|
||||||
@patch("builtins.input", side_effect=["bad line", ""])
|
@patch("builtins.input", side_effect=["bad line", ""])
|
||||||
def test_invalid_input(self, _mock: MagicMock) -> None:
|
def test_invalid_input(self, mock: MagicMock) -> None:
|
||||||
result = _load_movies_interactive()
|
result = _load_movies_interactive()
|
||||||
assert result == []
|
assert result == []
|
||||||
|
|
||||||
@ -125,7 +124,7 @@ class TestLoadMoviesInteractive:
|
|||||||
"builtins.input",
|
"builtins.input",
|
||||||
side_effect=["bad line", "Movie A, 10:00, 90min", ""],
|
side_effect=["bad line", "Movie A, 10:00, 90min", ""],
|
||||||
)
|
)
|
||||||
def test_mixed_valid_invalid(self, _mock: MagicMock) -> None:
|
def test_mixed_valid_invalid(self, mock: MagicMock) -> None:
|
||||||
result = _load_movies_interactive()
|
result = _load_movies_interactive()
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
@ -147,7 +146,7 @@ class TestLoadMoviesFromFile:
|
|||||||
)
|
)
|
||||||
def test_htm_file(self, mock_parse: MagicMock) -> None:
|
def test_htm_file(self, mock_parse: MagicMock) -> None:
|
||||||
mock_parse.return_value = ([Movie("A", [600], 120)], None)
|
mock_parse.return_value = ([Movie("A", [600], 120)], None)
|
||||||
movies, date = _load_movies_from_file(Path("test.htm"))
|
_, _ = _load_movies_from_file(Path("test.htm"))
|
||||||
mock_parse.assert_called_once()
|
mock_parse.assert_called_once()
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
@ -161,17 +160,21 @@ class TestLoadMoviesFromFile:
|
|||||||
|
|
||||||
def test_text_file(self) -> None:
|
def test_text_file(self) -> None:
|
||||||
content = "Movie A, 10:00, 90min\n# comment\nMovie B, 14:00, 120min\n"
|
content = "Movie A, 10:00, 90min\n# comment\nMovie B, 14:00, 120min\n"
|
||||||
with patch.object(Path, "open", mock_open(read_data=content)):
|
with (
|
||||||
with patch.object(Path, "suffix", new=".txt"):
|
patch.object(Path, "open", mock_open(read_data=content)),
|
||||||
movies, date = _load_movies_from_file(Path("test.txt"))
|
patch.object(Path, "suffix", new=".txt"),
|
||||||
|
):
|
||||||
|
movies, date = _load_movies_from_file(Path("test.txt"))
|
||||||
assert len(movies) == 2
|
assert len(movies) == 2
|
||||||
assert date is None
|
assert date is None
|
||||||
|
|
||||||
def test_text_file_with_bad_line(self) -> None:
|
def test_text_file_with_bad_line(self) -> None:
|
||||||
content = "Movie A, 10:00, 90min\nbad line\n"
|
content = "Movie A, 10:00, 90min\nbad line\n"
|
||||||
with patch.object(Path, "open", mock_open(read_data=content)):
|
with (
|
||||||
with patch.object(Path, "suffix", new=".txt"):
|
patch.object(Path, "open", mock_open(read_data=content)),
|
||||||
movies, date = _load_movies_from_file(Path("test.txt"))
|
patch.object(Path, "suffix", new=".txt"),
|
||||||
|
):
|
||||||
|
movies, _ = _load_movies_from_file(Path("test.txt"))
|
||||||
assert len(movies) == 1
|
assert len(movies) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -192,7 +195,7 @@ class TestLoadMoviesFromStdin:
|
|||||||
class TestFilterMovies:
|
class TestFilterMovies:
|
||||||
"""Tests for _filter_movies."""
|
"""Tests for _filter_movies."""
|
||||||
|
|
||||||
def _make_args(self, **kwargs: Any) -> argparse.Namespace:
|
def _make_args(self, **kwargs: str | bool | None) -> argparse.Namespace:
|
||||||
defaults = {
|
defaults = {
|
||||||
"select": None,
|
"select": None,
|
||||||
"exclude": None,
|
"exclude": None,
|
||||||
@ -204,7 +207,7 @@ class TestFilterMovies:
|
|||||||
|
|
||||||
def test_no_filters(self) -> None:
|
def test_no_filters(self) -> None:
|
||||||
movies = [Movie("A", [600], 120)]
|
movies = [Movie("A", [600], 120)]
|
||||||
result, excluded = _filter_movies(movies, self._make_args())
|
result, _ = _filter_movies(movies, self._make_args())
|
||||||
# Default horror exclusion but no genre matches
|
# Default horror exclusion but no genre matches
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
@ -259,7 +262,7 @@ class TestFilterMovies:
|
|||||||
Movie("Action Movie", [600], 120, ["Action"]),
|
Movie("Action Movie", [600], 120, ["Action"]),
|
||||||
Movie("Drama Movie", [600], 120, ["Drama"]),
|
Movie("Drama Movie", [600], 120, ["Drama"]),
|
||||||
]
|
]
|
||||||
result, excluded = _filter_movies(
|
result, _ = _filter_movies(
|
||||||
movies,
|
movies,
|
||||||
self._make_args(all_genres=True, exclude_genre="action"),
|
self._make_args(all_genres=True, exclude_genre="action"),
|
||||||
)
|
)
|
||||||
@ -268,7 +271,7 @@ class TestFilterMovies:
|
|||||||
|
|
||||||
def test_no_genre_filtered(self) -> None:
|
def test_no_genre_filtered(self) -> None:
|
||||||
movies = [Movie("Movie", [600], 120, ["Comedy"])]
|
movies = [Movie("Movie", [600], 120, ["Comedy"])]
|
||||||
result, excluded = _filter_movies(movies, self._make_args())
|
result, _ = _filter_movies(movies, self._make_args())
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
|
|
||||||
|
|
||||||
@ -301,7 +304,7 @@ class TestApplyMustWatchFilter:
|
|||||||
class TestOutputSchedules:
|
class TestOutputSchedules:
|
||||||
"""Tests for _output_schedules."""
|
"""Tests for _output_schedules."""
|
||||||
|
|
||||||
def _make_args(self, **kwargs: Any) -> argparse.Namespace:
|
def _make_args(self, **kwargs: str | int | None) -> argparse.Namespace:
|
||||||
defaults = {
|
defaults = {
|
||||||
"buffer": 0,
|
"buffer": 0,
|
||||||
"max_schedules": 5,
|
"max_schedules": 5,
|
||||||
|
|||||||
@ -297,7 +297,9 @@ class KeyboardCoopGame:
|
|||||||
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
|
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
|
||||||
|
|
||||||
# Draw letter
|
# Draw letter
|
||||||
text = self.fonts.small.render(letter.upper(), True, TEXT_COLOR)
|
text = self.fonts.small.render(
|
||||||
|
letter.upper(), antialias=True, color=TEXT_COLOR
|
||||||
|
)
|
||||||
text_rect = text.get_rect(center=rect.center)
|
text_rect = text.get_rect(center=rect.center)
|
||||||
self.screen.blit(text, text_rect)
|
self.screen.blit(text, text_rect)
|
||||||
|
|
||||||
@ -305,14 +307,14 @@ class KeyboardCoopGame:
|
|||||||
self, text: str, pos: tuple[int, int], font: pygame.font.Font
|
self, text: str, pos: tuple[int, int], font: pygame.font.Font
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw a single line of text at the given position."""
|
"""Draw a single line of text at the given position."""
|
||||||
rendered = font.render(text, True, TEXT_COLOR)
|
rendered = font.render(text, antialias=True, color=TEXT_COLOR)
|
||||||
self.screen.blit(rendered, pos)
|
self.screen.blit(rendered, pos)
|
||||||
|
|
||||||
def _draw_button(self, rect: pygame.Rect, label: str) -> None:
|
def _draw_button(self, rect: pygame.Rect, label: str) -> None:
|
||||||
"""Draw a button with the given label."""
|
"""Draw a button with the given label."""
|
||||||
pygame.draw.rect(self.screen, KEY_COLOR, rect)
|
pygame.draw.rect(self.screen, KEY_COLOR, rect)
|
||||||
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
|
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2)
|
||||||
text = self.fonts.small.render(label, True, TEXT_COLOR)
|
text = self.fonts.small.render(label, antialias=True, color=TEXT_COLOR)
|
||||||
self.screen.blit(text, text.get_rect(center=rect.center))
|
self.screen.blit(text, text.get_rect(center=rect.center))
|
||||||
|
|
||||||
def _draw_ui(self) -> tuple[pygame.Rect, pygame.Rect]:
|
def _draw_ui(self) -> tuple[pygame.Rect, pygame.Rect]:
|
||||||
@ -329,7 +331,9 @@ class KeyboardCoopGame:
|
|||||||
# Current player with color
|
# Current player with color
|
||||||
player_color = PLAYER_COLORS[self.state.current_player]
|
player_color = PLAYER_COLORS[self.state.current_player]
|
||||||
player_text = self.fonts.normal.render(
|
player_text = self.fonts.normal.render(
|
||||||
f"Current Player: {self.state.current_player + 1}", True, player_color
|
f"Current Player: {self.state.current_player + 1}",
|
||||||
|
antialias=True,
|
||||||
|
color=player_color,
|
||||||
)
|
)
|
||||||
self.screen.blit(player_text, (30, 100))
|
self.screen.blit(player_text, (30, 100))
|
||||||
|
|
||||||
|
|||||||
@ -194,7 +194,9 @@ def main() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _build(tmpdir: str) -> None:
|
def _build(tmpdir: str) -> None:
|
||||||
# ── Lazy imports of moved part builders ───────────────────────
|
# ── Lazy imports to avoid circular dependency ────────────────
|
||||||
|
# These submodules import constants from this module, so they
|
||||||
|
# cannot be imported at the top level.
|
||||||
from moviepy.audio.fx import MultiplyVolume
|
from moviepy.audio.fx import MultiplyVolume
|
||||||
|
|
||||||
from python_pkg.moviepy_showcase._moviepy_audio_output import (
|
from python_pkg.moviepy_showcase._moviepy_audio_output import (
|
||||||
@ -208,7 +210,9 @@ def _build(tmpdir: str) -> None:
|
|||||||
part1_clip_types,
|
part1_clip_types,
|
||||||
part2_clip_methods,
|
part2_clip_methods,
|
||||||
)
|
)
|
||||||
from python_pkg.moviepy_showcase._moviepy_video_effects import part3_video_effects
|
from python_pkg.moviepy_showcase._moviepy_video_effects import (
|
||||||
|
part3_video_effects,
|
||||||
|
)
|
||||||
|
|
||||||
# ── Render each part to its own temp file ─────────────────────
|
# ── Render each part to its own temp file ─────────────────────
|
||||||
# Title card
|
# Title card
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import pytest
|
|||||||
_H, _W = 1080, 1920
|
_H, _W = 1080, 1920
|
||||||
|
|
||||||
|
|
||||||
def create_mock_clip(**overrides: Any) -> MagicMock:
|
def create_mock_clip(**overrides: float | tuple[int, int]) -> MagicMock:
|
||||||
"""Return a MagicMock that behaves enough like a moviepy clip."""
|
"""Return a MagicMock that behaves enough like a moviepy clip."""
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.duration = overrides.get("duration", 2.0)
|
clip.duration = overrides.get("duration", 2.0)
|
||||||
@ -71,12 +71,12 @@ _clip_classes = [
|
|||||||
"CompositeAudioClip",
|
"CompositeAudioClip",
|
||||||
]
|
]
|
||||||
for _cls in _clip_classes:
|
for _cls in _clip_classes:
|
||||||
getattr(mock_moviepy, _cls).side_effect = lambda *a, **kw: create_mock_clip()
|
getattr(mock_moviepy, _cls).side_effect = lambda *_a, **_kw: create_mock_clip()
|
||||||
|
|
||||||
mock_moviepy.concatenate_videoclips.side_effect = lambda *a, **kw: create_mock_clip()
|
mock_moviepy.concatenate_videoclips.side_effect = lambda *_a, **_kw: create_mock_clip()
|
||||||
mock_moviepy.concatenate_audioclips.side_effect = lambda *a, **kw: create_mock_clip()
|
mock_moviepy.concatenate_audioclips.side_effect = lambda *_a, **_kw: create_mock_clip()
|
||||||
mock_moviepy.video.compositing.CompositeVideoClip.clips_array.side_effect = (
|
mock_moviepy.video.compositing.CompositeVideoClip.clips_array.side_effect = (
|
||||||
lambda *a, **kw: create_mock_clip()
|
lambda *_a, **_kw: create_mock_clip()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Drawing tools must return real numpy arrays (used in numpy ops)
|
# Drawing tools must return real numpy arrays (used in numpy ops)
|
||||||
|
|||||||
@ -25,7 +25,7 @@ def test_make_sine_maker_scalar() -> None:
|
|||||||
"""maker() with scalar t → t_arr.ndim == 0 → returns 1-D."""
|
"""maker() with scalar t → t_arr.ndim == 0 → returns 1-D."""
|
||||||
import moviepy as mp
|
import moviepy as mp
|
||||||
|
|
||||||
mp.AudioClip.side_effect = lambda *a, **kw: MagicMock()
|
mp.AudioClip.side_effect = lambda *_a, **_kw: MagicMock()
|
||||||
_make_sine(440.0, 1.0)
|
_make_sine(440.0, 1.0)
|
||||||
maker = mp.AudioClip.call_args[0][0]
|
maker = mp.AudioClip.call_args[0][0]
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ def test_make_sine_maker_array() -> None:
|
|||||||
"""maker() with array t → t_arr.ndim > 0 → returns 2-D."""
|
"""maker() with array t → t_arr.ndim > 0 → returns 2-D."""
|
||||||
import moviepy as mp
|
import moviepy as mp
|
||||||
|
|
||||||
mp.AudioClip.side_effect = lambda *a, **kw: MagicMock()
|
mp.AudioClip.side_effect = lambda *_a, **_kw: MagicMock()
|
||||||
_make_sine(440.0, 1.0)
|
_make_sine(440.0, 1.0)
|
||||||
maker = mp.AudioClip.call_args[0][0]
|
maker = mp.AudioClip.call_args[0][0]
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ def test_part1_data_to_frame() -> None:
|
|||||||
"""Extract and test the inner data_to_frame function."""
|
"""Extract and test the inner data_to_frame function."""
|
||||||
import moviepy as mp
|
import moviepy as mp
|
||||||
|
|
||||||
mp.DataVideoClip.side_effect = lambda *a, **kw: create_mock_clip()
|
mp.DataVideoClip.side_effect = lambda *_a, **_kw: create_mock_clip()
|
||||||
result = part1_clip_types()
|
result = part1_clip_types()
|
||||||
assert len(result) > 0
|
assert len(result) > 0
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
@ -102,17 +103,18 @@ def test_resize_to_canvas() -> None:
|
|||||||
def test_render_part() -> None:
|
def test_render_part() -> None:
|
||||||
s1 = create_mock_clip()
|
s1 = create_mock_clip()
|
||||||
s2 = create_mock_clip()
|
s2 = create_mock_clip()
|
||||||
_render_part([s1, s2], "/tmp/test_part.mp4", "test")
|
_render_part([s1, s2], tempfile.gettempdir() + "/test_part.mp4", "test")
|
||||||
s1.close.assert_called_once()
|
s1.close.assert_called_once()
|
||||||
s2.close.assert_called_once()
|
s2.close.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
# ── main ─────────────────────────────────────────────────────────
|
# ── main ─────────────────────────────────────────────────────────
|
||||||
def test_main_success() -> None:
|
def test_main_success() -> None:
|
||||||
|
mock_dir = tempfile.gettempdir() + "/mock_dir"
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp",
|
"python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp",
|
||||||
return_value="/tmp/mock_dir",
|
return_value=mock_dir,
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.moviepy_showcase.moviepy_showcase._build",
|
"python_pkg.moviepy_showcase.moviepy_showcase._build",
|
||||||
@ -122,15 +124,16 @@ def test_main_success() -> None:
|
|||||||
) as mock_rmtree,
|
) as mock_rmtree,
|
||||||
):
|
):
|
||||||
main()
|
main()
|
||||||
mock_build.assert_called_once_with("/tmp/mock_dir")
|
mock_build.assert_called_once_with(mock_dir)
|
||||||
mock_rmtree.assert_called_once_with("/tmp/mock_dir", ignore_errors=True)
|
mock_rmtree.assert_called_once_with(mock_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def test_main_build_raises() -> None:
|
def test_main_build_raises() -> None:
|
||||||
|
mock_dir = tempfile.gettempdir() + "/mock_dir"
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp",
|
"python_pkg.moviepy_showcase.moviepy_showcase.tempfile.mkdtemp",
|
||||||
return_value="/tmp/mock_dir",
|
return_value=mock_dir,
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.moviepy_showcase.moviepy_showcase._build",
|
"python_pkg.moviepy_showcase.moviepy_showcase._build",
|
||||||
@ -142,7 +145,7 @@ def test_main_build_raises() -> None:
|
|||||||
):
|
):
|
||||||
with contextlib.suppress(RuntimeError):
|
with contextlib.suppress(RuntimeError):
|
||||||
main()
|
main()
|
||||||
mock_rmtree.assert_called_once_with("/tmp/mock_dir", ignore_errors=True)
|
mock_rmtree.assert_called_once_with(mock_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
# ── _build ───────────────────────────────────────────────────────
|
# ── _build ───────────────────────────────────────────────────────
|
||||||
@ -155,4 +158,4 @@ def test_build() -> None:
|
|||||||
),
|
),
|
||||||
patch.object(Path, "stat", return_value=mock_stat),
|
patch.object(Path, "stat", return_value=mock_stat),
|
||||||
):
|
):
|
||||||
_build("/tmp/test_build")
|
_build(tempfile.gettempdir() + "/test_build")
|
||||||
|
|||||||
@ -14,6 +14,8 @@ Usage:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import importlib.util
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
@ -49,6 +51,8 @@ from python_pkg.music_gen._music_speech import (
|
|||||||
warnings.filterwarnings("ignore", category=FutureWarning)
|
warnings.filterwarnings("ignore", category=FutureWarning)
|
||||||
warnings.filterwarnings("ignore", category=UserWarning)
|
warnings.filterwarnings("ignore", category=UserWarning)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Re-export all public symbols for backwards compatibility
|
# Re-export all public symbols for backwards compatibility
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"BARK_MAX_CHARS",
|
"BARK_MAX_CHARS",
|
||||||
@ -85,8 +89,6 @@ def check_dependencies(*, include_bark: bool = False) -> bool:
|
|||||||
Args:
|
Args:
|
||||||
include_bark: Whether to check for Bark dependencies as well.
|
include_bark: Whether to check for Bark dependencies as well.
|
||||||
"""
|
"""
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
missing = []
|
missing = []
|
||||||
|
|
||||||
if importlib.util.find_spec("torch") is None:
|
if importlib.util.find_spec("torch") is None:
|
||||||
@ -102,87 +104,128 @@ def check_dependencies(*, include_bark: bool = False) -> bool:
|
|||||||
missing.append("git+https://github.com/suno-ai/bark.git")
|
missing.append("git+https://github.com/suno-ai/bark.git")
|
||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
print("Missing dependencies. Install with:")
|
logger.error("Missing dependencies. Install with:")
|
||||||
print(f" pip install {' '.join(missing)}")
|
logger.error(" pip install %s", " ".join(missing))
|
||||||
print("\nFor CUDA support:")
|
logger.error("For CUDA support:")
|
||||||
print(" pip install torch --index-url https://download.pytorch.org/whl/cu121")
|
logger.error(
|
||||||
print(" pip install transformers scipy")
|
" pip install torch --index-url https://download.pytorch.org/whl/cu121",
|
||||||
|
)
|
||||||
|
logger.error(" pip install transformers scipy")
|
||||||
if include_bark:
|
if include_bark:
|
||||||
print("\nFor Bark vocals:")
|
logger.error("For Bark vocals:")
|
||||||
print(" pip install git+https://github.com/suno-ai/bark.git")
|
logger.error(
|
||||||
|
" pip install git+https://github.com/suno-ai/bark.git",
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLE_PROMPTS = [
|
||||||
|
"upbeat electronic dance music with heavy bass",
|
||||||
|
"calm acoustic guitar melody with soft percussion",
|
||||||
|
"epic orchestral soundtrack with dramatic strings",
|
||||||
|
"lo-fi hip hop beats for studying",
|
||||||
|
"80s synthwave with retro vibes",
|
||||||
|
"jazz piano trio with upright bass",
|
||||||
|
"ambient electronic music for relaxation",
|
||||||
|
"rock guitar riff with drums",
|
||||||
|
"classical piano sonata in minor key",
|
||||||
|
"tropical house with steel drums",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _show_help() -> None:
|
||||||
|
"""Display example prompts."""
|
||||||
|
logger.info("Example prompts:")
|
||||||
|
for i, ex in enumerate(EXAMPLE_PROMPTS, 1):
|
||||||
|
logger.info(" %d. %s", i, ex)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_duration(raw: str) -> int | None:
|
||||||
|
"""Parse and return a new duration, or None on failure."""
|
||||||
|
try:
|
||||||
|
value = int(raw.strip())
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(
|
||||||
|
"Invalid duration. Use ':d <number>' e.g., ':d 15'",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
clamped = max(1, min(30, value))
|
||||||
|
logger.info("Duration set to %ds", clamped)
|
||||||
|
return clamped
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_prompt(prompt: str) -> str | None:
|
||||||
|
"""Resolve a numeric prompt to an example, or return as-is.
|
||||||
|
|
||||||
|
Returns None if the number is out of range.
|
||||||
|
"""
|
||||||
|
if prompt.isdigit():
|
||||||
|
idx = int(prompt) - 1
|
||||||
|
if 0 <= idx < len(EXAMPLE_PROMPTS):
|
||||||
|
resolved = EXAMPLE_PROMPTS[idx]
|
||||||
|
logger.info("Using: %s", resolved)
|
||||||
|
return resolved
|
||||||
|
logger.warning(
|
||||||
|
"Invalid number. Enter 1-%d",
|
||||||
|
len(EXAMPLE_PROMPTS),
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return prompt
|
||||||
|
|
||||||
|
|
||||||
def interactive_mode(model: object, processor: object) -> None:
|
def interactive_mode(model: object, processor: object) -> None:
|
||||||
"""Run interactive prompt mode."""
|
"""Run interactive prompt mode."""
|
||||||
print("\n" + "=" * 60)
|
banner = "=" * 60
|
||||||
print("INTERACTIVE MODE")
|
logger.info("\n%s", banner)
|
||||||
print("=" * 60)
|
logger.info("INTERACTIVE MODE")
|
||||||
print("Enter prompts to generate music. Commands:")
|
logger.info("%s", banner)
|
||||||
print(" :q or :quit - Exit")
|
logger.info("Enter prompts to generate music. Commands:")
|
||||||
print(" :d <seconds> - Set duration (e.g., ':d 15')")
|
logger.info(" :q or :quit - Exit")
|
||||||
print(" :h or :help - Show example prompts")
|
logger.info(" :d <seconds> - Set duration (e.g., ':d 15')")
|
||||||
print("=" * 60)
|
logger.info(" :h or :help - Show example prompts")
|
||||||
|
logger.info("%s", banner)
|
||||||
|
|
||||||
duration = 10
|
duration = 10
|
||||||
|
|
||||||
example_prompts = [
|
|
||||||
"upbeat electronic dance music with heavy bass",
|
|
||||||
"calm acoustic guitar melody with soft percussion",
|
|
||||||
"epic orchestral soundtrack with dramatic strings",
|
|
||||||
"lo-fi hip hop beats for studying",
|
|
||||||
"80s synthwave with retro vibes",
|
|
||||||
"jazz piano trio with upright bass",
|
|
||||||
"ambient electronic music for relaxation",
|
|
||||||
"rock guitar riff with drums",
|
|
||||||
"classical piano sonata in minor key",
|
|
||||||
"tropical house with steel drums",
|
|
||||||
]
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
prompt = input(f"\n[{duration}s] Enter prompt: ").strip()
|
prompt = input(f"\n[{duration}s] Enter prompt: ").strip()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
print("\nExiting...")
|
logger.info("Exiting...")
|
||||||
break
|
break
|
||||||
|
|
||||||
if not prompt:
|
if not prompt:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if prompt.lower() in (":q", ":quit", "quit", "exit"):
|
if prompt.lower() in (":q", ":quit", "quit", "exit"):
|
||||||
print("Exiting...")
|
logger.info("Exiting...")
|
||||||
break
|
break
|
||||||
|
|
||||||
if prompt.lower() in (":h", ":help", "help"):
|
if prompt.lower() in (":h", ":help", "help"):
|
||||||
print("\nExample prompts:")
|
_show_help()
|
||||||
for i, ex in enumerate(example_prompts, 1):
|
|
||||||
print(f" {i}. {ex}")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if prompt.startswith(":d "):
|
if prompt.startswith(":d "):
|
||||||
try:
|
new_dur = _handle_duration(prompt[3:])
|
||||||
duration = int(prompt[3:].strip())
|
if new_dur is not None:
|
||||||
duration = max(1, min(30, duration)) # Clamp to 1-30
|
duration = new_dur
|
||||||
print(f"Duration set to {duration}s")
|
|
||||||
except ValueError:
|
|
||||||
print("Invalid duration. Use ':d <number>' e.g., ':d 15'")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check if user entered a number to use example prompt
|
resolved = _resolve_prompt(prompt)
|
||||||
if prompt.isdigit():
|
if resolved is None:
|
||||||
idx = int(prompt) - 1
|
continue
|
||||||
if 0 <= idx < len(example_prompts):
|
|
||||||
prompt = example_prompts[idx]
|
|
||||||
print(f"Using: {prompt}")
|
|
||||||
else:
|
|
||||||
print(f"Invalid number. Enter 1-{len(example_prompts)}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
generate_music(prompt, model, processor, duration_seconds=duration)
|
generate_music(
|
||||||
except (RuntimeError, ValueError, OSError) as e:
|
resolved,
|
||||||
print(f"Error generating music: {e}")
|
model,
|
||||||
|
processor,
|
||||||
|
duration_seconds=duration,
|
||||||
|
)
|
||||||
|
except (RuntimeError, ValueError, OSError):
|
||||||
|
logger.exception("Error generating music")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@ -275,7 +318,9 @@ Bark tokens: [laughter] [laughs] [sighs] [music] [gasps] ♪ (singing)
|
|||||||
|
|
||||||
if not args.prompt and not args.interactive:
|
if not args.prompt and not args.interactive:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
print("\nError: Either provide a prompt or use --interactive mode")
|
logger.error(
|
||||||
|
"Either provide a prompt or use --interactive mode",
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Check dependencies
|
# Check dependencies
|
||||||
|
|||||||
@ -57,9 +57,9 @@ class TestGetDevice:
|
|||||||
patch.dict("sys.modules", {"torch": mock_torch}),
|
patch.dict("sys.modules", {"torch": mock_torch}),
|
||||||
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
|
patch("shutil.which", return_value="/usr/bin/nvidia-smi"),
|
||||||
patch("subprocess.run", return_value=mock_result),
|
patch("subprocess.run", return_value=mock_result),
|
||||||
|
pytest.raises(RuntimeError, match="NVIDIA GPU detected"),
|
||||||
):
|
):
|
||||||
with pytest.raises(RuntimeError, match="NVIDIA GPU detected"):
|
get_device()
|
||||||
get_device()
|
|
||||||
|
|
||||||
def test_nvidia_smi_not_found(self) -> None:
|
def test_nvidia_smi_not_found(self) -> None:
|
||||||
mock_torch = MagicMock()
|
mock_torch = MagicMock()
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from python_pkg.music_gen.music_generator import (
|
from python_pkg.music_gen.music_generator import (
|
||||||
@ -21,56 +22,64 @@ class TestCheckDependencies:
|
|||||||
with patch("importlib.util.find_spec", return_value=MagicMock()):
|
with patch("importlib.util.find_spec", return_value=MagicMock()):
|
||||||
assert check_dependencies() is True
|
assert check_dependencies() is True
|
||||||
|
|
||||||
def test_torch_missing(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_torch_missing(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
def mock_find_spec(name: str) -> Any:
|
def mock_find_spec(name: str) -> MagicMock | None:
|
||||||
if name == "torch":
|
if name == "torch":
|
||||||
return None
|
return None
|
||||||
return MagicMock()
|
return MagicMock()
|
||||||
|
|
||||||
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("importlib.util.find_spec", side_effect=mock_find_spec),
|
||||||
|
):
|
||||||
assert check_dependencies() is False
|
assert check_dependencies() is False
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "torch" in caplog.text
|
||||||
assert "torch" in captured.out
|
|
||||||
|
|
||||||
def test_transformers_missing(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_transformers_missing(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
def mock_find_spec(name: str) -> Any:
|
def mock_find_spec(name: str) -> MagicMock | None:
|
||||||
if name == "transformers":
|
if name == "transformers":
|
||||||
return None
|
return None
|
||||||
return MagicMock()
|
return MagicMock()
|
||||||
|
|
||||||
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("importlib.util.find_spec", side_effect=mock_find_spec),
|
||||||
|
):
|
||||||
assert check_dependencies() is False
|
assert check_dependencies() is False
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "transformers" in caplog.text
|
||||||
assert "transformers" in captured.out
|
|
||||||
|
|
||||||
def test_scipy_missing(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_scipy_missing(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
def mock_find_spec(name: str) -> Any:
|
def mock_find_spec(name: str) -> MagicMock | None:
|
||||||
if name == "scipy":
|
if name == "scipy":
|
||||||
return None
|
return None
|
||||||
return MagicMock()
|
return MagicMock()
|
||||||
|
|
||||||
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("importlib.util.find_spec", side_effect=mock_find_spec),
|
||||||
|
):
|
||||||
assert check_dependencies() is False
|
assert check_dependencies() is False
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "scipy" in caplog.text
|
||||||
assert "scipy" in captured.out
|
|
||||||
|
|
||||||
def test_bark_missing_with_include_bark(
|
def test_bark_missing_with_include_bark(
|
||||||
self,
|
self,
|
||||||
capsys: pytest.CaptureFixture[str],
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
def mock_find_spec(name: str) -> Any:
|
def mock_find_spec(name: str) -> MagicMock | None:
|
||||||
if name == "bark":
|
if name == "bark":
|
||||||
return None
|
return None
|
||||||
return MagicMock()
|
return MagicMock()
|
||||||
|
|
||||||
with patch("importlib.util.find_spec", side_effect=mock_find_spec):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("importlib.util.find_spec", side_effect=mock_find_spec),
|
||||||
|
):
|
||||||
assert check_dependencies(include_bark=True) is False
|
assert check_dependencies(include_bark=True) is False
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "bark" in caplog.text.lower()
|
||||||
assert "bark" in captured.out.lower()
|
|
||||||
|
|
||||||
def test_bark_not_checked_without_flag(self) -> None:
|
def test_bark_not_checked_without_flag(self) -> None:
|
||||||
with patch("importlib.util.find_spec", return_value=MagicMock()):
|
with patch("importlib.util.find_spec", return_value=MagicMock()):
|
||||||
@ -84,64 +93,78 @@ class TestCheckDependencies:
|
|||||||
class TestInteractiveMode:
|
class TestInteractiveMode:
|
||||||
"""Tests for interactive_mode()."""
|
"""Tests for interactive_mode()."""
|
||||||
|
|
||||||
def test_quit_command(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_quit_command(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", return_value=":q"):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", return_value=":q"),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Exiting" in caplog.text
|
||||||
assert "Exiting" in captured.out
|
|
||||||
|
|
||||||
def test_quit_word(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_quit_word(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", return_value="quit"):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", return_value="quit"),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Exiting" in caplog.text
|
||||||
assert "Exiting" in captured.out
|
|
||||||
|
|
||||||
def test_exit_word(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_exit_word(self) -> None:
|
||||||
with patch("builtins.input", return_value="exit"):
|
with patch("builtins.input", return_value="exit"):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
def test_help_command(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_help_command(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=[":h", ":q"]):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=[":h", ":q"]),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Example prompts" in caplog.text
|
||||||
assert "Example prompts" in captured.out
|
|
||||||
|
|
||||||
def test_help_word(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_help_word(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=["help", ":q"]):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=["help", ":q"]),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Example prompts" in caplog.text
|
||||||
assert "Example prompts" in captured.out
|
|
||||||
|
|
||||||
def test_set_duration(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_set_duration(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=[":d 15", ":q"]):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=[":d 15", ":q"]),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Duration set to 15s" in caplog.text
|
||||||
assert "Duration set to 15s" in captured.out
|
|
||||||
|
|
||||||
def test_set_duration_clamped(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_set_duration_clamped(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=[":d 100", ":q"]):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=[":d 100", ":q"]),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Duration set to 30s" in caplog.text
|
||||||
assert "Duration set to 30s" in captured.out
|
|
||||||
|
|
||||||
def test_set_duration_invalid(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_set_duration_invalid(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=[":d abc", ":q"]):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=[":d abc", ":q"]),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Invalid duration" in caplog.text
|
||||||
assert "Invalid duration" in captured.out
|
|
||||||
|
|
||||||
def test_empty_prompt(self) -> None:
|
def test_empty_prompt(self) -> None:
|
||||||
with patch("builtins.input", side_effect=["", ":q"]):
|
with patch("builtins.input", side_effect=["", ":q"]):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
def test_number_prompt_valid(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_number_prompt_valid(self) -> None:
|
||||||
with (
|
with (
|
||||||
patch("builtins.input", side_effect=["1", ":q"]),
|
patch("builtins.input", side_effect=["1", ":q"]),
|
||||||
patch(
|
patch(
|
||||||
@ -152,12 +175,14 @@ class TestInteractiveMode:
|
|||||||
|
|
||||||
mock_gen.assert_called_once()
|
mock_gen.assert_called_once()
|
||||||
|
|
||||||
def test_number_prompt_invalid(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_number_prompt_invalid(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=["99", ":q"]):
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=["99", ":q"]),
|
||||||
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Invalid number" in caplog.text
|
||||||
assert "Invalid number" in captured.out
|
|
||||||
|
|
||||||
def test_normal_prompt(self) -> None:
|
def test_normal_prompt(self) -> None:
|
||||||
with (
|
with (
|
||||||
@ -170,8 +195,9 @@ class TestInteractiveMode:
|
|||||||
|
|
||||||
mock_gen.assert_called_once()
|
mock_gen.assert_called_once()
|
||||||
|
|
||||||
def test_generation_error(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_generation_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with (
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
patch("builtins.input", side_effect=["jazz music", ":q"]),
|
patch("builtins.input", side_effect=["jazz music", ":q"]),
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.music_gen.music_generator.generate_music",
|
"python_pkg.music_gen.music_generator.generate_music",
|
||||||
@ -180,46 +206,56 @@ class TestInteractiveMode:
|
|||||||
):
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Error generating music" in caplog.text
|
||||||
assert "Error generating music" in captured.out
|
|
||||||
|
|
||||||
def test_eof_error(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_eof_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with patch("builtins.input", side_effect=EOFError):
|
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
|
||||||
assert "Exiting" in captured.out
|
|
||||||
|
|
||||||
def test_keyboard_interrupt(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
||||||
with patch("builtins.input", side_effect=KeyboardInterrupt):
|
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
|
||||||
assert "Exiting" in captured.out
|
|
||||||
|
|
||||||
def test_quit_long(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
||||||
with patch("builtins.input", return_value=":quit"):
|
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
|
||||||
assert "Exiting" in captured.out
|
|
||||||
|
|
||||||
def test_help_long(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
||||||
with patch("builtins.input", side_effect=[":help", ":q"]):
|
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
|
||||||
assert "Example prompts" in captured.out
|
|
||||||
|
|
||||||
def test_duration_clamp_minimum(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
||||||
with patch("builtins.input", side_effect=[":d 0", ":q"]):
|
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
|
||||||
assert "Duration set to 1s" in captured.out
|
|
||||||
|
|
||||||
def test_generation_value_error(self, capsys: pytest.CaptureFixture[str]) -> None:
|
|
||||||
with (
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=EOFError),
|
||||||
|
):
|
||||||
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
|
assert "Exiting" in caplog.text
|
||||||
|
|
||||||
|
def test_keyboard_interrupt(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=KeyboardInterrupt),
|
||||||
|
):
|
||||||
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
|
assert "Exiting" in caplog.text
|
||||||
|
|
||||||
|
def test_quit_long(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", return_value=":quit"),
|
||||||
|
):
|
||||||
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
|
assert "Exiting" in caplog.text
|
||||||
|
|
||||||
|
def test_help_long(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=[":help", ":q"]),
|
||||||
|
):
|
||||||
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
|
assert "Example prompts" in caplog.text
|
||||||
|
|
||||||
|
def test_duration_clamp_minimum(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
|
patch("builtins.input", side_effect=[":d 0", ":q"]),
|
||||||
|
):
|
||||||
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
|
assert "Duration set to 1s" in caplog.text
|
||||||
|
|
||||||
|
def test_generation_value_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
patch("builtins.input", side_effect=["jazz", ":q"]),
|
patch("builtins.input", side_effect=["jazz", ":q"]),
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.music_gen.music_generator.generate_music",
|
"python_pkg.music_gen.music_generator.generate_music",
|
||||||
@ -228,11 +264,11 @@ class TestInteractiveMode:
|
|||||||
):
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Error generating music" in caplog.text
|
||||||
assert "Error generating music" in captured.out
|
|
||||||
|
|
||||||
def test_generation_os_error(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_generation_os_error(self, caplog: pytest.LogCaptureFixture) -> None:
|
||||||
with (
|
with (
|
||||||
|
caplog.at_level(logging.DEBUG),
|
||||||
patch("builtins.input", side_effect=["jazz", ":q"]),
|
patch("builtins.input", side_effect=["jazz", ":q"]),
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.music_gen.music_generator.generate_music",
|
"python_pkg.music_gen.music_generator.generate_music",
|
||||||
@ -241,5 +277,4 @@ class TestInteractiveMode:
|
|||||||
):
|
):
|
||||||
interactive_mode(MagicMock(), MagicMock())
|
interactive_mode(MagicMock(), MagicMock())
|
||||||
|
|
||||||
captured = capsys.readouterr()
|
assert "Error generating music" in caplog.text
|
||||||
assert "Error generating music" in captured.out
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -227,7 +228,7 @@ class TestMain:
|
|||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"sys.argv",
|
"sys.argv",
|
||||||
["music_generator", "--output", "/tmp/out", "test"],
|
["music_generator", "--output", tempfile.gettempdir() + "/out", "test"],
|
||||||
),
|
),
|
||||||
patch(
|
patch(
|
||||||
"python_pkg.music_gen.music_generator.check_dependencies",
|
"python_pkg.music_gen.music_generator.check_dependencies",
|
||||||
|
|||||||
@ -426,7 +426,7 @@ class TestGenerateVocalsForSong:
|
|||||||
return_value=["Hello."],
|
return_value=["Hello."],
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
vocals, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6")
|
_, sr = _generate_vocals_for_song("Hello.", "v2/en_speaker_6")
|
||||||
|
|
||||||
assert sr == 24000
|
assert sr == 24000
|
||||||
# The original_load should have been called via patched_load
|
# The original_load should have been called via patched_load
|
||||||
@ -487,6 +487,6 @@ class TestGenerateInstrumentalForSong:
|
|||||||
return_value=audio,
|
return_value=audio,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
instrumental, sr = _generate_instrumental_for_song("test", 60)
|
_, sr = _generate_instrumental_for_song("test", 60)
|
||||||
|
|
||||||
assert sr == 100
|
assert sr == 100
|
||||||
|
|||||||
@ -31,7 +31,7 @@ class PokerGuiMixin:
|
|||||||
self.root.title("🃏 Texas Hold'em Modifier")
|
self.root.title("🃏 Texas Hold'em Modifier")
|
||||||
self.root.geometry("650x750")
|
self.root.geometry("650x750")
|
||||||
self.root.configure(bg="#0f4c3a")
|
self.root.configure(bg="#0f4c3a")
|
||||||
self.root.resizable(True, True)
|
self.root.resizable(width=True, height=True)
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
style.theme_use("clam")
|
style.theme_use("clam")
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ class PokerGuiMixin:
|
|||||||
parent, bg="#2d2d2d", relief=tk.RIDGE, bd=3, height=150
|
parent, bg="#2d2d2d", relief=tk.RIDGE, bd=3, height=150
|
||||||
)
|
)
|
||||||
self.result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 20), padx=10)
|
self.result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 20), padx=10)
|
||||||
self.result_frame.pack_propagate(False)
|
self.result_frame.pack_propagate(flag=False)
|
||||||
|
|
||||||
self.result_label = tk.Label(
|
self.result_label = tk.Label(
|
||||||
self.result_frame,
|
self.result_frame,
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from python_pkg.poker_modifier_app._poker_gui import PokerGuiMixin
|
||||||
|
|
||||||
|
|
||||||
def _install_tk_mocks() -> dict[str, MagicMock]:
|
def _install_tk_mocks() -> dict[str, MagicMock]:
|
||||||
"""Install mock tkinter modules and return them."""
|
"""Install mock tkinter modules and return them."""
|
||||||
@ -26,19 +29,19 @@ def _install_tk_mocks() -> dict[str, MagicMock]:
|
|||||||
|
|
||||||
# Make constructors return fresh mocks each time
|
# Make constructors return fresh mocks each time
|
||||||
mock_tk.Tk.return_value = MagicMock(name="root")
|
mock_tk.Tk.return_value = MagicMock(name="root")
|
||||||
mock_tk.Frame.side_effect = lambda *a, **kw: MagicMock(name="Frame")
|
mock_tk.Frame.side_effect = lambda *_a, **_kw: MagicMock(name="Frame")
|
||||||
mock_tk.Label.side_effect = lambda *a, **kw: MagicMock(name="Label")
|
mock_tk.Label.side_effect = lambda *_a, **_kw: MagicMock(name="Label")
|
||||||
mock_tk.LabelFrame.side_effect = lambda *a, **kw: MagicMock(name="LabelFrame")
|
mock_tk.LabelFrame.side_effect = lambda *_a, **_kw: MagicMock(name="LabelFrame")
|
||||||
mock_tk.Scale.side_effect = lambda *a, **kw: MagicMock(name="Scale")
|
mock_tk.Scale.side_effect = lambda *_a, **_kw: MagicMock(name="Scale")
|
||||||
mock_tk.IntVar.side_effect = lambda *a, **kw: MagicMock(name="IntVar")
|
mock_tk.IntVar.side_effect = lambda *_a, **_kw: MagicMock(name="IntVar")
|
||||||
mock_tk.BooleanVar.side_effect = lambda *a, **kw: MagicMock(name="BooleanVar")
|
mock_tk.BooleanVar.side_effect = lambda *_a, **_kw: MagicMock(name="BooleanVar")
|
||||||
mock_tk.Checkbutton.side_effect = lambda *a, **kw: MagicMock(name="Checkbutton")
|
mock_tk.Checkbutton.side_effect = lambda *_a, **_kw: MagicMock(name="Checkbutton")
|
||||||
mock_tk.Button.side_effect = lambda *a, **kw: MagicMock(name="Button")
|
mock_tk.Button.side_effect = lambda *_a, **_kw: MagicMock(name="Button")
|
||||||
|
|
||||||
return {"tk": mock_tk, "ttk": mock_ttk}
|
return {"tk": mock_tk, "ttk": mock_ttk}
|
||||||
|
|
||||||
|
|
||||||
def _make_mixin() -> Any:
|
def _make_mixin() -> tuple[PokerGuiMixin, MagicMock, MagicMock]:
|
||||||
"""Create a PokerGuiMixin instance with mocked tkinter."""
|
"""Create a PokerGuiMixin instance with mocked tkinter."""
|
||||||
tk_mocks = _install_tk_mocks()
|
tk_mocks = _install_tk_mocks()
|
||||||
|
|
||||||
@ -99,7 +102,7 @@ class TestSetupMainWindow:
|
|||||||
root.title.assert_called_once_with("🃏 Texas Hold'em Modifier")
|
root.title.assert_called_once_with("🃏 Texas Hold'em Modifier")
|
||||||
root.geometry.assert_called_once_with("650x750")
|
root.geometry.assert_called_once_with("650x750")
|
||||||
root.configure.assert_called_once_with(bg="#0f4c3a")
|
root.configure.assert_called_once_with(bg="#0f4c3a")
|
||||||
root.resizable.assert_called_once_with(True, True)
|
root.resizable.assert_called_once_with(width=True, height=True)
|
||||||
mock_ttk.Style.assert_called_once()
|
mock_ttk.Style.assert_called_once()
|
||||||
mock_ttk.Style.return_value.theme_use.assert_called_once_with("clam")
|
mock_ttk.Style.return_value.theme_use.assert_called_once_with("clam")
|
||||||
|
|
||||||
@ -239,7 +242,7 @@ class TestCreateResultDisplay:
|
|||||||
frame_calls = mock_tk.Frame.call_args_list
|
frame_calls = mock_tk.Frame.call_args_list
|
||||||
assert any(c[1].get("height") == 150 for c in frame_calls)
|
assert any(c[1].get("height") == 150 for c in frame_calls)
|
||||||
assert hasattr(mixin, "result_frame")
|
assert hasattr(mixin, "result_frame")
|
||||||
mixin.result_frame.pack_propagate.assert_called_once_with(False)
|
mixin.result_frame.pack_propagate.assert_called_once_with(flag=False)
|
||||||
|
|
||||||
# Result label
|
# Result label
|
||||||
label_calls = mock_tk.Label.call_args_list
|
label_calls = mock_tk.Label.call_args_list
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from python_pkg.poker_modifier_app._poker_modifiers import (
|
from python_pkg.poker_modifier_app._poker_modifiers import (
|
||||||
@ -11,8 +11,11 @@ from python_pkg.poker_modifier_app._poker_modifiers import (
|
|||||||
Modifier,
|
Modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from python_pkg.poker_modifier_app.poker_modifier_app import PokerModifierApp
|
||||||
|
|
||||||
def _make_app() -> Any:
|
|
||||||
|
def _make_app() -> PokerModifierApp:
|
||||||
"""Create a PokerModifierApp with setup_gui mocked out."""
|
"""Create a PokerModifierApp with setup_gui mocked out."""
|
||||||
with patch(
|
with patch(
|
||||||
"python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui"
|
"python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui"
|
||||||
@ -422,7 +425,7 @@ class TestMainBlock:
|
|||||||
"""Test the if __name__ == '__main__' block."""
|
"""Test the if __name__ == '__main__' block."""
|
||||||
|
|
||||||
@patch("python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui")
|
@patch("python_pkg.poker_modifier_app.poker_modifier_app.PokerGuiMixin.setup_gui")
|
||||||
def test_main_block(self, _mock_setup: MagicMock) -> None:
|
def test_main_block(self, mock_setup: MagicMock) -> None:
|
||||||
with patch(
|
with patch(
|
||||||
"python_pkg.poker_modifier_app.poker_modifier_app.PokerModifierApp.run"
|
"python_pkg.poker_modifier_app.poker_modifier_app.PokerModifierApp.run"
|
||||||
):
|
):
|
||||||
|
|||||||
@ -221,14 +221,16 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]:
|
|||||||
(100, 480),
|
(100, 480),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Z\u0142o\u017cono\u015b\u0107: O(n\u00b2) pami\u0119ci \u2014 n = liczba pikseli/token\u00f3w",
|
"Z\u0142o\u017cono\u015b\u0107: O(n\u00b2) pami\u0119ci \u2014 n = liczba "
|
||||||
|
"pikseli/token\u00f3w",
|
||||||
16,
|
16,
|
||||||
"#EF9A9A",
|
"#EF9A9A",
|
||||||
FONT_R,
|
FONT_R,
|
||||||
(100, 535),
|
(100, 535),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Dlatego SegFormer u\u017cywa efficient attention (liniowa z\u0142o\u017cono\u015b\u0107)",
|
"Dlatego SegFormer u\u017cywa efficient attention (liniowa "
|
||||||
|
"z\u0142o\u017cono\u015b\u0107)",
|
||||||
15,
|
15,
|
||||||
"#78909C",
|
"#78909C",
|
||||||
FONT_R,
|
FONT_R,
|
||||||
@ -269,14 +271,16 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]:
|
|||||||
(80, 90),
|
(80, 90),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Encoder: obraz \u2192 cechy (zmniejsza rozdzielczo\u015b\u0107, wyci\u0105ga CO)",
|
"Encoder: obraz \u2192 cechy (zmniejsza rozdzielczo\u015b\u0107, "
|
||||||
|
"wyci\u0105ga CO)",
|
||||||
16,
|
16,
|
||||||
"#64B5F6",
|
"#64B5F6",
|
||||||
FONT_R,
|
FONT_R,
|
||||||
(100, 140),
|
(100, 140),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Decoder: cechy \u2192 mapa (zwi\u0119ksza rozdzielczo\u015b\u0107, odtwarza GDZIE)",
|
"Decoder: cechy \u2192 mapa (zwi\u0119ksza rozdzielczo\u015b\u0107, "
|
||||||
|
"odtwarza GDZIE)",
|
||||||
16,
|
16,
|
||||||
"#A5D6A7",
|
"#A5D6A7",
|
||||||
FONT_R,
|
FONT_R,
|
||||||
@ -334,7 +338,8 @@ def _transformer_seg_demo() -> list[CompositeVideoClip]:
|
|||||||
(80, 465),
|
(80, 465),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
" CNN lokal. \u2192 dilated (szersze RF) \u2192 transformer (global) \u2192 masked att.",
|
" CNN lokal. \u2192 dilated (szersze RF) \u2192 transformer (global) "
|
||||||
|
"\u2192 masked att.",
|
||||||
16,
|
16,
|
||||||
"#B0BEC5",
|
"#B0BEC5",
|
||||||
FONT_R,
|
FONT_R,
|
||||||
|
|||||||
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import importlib
|
import importlib
|
||||||
|
import importlib.util as _ilu
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@ -12,6 +14,7 @@ import numpy as np
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
||||||
# Add the source directory to sys.path so bare imports like
|
# Add the source directory to sys.path so bare imports like
|
||||||
@ -35,7 +38,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
|
|||||||
moviepy_mod = MagicMock()
|
moviepy_mod = MagicMock()
|
||||||
|
|
||||||
# VideoClip: needs to accept make_frame callable -> return mock with methods
|
# VideoClip: needs to accept make_frame callable -> return mock with methods
|
||||||
def _video_clip_factory(make_frame=None, duration=None, **kw):
|
def _video_clip_factory(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.make_frame = make_frame
|
clip.make_frame = make_frame
|
||||||
clip.duration = duration
|
clip.duration = duration
|
||||||
@ -57,14 +64,18 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
|
|||||||
|
|
||||||
moviepy_mod.VideoClip = _video_clip_factory
|
moviepy_mod.VideoClip = _video_clip_factory
|
||||||
|
|
||||||
def _color_clip_factory(size=None, color=None, **kw):
|
def _color_clip_factory(
|
||||||
|
_size: tuple[int, int] | None = None,
|
||||||
|
_color: tuple[int, ...] | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.with_duration.return_value = clip
|
clip.with_duration.return_value = clip
|
||||||
return clip
|
return clip
|
||||||
|
|
||||||
moviepy_mod.ColorClip = _color_clip_factory
|
moviepy_mod.ColorClip = _color_clip_factory
|
||||||
|
|
||||||
def _text_clip_factory(**kw):
|
def _text_clip_factory(**_kw: object) -> MagicMock:
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.with_duration.return_value = clip
|
clip.with_duration.return_value = clip
|
||||||
clip.with_position.return_value = clip
|
clip.with_position.return_value = clip
|
||||||
@ -72,7 +83,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
|
|||||||
|
|
||||||
moviepy_mod.TextClip = _text_clip_factory
|
moviepy_mod.TextClip = _text_clip_factory
|
||||||
|
|
||||||
def _composite_factory(clips=None, size=None, **kw):
|
def _composite_factory(
|
||||||
|
_clips: list[MagicMock] | None = None,
|
||||||
|
_size: tuple[int, int] | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.with_effects.return_value = clip
|
clip.with_effects.return_value = clip
|
||||||
clip.with_duration.return_value = clip
|
clip.with_duration.return_value = clip
|
||||||
@ -81,7 +96,11 @@ def _make_moviepy_mocks() -> dict[str, ModuleType | MagicMock]:
|
|||||||
|
|
||||||
moviepy_mod.CompositeVideoClip = _composite_factory
|
moviepy_mod.CompositeVideoClip = _composite_factory
|
||||||
|
|
||||||
def _concat_factory(clips=None, method=None, **kw):
|
def _concat_factory(
|
||||||
|
_clips: list[MagicMock] | None = None,
|
||||||
|
_method: str | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.write_videofile = MagicMock()
|
clip.write_videofile = MagicMock()
|
||||||
return clip
|
return clip
|
||||||
@ -117,7 +136,6 @@ for _name, _mock in _MOVIEPY_MOCKS.items():
|
|||||||
# modules (``_q24_classical.py``, etc.) find ``BG_COLOR`` etc.
|
# modules (``_q24_classical.py``, etc.) find ``BG_COLOR`` etc.
|
||||||
# 3. Register both under their full package paths for coverage.
|
# 3. Register both under their full package paths for coverage.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
import importlib.util as _ilu
|
|
||||||
|
|
||||||
# Load generate_images _q24_common first.
|
# Load generate_images _q24_common first.
|
||||||
_gen_q24_spec = _ilu.spec_from_file_location(
|
_gen_q24_spec = _ilu.spec_from_file_location(
|
||||||
@ -127,9 +145,9 @@ _gen_q24_spec = _ilu.spec_from_file_location(
|
|||||||
assert _gen_q24_spec is not None
|
assert _gen_q24_spec is not None
|
||||||
assert _gen_q24_spec.loader is not None
|
assert _gen_q24_spec.loader is not None
|
||||||
_q24_common_gen = _ilu.module_from_spec(_gen_q24_spec)
|
_q24_common_gen = _ilu.module_from_spec(_gen_q24_spec)
|
||||||
_gen_q24_spec.loader.exec_module(_q24_common_gen)
|
# Register BEFORE exec so @dataclass can resolve __module__ in Python 3.14+.
|
||||||
# Cache as bare name so generate_images imports work during _BARE_MODULES.
|
|
||||||
sys.modules["_q24_common"] = _q24_common_gen
|
sys.modules["_q24_common"] = _q24_common_gen
|
||||||
|
_gen_q24_spec.loader.exec_module(_q24_common_gen)
|
||||||
|
|
||||||
# Load top-level _q24_common.
|
# Load top-level _q24_common.
|
||||||
_top_q24_spec = _ilu.spec_from_file_location(
|
_top_q24_spec = _ilu.spec_from_file_location(
|
||||||
@ -139,6 +157,8 @@ _top_q24_spec = _ilu.spec_from_file_location(
|
|||||||
assert _top_q24_spec is not None
|
assert _top_q24_spec is not None
|
||||||
assert _top_q24_spec.loader is not None
|
assert _top_q24_spec.loader is not None
|
||||||
_q24_common_top = _ilu.module_from_spec(_top_q24_spec)
|
_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)
|
_top_q24_spec.loader.exec_module(_q24_common_top)
|
||||||
|
|
||||||
|
|
||||||
@ -205,11 +225,9 @@ _BARE_MODULES = [
|
|||||||
"generate_scheduling_diagrams",
|
"generate_scheduling_diagrams",
|
||||||
]
|
]
|
||||||
for _bare in _BARE_MODULES:
|
for _bare in _BARE_MODULES:
|
||||||
try:
|
with contextlib.suppress(ImportError):
|
||||||
_mod = importlib.import_module(_bare)
|
_mod = importlib.import_module(_bare)
|
||||||
sys.modules.setdefault(f"{_GEN_PKG}.{_bare}", _mod)
|
sys.modules.setdefault(f"{_GEN_PKG}.{_bare}", _mod)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Now swap _q24_common to the top-level version so that top-level source
|
# 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.
|
# modules (``_q24_classical.py`` etc.) find BG_COLOR, W, H, etc.
|
||||||
@ -242,6 +260,7 @@ def _no_savefig(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||||||
|
|
||||||
def _compat_auto_set_font_size(
|
def _compat_auto_set_font_size(
|
||||||
self: matplotlib.table.Table,
|
self: matplotlib.table.Table,
|
||||||
|
*,
|
||||||
value: bool = True,
|
value: bool = True,
|
||||||
**_kw: object,
|
**_kw: object,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@ -117,7 +117,7 @@ def test_get_file_metadata_no_match(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
p = tmp_path / "readme.txt"
|
p = tmp_path / "readme.txt"
|
||||||
p.write_text("No Przedmiot here", encoding="utf-8")
|
p.write_text("No Przedmiot here", encoding="utf-8")
|
||||||
num, subject, content = get_file_metadata(str(p))
|
num, subject, _ = get_file_metadata(str(p))
|
||||||
assert num == "00"
|
assert num == "00"
|
||||||
assert subject == "Ogólne"
|
assert subject == "Ogólne"
|
||||||
|
|
||||||
@ -386,7 +386,7 @@ def test_generate_anki_basic(tmp_path: Path) -> None:
|
|||||||
out_dir.mkdir()
|
out_dir.mkdir()
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch(f"{_PKG}.Path.__truediv__", side_effect=lambda self, x: tmp_path / x),
|
patch(f"{_PKG}.Path.__truediv__", side_effect=lambda _self, x: tmp_path / x),
|
||||||
patch(
|
patch(
|
||||||
f"{_PKG}.generate_anki.__defaults__",
|
f"{_PKG}.generate_anki.__defaults__",
|
||||||
(False, False, False),
|
(False, False, False),
|
||||||
|
|||||||
@ -14,6 +14,27 @@ mpl.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import (
|
||||||
|
draw_bdi_model,
|
||||||
|
draw_behavior_tree,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._agent_reactive import (
|
||||||
|
draw_3t_architecture,
|
||||||
|
draw_see_think_act,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
||||||
|
BG,
|
||||||
|
DPI,
|
||||||
|
GRAY5,
|
||||||
|
OUTPUT_DIR,
|
||||||
|
ArrowCfg,
|
||||||
|
BoxStyle,
|
||||||
|
DashedArrowCfg,
|
||||||
|
draw_arrow,
|
||||||
|
draw_box,
|
||||||
|
draw_dashed_arrow,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
||||||
|
|
||||||
_MOD = "python_pkg.praca_magisterska_video.generate_images"
|
_MOD = "python_pkg.praca_magisterska_video.generate_images"
|
||||||
@ -26,79 +47,41 @@ class TestAgentHelpers:
|
|||||||
"""Test draw_box, draw_arrow, draw_dashed_arrow and dataclasses."""
|
"""Test draw_box, draw_arrow, draw_dashed_arrow and dataclasses."""
|
||||||
|
|
||||||
def test_draw_box_rounded(self) -> None:
|
def test_draw_box_rounded(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
BoxStyle,
|
|
||||||
draw_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=True))
|
draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=True))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_box_not_rounded(self) -> None:
|
def test_draw_box_not_rounded(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
BoxStyle,
|
|
||||||
draw_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=False))
|
draw_box(ax, (0, 0), (1, 1), "hi", BoxStyle(rounded=False))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_box_no_style(self) -> None:
|
def test_draw_box_no_style(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
draw_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_box(ax, (0, 0), (1, 1), "hi")
|
draw_box(ax, (0, 0), (1, 1), "hi")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_arrow_with_label(self) -> None:
|
def test_draw_arrow_with_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
ArrowCfg,
|
|
||||||
draw_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_arrow(ax, (0, 0), (1, 1), ArrowCfg(label="lbl"))
|
draw_arrow(ax, (0, 0), (1, 1), ArrowCfg(label="lbl"))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_arrow_no_label(self) -> None:
|
def test_draw_arrow_no_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
draw_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_arrow(ax, (0, 0), (1, 1))
|
draw_arrow(ax, (0, 0), (1, 1))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_dashed_arrow_with_label(self) -> None:
|
def test_draw_dashed_arrow_with_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
DashedArrowCfg,
|
|
||||||
draw_dashed_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_dashed_arrow(ax, (0, 0), (1, 1), DashedArrowCfg(label="lbl"))
|
draw_dashed_arrow(ax, (0, 0), (1, 1), DashedArrowCfg(label="lbl"))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_dashed_arrow_no_label(self) -> None:
|
def test_draw_dashed_arrow_no_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
draw_dashed_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_dashed_arrow(ax, (0, 0), (1, 1))
|
draw_dashed_arrow(ax, (0, 0), (1, 1))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_dataclass_defaults(self) -> None:
|
def test_dataclass_defaults(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
ArrowCfg,
|
|
||||||
BoxStyle,
|
|
||||||
DashedArrowCfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
bs = BoxStyle()
|
bs = BoxStyle()
|
||||||
assert bs.rounded is True
|
assert bs.rounded is True
|
||||||
assert bs.fill == "white"
|
assert bs.fill == "white"
|
||||||
@ -108,13 +91,6 @@ class TestAgentHelpers:
|
|||||||
assert dc.label == ""
|
assert dc.label == ""
|
||||||
|
|
||||||
def test_module_constants(self) -> None:
|
def test_module_constants(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_agent_diagrams import (
|
|
||||||
BG,
|
|
||||||
DPI,
|
|
||||||
GRAY5,
|
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert DPI == 300
|
assert DPI == 300
|
||||||
assert BG == "white"
|
assert BG == "white"
|
||||||
assert isinstance(GRAY5, str)
|
assert isinstance(GRAY5, str)
|
||||||
@ -128,17 +104,9 @@ class TestAgentReactive:
|
|||||||
"""Test draw_see_think_act and draw_3t_architecture."""
|
"""Test draw_see_think_act and draw_3t_architecture."""
|
||||||
|
|
||||||
def test_draw_see_think_act(self) -> None:
|
def test_draw_see_think_act(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._agent_reactive import (
|
|
||||||
draw_see_think_act,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_see_think_act()
|
draw_see_think_act()
|
||||||
|
|
||||||
def test_draw_3t_architecture(self) -> None:
|
def test_draw_3t_architecture(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._agent_reactive import (
|
|
||||||
draw_3t_architecture,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_3t_architecture()
|
draw_3t_architecture()
|
||||||
|
|
||||||
|
|
||||||
@ -149,15 +117,7 @@ class TestAgentCognitive:
|
|||||||
"""Test draw_behavior_tree (covers all node types) and draw_bdi_model."""
|
"""Test draw_behavior_tree (covers all node types) and draw_bdi_model."""
|
||||||
|
|
||||||
def test_draw_behavior_tree(self) -> None:
|
def test_draw_behavior_tree(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import (
|
|
||||||
draw_behavior_tree,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_behavior_tree()
|
draw_behavior_tree()
|
||||||
|
|
||||||
def test_draw_bdi_model(self) -> None:
|
def test_draw_bdi_model(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._agent_cognitive import (
|
|
||||||
draw_bdi_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_bdi_model()
|
draw_bdi_model()
|
||||||
|
|||||||
@ -122,7 +122,7 @@ class TestAnkiApproach1:
|
|||||||
patch.object(
|
patch.object(
|
||||||
Path,
|
Path,
|
||||||
"open",
|
"open",
|
||||||
side_effect=lambda *a, **kw: StringIO(fake_md),
|
side_effect=lambda *_a, **_kw: StringIO(fake_md),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
main()
|
main()
|
||||||
@ -187,7 +187,7 @@ class TestAnkiApproach1:
|
|||||||
patch.object(
|
patch.object(
|
||||||
Path,
|
Path,
|
||||||
"open",
|
"open",
|
||||||
side_effect=lambda *a, **kw: StringIO(fake_md),
|
side_effect=lambda *_a, **_kw: StringIO(fake_md),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
main()
|
main()
|
||||||
@ -324,7 +324,7 @@ class TestAnkiApproach2:
|
|||||||
patch.object(
|
patch.object(
|
||||||
Path,
|
Path,
|
||||||
"open",
|
"open",
|
||||||
side_effect=lambda *a, **kw: StringIO(fake_md),
|
side_effect=lambda *_a, **_kw: StringIO(fake_md),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
main()
|
main()
|
||||||
@ -387,7 +387,7 @@ class TestAnkiApproach2:
|
|||||||
patch.object(
|
patch.object(
|
||||||
Path,
|
Path,
|
||||||
"open",
|
"open",
|
||||||
side_effect=lambda *a, **kw: StringIO(fake_md),
|
side_effect=lambda *_a, **_kw: StringIO(fake_md),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -17,6 +17,47 @@ mpl.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
generate_automata_diagrams as _auto_diags,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
||||||
|
BG,
|
||||||
|
DPI,
|
||||||
|
FS,
|
||||||
|
FS_SMALL,
|
||||||
|
FS_TITLE,
|
||||||
|
GRAY1,
|
||||||
|
GRAY2,
|
||||||
|
GRAY3,
|
||||||
|
GRAY4,
|
||||||
|
GRAY5,
|
||||||
|
INNER_RATIO,
|
||||||
|
LIGHT_BLUE,
|
||||||
|
LIGHT_GREEN,
|
||||||
|
LIGHT_RED,
|
||||||
|
LIGHT_YELLOW,
|
||||||
|
LN,
|
||||||
|
OUTPUT_DIR,
|
||||||
|
ArrowStyle,
|
||||||
|
LoopStyle,
|
||||||
|
StateStyle,
|
||||||
|
draw_curved_arrow,
|
||||||
|
draw_self_loop,
|
||||||
|
draw_state_circle,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._automata_fa import (
|
||||||
|
draw_fa_recognition,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._automata_lba import (
|
||||||
|
draw_lba_recognition,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._automata_pda import (
|
||||||
|
draw_pda_recognition,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._automata_tm import (
|
||||||
|
draw_tm_recognition,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
||||||
|
|
||||||
|
|
||||||
@ -27,10 +68,6 @@ class TestAutomataCommon:
|
|||||||
"""Test draw_state_circle, draw_curved_arrow, draw_self_loop."""
|
"""Test draw_state_circle, draw_curved_arrow, draw_self_loop."""
|
||||||
|
|
||||||
def test_state_circle_basic(self) -> None:
|
def test_state_circle_basic(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
draw_state_circle,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -38,11 +75,6 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_state_circle_accepting(self) -> None:
|
def test_state_circle_accepting(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
StateStyle,
|
|
||||||
draw_state_circle,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -50,11 +82,6 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_state_circle_initial(self) -> None:
|
def test_state_circle_initial(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
StateStyle,
|
|
||||||
draw_state_circle,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -62,11 +89,6 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_state_circle_both(self) -> None:
|
def test_state_circle_both(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
StateStyle,
|
|
||||||
draw_state_circle,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -76,20 +98,11 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_curved_arrow(self) -> None:
|
def test_curved_arrow(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
draw_curved_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_curved_arrow(ax, (0, 0), (1, 1), "a")
|
draw_curved_arrow(ax, (0, 0), (1, 1), "a")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_self_loop_top(self) -> None:
|
def test_self_loop_top(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
LoopStyle,
|
|
||||||
draw_self_loop,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -97,11 +110,6 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_self_loop_bottom(self) -> None:
|
def test_self_loop_bottom(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
LoopStyle,
|
|
||||||
draw_self_loop,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -109,10 +117,6 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_self_loop_default(self) -> None:
|
def test_self_loop_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
draw_self_loop,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -121,11 +125,6 @@ class TestAutomataCommon:
|
|||||||
|
|
||||||
def test_self_loop_unknown_direction(self) -> None:
|
def test_self_loop_unknown_direction(self) -> None:
|
||||||
"""Cover implicit else when direction is not top/bottom."""
|
"""Cover implicit else when direction is not top/bottom."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
LoopStyle,
|
|
||||||
draw_self_loop,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-2, 2)
|
ax.set_xlim(-2, 2)
|
||||||
ax.set_ylim(-2, 2)
|
ax.set_ylim(-2, 2)
|
||||||
@ -133,12 +132,6 @@ class TestAutomataCommon:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_dataclass_defaults(self) -> None:
|
def test_dataclass_defaults(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
ArrowStyle,
|
|
||||||
LoopStyle,
|
|
||||||
StateStyle,
|
|
||||||
)
|
|
||||||
|
|
||||||
ss = StateStyle()
|
ss = StateStyle()
|
||||||
assert ss.accepting is False
|
assert ss.accepting is False
|
||||||
assert ss.initial is False
|
assert ss.initial is False
|
||||||
@ -148,26 +141,6 @@ class TestAutomataCommon:
|
|||||||
assert ls.direction == "top"
|
assert ls.direction == "top"
|
||||||
|
|
||||||
def test_module_constants(self) -> None:
|
def test_module_constants(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_common import (
|
|
||||||
BG,
|
|
||||||
DPI,
|
|
||||||
FS,
|
|
||||||
FS_SMALL,
|
|
||||||
FS_TITLE,
|
|
||||||
GRAY1,
|
|
||||||
GRAY2,
|
|
||||||
GRAY3,
|
|
||||||
GRAY4,
|
|
||||||
GRAY5,
|
|
||||||
INNER_RATIO,
|
|
||||||
LIGHT_BLUE,
|
|
||||||
LIGHT_GREEN,
|
|
||||||
LIGHT_RED,
|
|
||||||
LIGHT_YELLOW,
|
|
||||||
LN,
|
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert DPI == 300
|
assert DPI == 300
|
||||||
assert BG == "white"
|
assert BG == "white"
|
||||||
assert isinstance(FS, int | float)
|
assert isinstance(FS, int | float)
|
||||||
@ -194,31 +167,15 @@ class TestAutomataDiagrams:
|
|||||||
"""Test all recognition diagram functions."""
|
"""Test all recognition diagram functions."""
|
||||||
|
|
||||||
def test_fa_recognition(self) -> None:
|
def test_fa_recognition(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_fa import (
|
|
||||||
draw_fa_recognition,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_fa_recognition()
|
draw_fa_recognition()
|
||||||
|
|
||||||
def test_pda_recognition(self) -> None:
|
def test_pda_recognition(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_pda import (
|
|
||||||
draw_pda_recognition,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_pda_recognition()
|
draw_pda_recognition()
|
||||||
|
|
||||||
def test_lba_recognition(self) -> None:
|
def test_lba_recognition(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_lba import (
|
|
||||||
draw_lba_recognition,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_lba_recognition()
|
draw_lba_recognition()
|
||||||
|
|
||||||
def test_tm_recognition(self) -> None:
|
def test_tm_recognition(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._automata_tm import (
|
|
||||||
draw_tm_recognition,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_tm_recognition()
|
draw_tm_recognition()
|
||||||
|
|
||||||
|
|
||||||
@ -229,15 +186,9 @@ class TestAutomataEntry:
|
|||||||
"""Verify generate_automata_diagrams exports are accessible."""
|
"""Verify generate_automata_diagrams exports are accessible."""
|
||||||
|
|
||||||
def test_all_exports(self) -> None:
|
def test_all_exports(self) -> None:
|
||||||
import python_pkg.praca_magisterska_video.generate_images.generate_automata_diagrams as mod
|
assert hasattr(_auto_diags, "__all__")
|
||||||
|
for name in _auto_diags.__all__:
|
||||||
assert hasattr(mod, "__all__")
|
assert hasattr(_auto_diags, name)
|
||||||
for name in mod.__all__:
|
|
||||||
assert hasattr(mod, name)
|
|
||||||
|
|
||||||
def test_output_dir(self) -> None:
|
def test_output_dir(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_automata_diagrams import (
|
assert isinstance(_auto_diags.OUTPUT_DIR, str)
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(OUTPUT_DIR, str)
|
|
||||||
|
|||||||
@ -13,6 +13,15 @@ mpl.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
generate_bf_negative_diagram as _bf_neg,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
|
||||||
|
_add_annotation_box,
|
||||||
|
generate_bf_negative_cycle,
|
||||||
|
generate_bf_negative_weights,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
||||||
|
|
||||||
_MOD = "python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram"
|
_MOD = "python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram"
|
||||||
@ -25,157 +34,100 @@ class TestBFHelpers:
|
|||||||
"""Test draw_node, _choose_edge_style, draw_edge, draw_neg_graph."""
|
"""Test draw_node, _choose_edge_style, draw_edge, draw_neg_graph."""
|
||||||
|
|
||||||
def test_draw_node_default(self) -> None:
|
def test_draw_node_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_node,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_node(ax, "S", (1, 1))
|
_bf_neg.draw_node(ax, "S", (1, 1))
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_node_current(self) -> None:
|
def test_draw_node_current(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_node,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_node(ax, "A", (1, 1), current=True, dist_label="2")
|
_bf_neg.draw_node(ax, "A", (1, 1), current=True, dist_label="2")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_node_visited(self) -> None:
|
def test_draw_node_visited(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_node,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_node(ax, "B", (1, 1), visited=True, dist_label="5")
|
_bf_neg.draw_node(ax, "B", (1, 1), visited=True, dist_label="5")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_node_error(self) -> None:
|
def test_draw_node_error(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_node,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_node(ax, "C", (1, 1), error=True, dist_label="?")
|
_bf_neg.draw_node(ax, "C", (1, 1), error=True, dist_label="?")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_node_no_dist_label(self) -> None:
|
def test_draw_node_no_dist_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_node,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_node(ax, "X", (1, 1), visited=True)
|
_bf_neg.draw_node(ax, "X", (1, 1), visited=True)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_choose_edge_style_cycle(self) -> None:
|
def test_choose_edge_style_cycle(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
_, lw, ls = _bf_neg._choose_edge_style(
|
||||||
_choose_edge_style,
|
|
||||||
)
|
|
||||||
|
|
||||||
color, lw, ls = _choose_edge_style(
|
|
||||||
negative=False, relaxed=False, highlighted=False, cycle_edge=True
|
negative=False, relaxed=False, highlighted=False, cycle_edge=True
|
||||||
)
|
)
|
||||||
assert ls == "--"
|
assert ls == "--"
|
||||||
assert lw == 2.5
|
assert lw == 2.5
|
||||||
|
|
||||||
def test_choose_edge_style_negative(self) -> None:
|
def test_choose_edge_style_negative(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
_, lw, ls = _bf_neg._choose_edge_style(
|
||||||
_choose_edge_style,
|
|
||||||
)
|
|
||||||
|
|
||||||
color, lw, ls = _choose_edge_style(
|
|
||||||
negative=True, relaxed=False, highlighted=False, cycle_edge=False
|
negative=True, relaxed=False, highlighted=False, cycle_edge=False
|
||||||
)
|
)
|
||||||
assert lw == 2.5
|
assert lw == 2.5
|
||||||
assert ls == "-"
|
assert ls == "-"
|
||||||
|
|
||||||
def test_choose_edge_style_relaxed(self) -> None:
|
def test_choose_edge_style_relaxed(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
_, lw, _ = _bf_neg._choose_edge_style(
|
||||||
_choose_edge_style,
|
|
||||||
)
|
|
||||||
|
|
||||||
color, lw, ls = _choose_edge_style(
|
|
||||||
negative=False, relaxed=True, highlighted=False, cycle_edge=False
|
negative=False, relaxed=True, highlighted=False, cycle_edge=False
|
||||||
)
|
)
|
||||||
assert lw == 2.5
|
assert lw == 2.5
|
||||||
|
|
||||||
def test_choose_edge_style_highlighted(self) -> None:
|
def test_choose_edge_style_highlighted(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
color, _, ls = _bf_neg._choose_edge_style(
|
||||||
_choose_edge_style,
|
|
||||||
)
|
|
||||||
|
|
||||||
color, lw, ls = _choose_edge_style(
|
|
||||||
negative=False, relaxed=False, highlighted=True, cycle_edge=False
|
negative=False, relaxed=False, highlighted=True, cycle_edge=False
|
||||||
)
|
)
|
||||||
assert ls == "-"
|
assert ls == "-"
|
||||||
assert color == "#1565C0"
|
assert color == "#1565C0"
|
||||||
|
|
||||||
def test_choose_edge_style_default(self) -> None:
|
def test_choose_edge_style_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
color, lw, _ = _bf_neg._choose_edge_style(
|
||||||
GRAY3,
|
|
||||||
_choose_edge_style,
|
|
||||||
)
|
|
||||||
|
|
||||||
color, lw, ls = _choose_edge_style(
|
|
||||||
negative=False, relaxed=False, highlighted=False, cycle_edge=False
|
negative=False, relaxed=False, highlighted=False, cycle_edge=False
|
||||||
)
|
)
|
||||||
assert color == GRAY3
|
assert color == _bf_neg.GRAY3
|
||||||
assert lw == 1.5
|
assert lw == 1.5
|
||||||
|
|
||||||
def test_draw_edge_no_offset(self) -> None:
|
def test_draw_edge_no_offset(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_edge,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_edge(ax, (0, 0), (2, 2), 3)
|
_bf_neg.draw_edge(ax, (0, 0), (2, 2), 3)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_edge_with_offset(self) -> None:
|
def test_draw_edge_with_offset(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_edge,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_edge(ax, (0, 0), (2, 2), -3, negative=True, offset=0.3)
|
_bf_neg.draw_edge(ax, (0, 0), (2, 2), -3, negative=True, offset=0.3)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_edge_highlighted(self) -> None:
|
def test_draw_edge_highlighted(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_edge,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_edge(ax, (0, 0), (2, 2), 5, highlighted=True)
|
_bf_neg.draw_edge(ax, (0, 0), (2, 2), 5, highlighted=True)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_edge_cycle(self) -> None:
|
def test_draw_edge_cycle(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_edge,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(-1, 5)
|
ax.set_xlim(-1, 5)
|
||||||
ax.set_ylim(-1, 5)
|
ax.set_ylim(-1, 5)
|
||||||
draw_edge(ax, (0, 0), (2, 2), -2, cycle_edge=True)
|
_bf_neg.draw_edge(ax, (0, 0), (2, 2), -2, cycle_edge=True)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@ -184,36 +136,20 @@ class TestDrawNegGraph:
|
|||||||
|
|
||||||
def test_minimal(self) -> None:
|
def test_minimal(self) -> None:
|
||||||
"""All-defaults: visited, relaxed, dist, error_nodes all None."""
|
"""All-defaults: visited, relaxed, dist, error_nodes all None."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
NEG_EDGES,
|
|
||||||
draw_neg_graph,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_neg_graph(ax, NEG_EDGES)
|
_bf_neg.draw_neg_graph(ax, _bf_neg.NEG_EDGES)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_with_title(self) -> None:
|
def test_with_title(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
NEG_EDGES,
|
|
||||||
draw_neg_graph,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_neg_graph(ax, NEG_EDGES, title="Test")
|
_bf_neg.draw_neg_graph(ax, _bf_neg.NEG_EDGES, title="Test")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_with_all_options(self) -> None:
|
def test_with_all_options(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
NEG_EDGES,
|
|
||||||
NEG_POS,
|
|
||||||
draw_neg_graph,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_neg_graph(
|
_bf_neg.draw_neg_graph(
|
||||||
ax,
|
ax,
|
||||||
NEG_EDGES,
|
_bf_neg.NEG_EDGES,
|
||||||
title="Full",
|
title="Full",
|
||||||
dist={"S": "0", "A": "1", "B": "5", "C": "4"},
|
dist={"S": "0", "A": "1", "B": "5", "C": "4"},
|
||||||
current="S",
|
current="S",
|
||||||
@ -221,19 +157,15 @@ class TestDrawNegGraph:
|
|||||||
relaxed_edges={("S", "A")},
|
relaxed_edges={("S", "A")},
|
||||||
error_nodes={"C"},
|
error_nodes={"C"},
|
||||||
extra_edges=[("C", "B", -3)],
|
extra_edges=[("C", "B", -3)],
|
||||||
node_positions=NEG_POS,
|
node_positions=_bf_neg.NEG_POS,
|
||||||
)
|
)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_explicit_node_positions(self) -> None:
|
def test_explicit_node_positions(self) -> None:
|
||||||
"""Cover node_positions is not None branch."""
|
"""Cover node_positions is not None branch."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
|
||||||
draw_neg_graph,
|
|
||||||
)
|
|
||||||
|
|
||||||
pos = {"X": (1.0, 1.0), "Y": (3.0, 1.0)}
|
pos = {"X": (1.0, 1.0), "Y": (3.0, 1.0)}
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_neg_graph(
|
_bf_neg.draw_neg_graph(
|
||||||
ax,
|
ax,
|
||||||
[("X", "Y", 2)],
|
[("X", "Y", 2)],
|
||||||
node_positions=pos,
|
node_positions=pos,
|
||||||
@ -250,24 +182,12 @@ class TestBFDiagramFunctions:
|
|||||||
"""Test the main diagram generation functions."""
|
"""Test the main diagram generation functions."""
|
||||||
|
|
||||||
def test_generate_bf_negative_weights(self) -> None:
|
def test_generate_bf_negative_weights(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
|
|
||||||
generate_bf_negative_weights,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_bf_negative_weights()
|
generate_bf_negative_weights()
|
||||||
|
|
||||||
def test_generate_bf_negative_cycle(self) -> None:
|
def test_generate_bf_negative_cycle(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
|
|
||||||
generate_bf_negative_cycle,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_bf_negative_cycle()
|
generate_bf_negative_cycle()
|
||||||
|
|
||||||
def test_add_annotation_box(self) -> None:
|
def test_add_annotation_box(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._bf_negative_diagrams import (
|
|
||||||
_add_annotation_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_add_annotation_box(ax, 1, 1, "test", color="red", bg_color="white")
|
_add_annotation_box(ax, 1, 1, "test", color="red", bg_color="white")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
@ -277,40 +197,20 @@ class TestBFModuleConstants:
|
|||||||
"""Verify module-level constants."""
|
"""Verify module-level constants."""
|
||||||
|
|
||||||
def test_constants(self) -> None:
|
def test_constants(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_bf_negative_diagram import (
|
assert _bf_neg.DPI == 300
|
||||||
BG,
|
assert _bf_neg.BG == "white"
|
||||||
DPI,
|
assert isinstance(_bf_neg.FS, int | float)
|
||||||
FS,
|
assert isinstance(_bf_neg.FS_EDGE, int | float)
|
||||||
FS_EDGE,
|
assert isinstance(_bf_neg.FS_SMALL, int | float)
|
||||||
FS_SMALL,
|
assert isinstance(_bf_neg.FS_TITLE, int | float)
|
||||||
FS_TITLE,
|
assert isinstance(_bf_neg.GRAY1, str)
|
||||||
GRAY1,
|
assert isinstance(_bf_neg.GRAY2, str)
|
||||||
GRAY2,
|
assert isinstance(_bf_neg.GRAY3, str)
|
||||||
GRAY3,
|
assert isinstance(_bf_neg.GRAY4, str)
|
||||||
GRAY4,
|
assert isinstance(_bf_neg.LIGHT_GREEN, str)
|
||||||
LIGHT_GREEN,
|
assert isinstance(_bf_neg.LIGHT_RED, str)
|
||||||
LIGHT_RED,
|
assert isinstance(_bf_neg.LIGHT_YELLOW, str)
|
||||||
LIGHT_YELLOW,
|
assert isinstance(_bf_neg.LN, str)
|
||||||
LN,
|
assert isinstance(_bf_neg.OUTPUT_DIR, str)
|
||||||
NEG_EDGES,
|
assert len(_bf_neg.NEG_EDGES) > 0
|
||||||
NEG_POS,
|
assert len(_bf_neg.NEG_POS) > 0
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert DPI == 300
|
|
||||||
assert BG == "white"
|
|
||||||
assert isinstance(FS, int | float)
|
|
||||||
assert isinstance(FS_EDGE, int | float)
|
|
||||||
assert isinstance(FS_SMALL, int | float)
|
|
||||||
assert isinstance(FS_TITLE, int | float)
|
|
||||||
assert isinstance(GRAY1, str)
|
|
||||||
assert isinstance(GRAY2, str)
|
|
||||||
assert isinstance(GRAY3, str)
|
|
||||||
assert isinstance(GRAY4, str)
|
|
||||||
assert isinstance(LIGHT_GREEN, str)
|
|
||||||
assert isinstance(LIGHT_RED, str)
|
|
||||||
assert isinstance(LIGHT_YELLOW, str)
|
|
||||||
assert isinstance(LN, str)
|
|
||||||
assert isinstance(OUTPUT_DIR, str)
|
|
||||||
assert len(NEG_EDGES) > 0
|
|
||||||
assert len(NEG_POS) > 0
|
|
||||||
|
|||||||
@ -17,6 +17,24 @@ mpl.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
generate_normalization_diagrams as _norm_mod,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
|
||||||
|
draw_3nf,
|
||||||
|
draw_4nf,
|
||||||
|
draw_bcnf,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
|
||||||
|
draw_0nf,
|
||||||
|
draw_1nf,
|
||||||
|
draw_2nf,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._norm_higher import (
|
||||||
|
draw_5nf,
|
||||||
|
draw_summary_flow,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
||||||
|
|
||||||
_GEN = (
|
_GEN = (
|
||||||
@ -34,51 +52,28 @@ class TestNormHelpers:
|
|||||||
"""Test _compute_col_widths, draw_table, create_figure, add_arrow, add_label."""
|
"""Test _compute_col_widths, draw_table, create_figure, add_arrow, add_label."""
|
||||||
|
|
||||||
def test_compute_col_widths_normal(self) -> None:
|
def test_compute_col_widths_normal(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
result = _norm_mod._compute_col_widths(["Name", "Age"], [["Alice", "30"]])
|
||||||
_compute_col_widths,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = _compute_col_widths(["Name", "Age"], [["Alice", "30"]])
|
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
assert all(w >= 0.5 for w in result)
|
assert all(w >= 0.5 for w in result)
|
||||||
|
|
||||||
def test_compute_col_widths_jagged(self) -> None:
|
def test_compute_col_widths_jagged(self) -> None:
|
||||||
"""Row shorter than headers → c < len(r) False branch."""
|
"""Row shorter than headers → c < len(r) False branch."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
result = _norm_mod._compute_col_widths(["A", "B", "C"], [["x"]])
|
||||||
_compute_col_widths,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = _compute_col_widths(["A", "B", "C"], [["x"]])
|
|
||||||
assert len(result) == 3
|
assert len(result) == 3
|
||||||
|
|
||||||
def test_draw_table_auto_widths(self) -> None:
|
def test_draw_table_auto_widths(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
_norm_mod.draw_table(ax, 0, 5, "T", ["A", "B"], [["1", "2"]])
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
draw_table(ax, 0, 5, "T", ["A", "B"], [["1", "2"]])
|
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_table_explicit_widths(self) -> None:
|
def test_draw_table_explicit_widths(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
_norm_mod.draw_table(ax, 0, 5, "T", ["A"], [["x"]], col_widths=[1.0])
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
draw_table(ax, 0, 5, "T", ["A"], [["x"]], col_widths=[1.0])
|
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_table_highlight_cols(self) -> None:
|
def test_draw_table_highlight_cols(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
_norm_mod.draw_table(
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
draw_table(
|
|
||||||
ax,
|
ax,
|
||||||
0,
|
0,
|
||||||
5,
|
5,
|
||||||
@ -90,13 +85,8 @@ class TestNormHelpers:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_table_highlight_rows(self) -> None:
|
def test_draw_table_highlight_rows(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
_norm_mod.draw_table(
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
draw_table(
|
|
||||||
ax,
|
ax,
|
||||||
0,
|
0,
|
||||||
5,
|
5,
|
||||||
@ -108,13 +98,8 @@ class TestNormHelpers:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_table_highlight_cells(self) -> None:
|
def test_draw_table_highlight_cells(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
_norm_mod.draw_table(
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
draw_table(
|
|
||||||
ax,
|
ax,
|
||||||
0,
|
0,
|
||||||
5,
|
5,
|
||||||
@ -126,13 +111,8 @@ class TestNormHelpers:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_table_strikethrough(self) -> None:
|
def test_draw_table_strikethrough(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
_norm_mod.draw_table(
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
draw_table(
|
|
||||||
ax,
|
ax,
|
||||||
0,
|
0,
|
||||||
5,
|
5,
|
||||||
@ -145,13 +125,8 @@ class TestNormHelpers:
|
|||||||
|
|
||||||
def test_draw_table_all_options(self) -> None:
|
def test_draw_table_all_options(self) -> None:
|
||||||
"""All highlight/strikethrough at once, with matching+non-matching cells."""
|
"""All highlight/strikethrough at once, with matching+non-matching cells."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
create_figure,
|
w, h = _norm_mod.draw_table(
|
||||||
draw_table,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
w, h = draw_table(
|
|
||||||
ax,
|
ax,
|
||||||
0,
|
0,
|
||||||
5,
|
5,
|
||||||
@ -169,65 +144,35 @@ class TestNormHelpers:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_create_figure(self) -> None:
|
def test_create_figure(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure(10, 8)
|
||||||
create_figure,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure(10, 8)
|
|
||||||
assert fig is not None
|
assert fig is not None
|
||||||
assert ax is not None
|
assert ax is not None
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_add_arrow_with_label(self) -> None:
|
def test_add_arrow_with_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
add_arrow,
|
_norm_mod.add_arrow(ax, 0, 5, 3, 5, "lbl", color="black")
|
||||||
create_figure,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
add_arrow(ax, 0, 5, 3, 5, "lbl", color="black")
|
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_add_arrow_no_label(self) -> None:
|
def test_add_arrow_no_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
add_arrow,
|
_norm_mod.add_arrow(ax, 0, 5, 3, 5)
|
||||||
create_figure,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
add_arrow(ax, 0, 5, 3, 5)
|
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_add_label(self) -> None:
|
def test_add_label(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
fig, ax = _norm_mod.create_figure()
|
||||||
add_label,
|
_norm_mod.add_label(ax, 0, 5, "note", fontsize=10, color="red")
|
||||||
create_figure,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = create_figure()
|
|
||||||
add_label(ax, 0, 5, "note", fontsize=10, color="red")
|
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_module_constants(self) -> None:
|
def test_module_constants(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_normalization_diagrams import (
|
assert _norm_mod.DPI == 300
|
||||||
CELL_COLOR,
|
assert isinstance(_norm_mod.OUTPUT_DIR, str)
|
||||||
DPI,
|
assert isinstance(_norm_mod.HEADER_COLOR, str)
|
||||||
FD_ARROW_COLOR,
|
assert isinstance(_norm_mod.CELL_COLOR, str)
|
||||||
FIXED_COLOR,
|
assert isinstance(_norm_mod.HIGHLIGHT_COLOR, str)
|
||||||
FONT_SIZE,
|
assert isinstance(_norm_mod.FIXED_COLOR, str)
|
||||||
HEADER_COLOR,
|
assert isinstance(_norm_mod.FD_ARROW_COLOR, str)
|
||||||
HIGHLIGHT_COLOR,
|
assert isinstance(_norm_mod.FONT_SIZE, int | float)
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert DPI == 300
|
|
||||||
assert isinstance(OUTPUT_DIR, str)
|
|
||||||
assert isinstance(HEADER_COLOR, str)
|
|
||||||
assert isinstance(CELL_COLOR, str)
|
|
||||||
assert isinstance(HIGHLIGHT_COLOR, str)
|
|
||||||
assert isinstance(FIXED_COLOR, str)
|
|
||||||
assert isinstance(FD_ARROW_COLOR, str)
|
|
||||||
assert isinstance(FONT_SIZE, int | float)
|
|
||||||
|
|
||||||
|
|
||||||
# ── _norm_basic (draw_table has positional-arg signature mismatch) ─────
|
# ── _norm_basic (draw_table has positional-arg signature mismatch) ─────
|
||||||
@ -243,29 +188,17 @@ class TestNormBasic:
|
|||||||
|
|
||||||
@patch(f"{_BASIC}.add_arrow")
|
@patch(f"{_BASIC}.add_arrow")
|
||||||
@patch(f"{_BASIC}.draw_table")
|
@patch(f"{_BASIC}.draw_table")
|
||||||
def test_draw_0nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_0nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
|
|
||||||
draw_0nf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_0nf()
|
draw_0nf()
|
||||||
|
|
||||||
@patch(f"{_BASIC}.add_arrow")
|
@patch(f"{_BASIC}.add_arrow")
|
||||||
@patch(f"{_BASIC}.draw_table")
|
@patch(f"{_BASIC}.draw_table")
|
||||||
def test_draw_1nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_1nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
|
|
||||||
draw_1nf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_1nf()
|
draw_1nf()
|
||||||
|
|
||||||
@patch(f"{_BASIC}.add_arrow")
|
@patch(f"{_BASIC}.add_arrow")
|
||||||
@patch(f"{_BASIC}.draw_table")
|
@patch(f"{_BASIC}.draw_table")
|
||||||
def test_draw_2nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_2nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_basic import (
|
|
||||||
draw_2nf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_2nf()
|
draw_2nf()
|
||||||
|
|
||||||
|
|
||||||
@ -277,29 +210,17 @@ class TestNormAdvanced:
|
|||||||
|
|
||||||
@patch(f"{_ADV}.add_arrow")
|
@patch(f"{_ADV}.add_arrow")
|
||||||
@patch(f"{_ADV}.draw_table")
|
@patch(f"{_ADV}.draw_table")
|
||||||
def test_draw_3nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_3nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
|
|
||||||
draw_3nf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_3nf()
|
draw_3nf()
|
||||||
|
|
||||||
@patch(f"{_ADV}.add_arrow")
|
@patch(f"{_ADV}.add_arrow")
|
||||||
@patch(f"{_ADV}.draw_table")
|
@patch(f"{_ADV}.draw_table")
|
||||||
def test_draw_bcnf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_bcnf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
|
|
||||||
draw_bcnf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_bcnf()
|
draw_bcnf()
|
||||||
|
|
||||||
@patch(f"{_ADV}.add_arrow")
|
@patch(f"{_ADV}.add_arrow")
|
||||||
@patch(f"{_ADV}.draw_table")
|
@patch(f"{_ADV}.draw_table")
|
||||||
def test_draw_4nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_4nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_advanced import (
|
|
||||||
draw_4nf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_4nf()
|
draw_4nf()
|
||||||
|
|
||||||
|
|
||||||
@ -311,18 +232,10 @@ class TestNormHigher:
|
|||||||
|
|
||||||
@patch(f"{_HIGH}.add_arrow")
|
@patch(f"{_HIGH}.add_arrow")
|
||||||
@patch(f"{_HIGH}.draw_table")
|
@patch(f"{_HIGH}.draw_table")
|
||||||
def test_draw_5nf(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_5nf(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_higher import (
|
|
||||||
draw_5nf,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_5nf()
|
draw_5nf()
|
||||||
|
|
||||||
@patch(f"{_HIGH}.add_arrow")
|
@patch(f"{_HIGH}.add_arrow")
|
||||||
@patch(f"{_HIGH}.draw_table")
|
@patch(f"{_HIGH}.draw_table")
|
||||||
def test_draw_summary_flow(self, _mock_dt: MagicMock, _mock_aa: MagicMock) -> None:
|
def test_draw_summary_flow(self, mock_dt: MagicMock, mock_aa: MagicMock) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._norm_higher import (
|
|
||||||
draw_summary_flow,
|
|
||||||
)
|
|
||||||
|
|
||||||
draw_summary_flow()
|
draw_summary_flow()
|
||||||
|
|||||||
@ -16,6 +16,19 @@ mpl.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
_pattern_pillars_observer as _pat_pillars,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
_pattern_template_catalog as _pat_tmpl,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
generate_pattern_diagrams as _pat_diags,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._pattern_navigation import (
|
||||||
|
generate_pattern_language_navigation,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
||||||
|
|
||||||
_GEN = "python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams"
|
_GEN = "python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams"
|
||||||
@ -31,74 +44,47 @@ class TestPatternConstants:
|
|||||||
"""Constants and module-level values."""
|
"""Constants and module-level values."""
|
||||||
|
|
||||||
def test_dpi(self) -> None:
|
def test_dpi(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
assert _pat_diags.DPI == 300
|
||||||
DPI,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert DPI == 300
|
|
||||||
|
|
||||||
def test_bg(self) -> None:
|
def test_bg(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
assert _pat_diags.BG == "white"
|
||||||
BG,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert BG == "white"
|
|
||||||
|
|
||||||
def test_gray_constants(self) -> None:
|
def test_gray_constants(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
assert all(
|
||||||
GRAY1,
|
isinstance(g, str)
|
||||||
GRAY2,
|
for g in [
|
||||||
GRAY3,
|
_pat_diags.GRAY1,
|
||||||
GRAY4,
|
_pat_diags.GRAY2,
|
||||||
GRAY5,
|
_pat_diags.GRAY3,
|
||||||
|
_pat_diags.GRAY4,
|
||||||
|
_pat_diags.GRAY5,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert all(isinstance(g, str) for g in [GRAY1, GRAY2, GRAY3, GRAY4, GRAY5])
|
|
||||||
|
|
||||||
def test_band_heights(self) -> None:
|
def test_band_heights(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
assert len(_pat_diags._BAND_HEIGHTS) == 5
|
||||||
_BAND_HEIGHTS,
|
assert all(isinstance(h, float) for h in _pat_diags._BAND_HEIGHTS)
|
||||||
)
|
|
||||||
|
|
||||||
assert len(_BAND_HEIGHTS) == 5
|
|
||||||
assert all(isinstance(h, float) for h in _BAND_HEIGHTS)
|
|
||||||
|
|
||||||
def test_output_dir_is_str(self) -> None:
|
def test_output_dir_is_str(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
assert isinstance(_pat_diags.OUTPUT_DIR, str)
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(OUTPUT_DIR, str)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDrawBox:
|
class TestDrawBox:
|
||||||
"""Test draw_box helper."""
|
"""Test draw_box helper."""
|
||||||
|
|
||||||
def test_rounded(self) -> None:
|
def test_rounded(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
|
||||||
draw_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_box(ax, 0, 0, 1, 1, "test", rounded=True)
|
_pat_diags.draw_box(ax, 0, 0, 1, 1, "test", rounded=True)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_not_rounded(self) -> None:
|
def test_not_rounded(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
|
||||||
draw_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_box(ax, 0, 0, 1, 1, "test", rounded=False)
|
_pat_diags.draw_box(ax, 0, 0, 1, 1, "test", rounded=False)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_custom_style(self) -> None:
|
def test_custom_style(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
|
||||||
draw_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_box(
|
_pat_diags.draw_box(
|
||||||
ax,
|
ax,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -120,21 +106,13 @@ class TestDrawArrow:
|
|||||||
"""Test draw_arrow helper."""
|
"""Test draw_arrow helper."""
|
||||||
|
|
||||||
def test_default(self) -> None:
|
def test_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
|
||||||
draw_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_arrow(ax, 0, 0, 1, 1)
|
_pat_diags.draw_arrow(ax, 0, 0, 1, 1)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_custom(self) -> None:
|
def test_custom(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_pattern_diagrams import (
|
|
||||||
draw_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_arrow(ax, 0, 0, 1, 1, lw=2.5, style="<->", color="red")
|
_pat_diags.draw_arrow(ax, 0, 0, 1, 1, lw=2.5, style="<->", color="red")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@ -145,22 +123,14 @@ class TestPatternTemplate:
|
|||||||
"""Test generate_pattern_template."""
|
"""Test generate_pattern_template."""
|
||||||
|
|
||||||
def test_runs(self) -> None:
|
def test_runs(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._pattern_template_catalog import (
|
_pat_tmpl.generate_pattern_template()
|
||||||
generate_pattern_template,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_pattern_template()
|
|
||||||
|
|
||||||
|
|
||||||
class TestCatalogMap:
|
class TestCatalogMap:
|
||||||
"""Test generate_catalog_map."""
|
"""Test generate_catalog_map."""
|
||||||
|
|
||||||
def test_runs(self) -> None:
|
def test_runs(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._pattern_template_catalog import (
|
_pat_tmpl.generate_catalog_map()
|
||||||
generate_catalog_map,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_catalog_map()
|
|
||||||
|
|
||||||
|
|
||||||
# ── _pattern_pillars_observer ──────────────────────────────────────────
|
# ── _pattern_pillars_observer ──────────────────────────────────────────
|
||||||
@ -170,34 +140,22 @@ class TestThreePillars:
|
|||||||
"""Test generate_three_pillars."""
|
"""Test generate_three_pillars."""
|
||||||
|
|
||||||
def test_runs(self) -> None:
|
def test_runs(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import (
|
_pat_pillars.generate_three_pillars()
|
||||||
generate_three_pillars,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_three_pillars()
|
|
||||||
|
|
||||||
|
|
||||||
class TestObserverCard:
|
class TestObserverCard:
|
||||||
"""Test generate_observer_card_filled."""
|
"""Test generate_observer_card_filled."""
|
||||||
|
|
||||||
def test_runs(self) -> None:
|
def test_runs(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import (
|
_pat_pillars.generate_observer_card_filled()
|
||||||
generate_observer_card_filled,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_observer_card_filled()
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetObserverBandHeight:
|
class TestGetObserverBandHeight:
|
||||||
"""Test _get_observer_band_height."""
|
"""Test _get_observer_band_height."""
|
||||||
|
|
||||||
def test_all_indices(self) -> None:
|
def test_all_indices(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._pattern_pillars_observer import (
|
|
||||||
_get_observer_band_height,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
h = _get_observer_band_height(i)
|
h = _pat_pillars._get_observer_band_height(i)
|
||||||
assert isinstance(h, float)
|
assert isinstance(h, float)
|
||||||
assert h > 0
|
assert h > 0
|
||||||
|
|
||||||
@ -209,8 +167,4 @@ class TestPatternLanguageNavigation:
|
|||||||
"""Test generate_pattern_language_navigation."""
|
"""Test generate_pattern_language_navigation."""
|
||||||
|
|
||||||
def test_runs(self) -> None:
|
def test_runs(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._pattern_navigation import (
|
|
||||||
generate_pattern_language_navigation,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_pattern_language_navigation()
|
generate_pattern_language_navigation()
|
||||||
|
|||||||
@ -16,6 +16,36 @@ mpl.use("Agg")
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images import (
|
||||||
|
generate_process_diagrams as _proc_diags,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
||||||
|
_draw_bpmn_elements,
|
||||||
|
_draw_bpmn_legend,
|
||||||
|
_draw_bpmn_pool_and_lanes,
|
||||||
|
_draw_uml_elements,
|
||||||
|
_draw_uml_legend,
|
||||||
|
generate_bpmn,
|
||||||
|
generate_uml_activity,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
||||||
|
_draw_epc_branches,
|
||||||
|
_draw_epc_connector,
|
||||||
|
_draw_epc_event,
|
||||||
|
_draw_epc_flow,
|
||||||
|
_draw_epc_function,
|
||||||
|
_draw_epc_legend,
|
||||||
|
generate_epc,
|
||||||
|
)
|
||||||
|
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
||||||
|
_draw_fc_elements,
|
||||||
|
_draw_fc_io_shape,
|
||||||
|
_draw_fc_legend,
|
||||||
|
_draw_fc_process_box,
|
||||||
|
_draw_fc_terminal,
|
||||||
|
generate_flowchart,
|
||||||
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
||||||
|
|
||||||
_GEN = "python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams"
|
_GEN = "python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams"
|
||||||
@ -31,37 +61,21 @@ class TestProcessConstants:
|
|||||||
"""Constants and module-level values."""
|
"""Constants and module-level values."""
|
||||||
|
|
||||||
def test_dpi(self) -> None:
|
def test_dpi(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
assert _proc_diags.DPI == 300
|
||||||
DPI,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert DPI == 300
|
|
||||||
|
|
||||||
def test_bg_color(self) -> None:
|
def test_bg_color(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
assert _proc_diags.BG_COLOR == "white"
|
||||||
BG_COLOR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert BG_COLOR == "white"
|
|
||||||
|
|
||||||
def test_output_dir(self) -> None:
|
def test_output_dir(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
assert isinstance(_proc_diags.OUTPUT_DIR, str)
|
||||||
OUTPUT_DIR,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(OUTPUT_DIR, str)
|
|
||||||
|
|
||||||
|
|
||||||
class TestProcessDrawArrow:
|
class TestProcessDrawArrow:
|
||||||
"""Test draw_arrow helper."""
|
"""Test draw_arrow helper."""
|
||||||
|
|
||||||
def test_default(self) -> None:
|
def test_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_arrow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_arrow(ax, 0, 0, 1, 1)
|
_proc_diags.draw_arrow(ax, 0, 0, 1, 1)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@ -69,12 +83,8 @@ class TestProcessDrawLine:
|
|||||||
"""Test draw_line helper."""
|
"""Test draw_line helper."""
|
||||||
|
|
||||||
def test_default(self) -> None:
|
def test_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_line,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_line(ax, 0, 0, 5, 5)
|
_proc_diags.draw_line(ax, 0, 0, 5, 5)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@ -82,21 +92,15 @@ class TestProcessDrawRoundedRect:
|
|||||||
"""Test draw_rounded_rect helper."""
|
"""Test draw_rounded_rect helper."""
|
||||||
|
|
||||||
def test_default(self) -> None:
|
def test_default(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_rounded_rect,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_rounded_rect(ax, 5, 5, 10, 4, "Hello")
|
_proc_diags.draw_rounded_rect(ax, 5, 5, 10, 4, "Hello")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_custom_params(self) -> None:
|
def test_custom_params(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_rounded_rect,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_rounded_rect(ax, 0, 0, 8, 3, "styled", fill="#CCC", lw=3, fontsize=12)
|
_proc_diags.draw_rounded_rect(
|
||||||
|
ax, 0, 0, 8, 3, "styled", fill="#CCC", lw=3, fontsize=12
|
||||||
|
)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@ -104,30 +108,18 @@ class TestProcessDrawDiamond:
|
|||||||
"""Test draw_diamond helper."""
|
"""Test draw_diamond helper."""
|
||||||
|
|
||||||
def test_with_text(self) -> None:
|
def test_with_text(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_diamond,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_diamond(ax, 5, 5, 3, "XOR")
|
_proc_diags.draw_diamond(ax, 5, 5, 3, "XOR")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_without_text(self) -> None:
|
def test_without_text(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_diamond,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_diamond(ax, 5, 5, 3)
|
_proc_diags.draw_diamond(ax, 5, 5, 3)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_custom_fill(self) -> None:
|
def test_custom_fill(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_process_diagrams import (
|
|
||||||
draw_diamond,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
draw_diamond(ax, 5, 5, 3, "Y", fill="#EEE", fontsize=12)
|
_proc_diags.draw_diamond(ax, 5, 5, 3, "Y", fill="#EEE", fontsize=12)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
@ -138,17 +130,9 @@ class TestBPMN:
|
|||||||
"""Test generate_bpmn and its sub-helpers."""
|
"""Test generate_bpmn and its sub-helpers."""
|
||||||
|
|
||||||
def test_generate_bpmn(self) -> None:
|
def test_generate_bpmn(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
generate_bpmn,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_bpmn()
|
generate_bpmn()
|
||||||
|
|
||||||
def test_draw_bpmn_pool_and_lanes(self) -> None:
|
def test_draw_bpmn_pool_and_lanes(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
_draw_bpmn_pool_and_lanes,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 110)
|
ax.set_xlim(0, 110)
|
||||||
ax.set_ylim(0, 75)
|
ax.set_ylim(0, 75)
|
||||||
@ -157,10 +141,6 @@ class TestBPMN:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_bpmn_elements(self) -> None:
|
def test_draw_bpmn_elements(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
_draw_bpmn_elements,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 110)
|
ax.set_xlim(0, 110)
|
||||||
ax.set_ylim(0, 75)
|
ax.set_ylim(0, 75)
|
||||||
@ -168,10 +148,6 @@ class TestBPMN:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_bpmn_legend(self) -> None:
|
def test_draw_bpmn_legend(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
_draw_bpmn_legend,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 110)
|
ax.set_xlim(0, 110)
|
||||||
ax.set_ylim(0, 75)
|
ax.set_ylim(0, 75)
|
||||||
@ -183,17 +159,9 @@ class TestUMLActivity:
|
|||||||
"""Test generate_uml_activity and its sub-helpers."""
|
"""Test generate_uml_activity and its sub-helpers."""
|
||||||
|
|
||||||
def test_generate_uml_activity(self) -> None:
|
def test_generate_uml_activity(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
generate_uml_activity,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_uml_activity()
|
generate_uml_activity()
|
||||||
|
|
||||||
def test_draw_uml_elements(self) -> None:
|
def test_draw_uml_elements(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
_draw_uml_elements,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 100)
|
ax.set_ylim(0, 100)
|
||||||
@ -201,10 +169,6 @@ class TestUMLActivity:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_uml_legend(self) -> None:
|
def test_draw_uml_legend(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_bpmn_uml import (
|
|
||||||
_draw_uml_legend,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 100)
|
ax.set_ylim(0, 100)
|
||||||
@ -219,44 +183,24 @@ class TestEPC:
|
|||||||
"""Test generate_epc and its sub-helpers."""
|
"""Test generate_epc and its sub-helpers."""
|
||||||
|
|
||||||
def test_generate_epc(self) -> None:
|
def test_generate_epc(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
generate_epc,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_epc()
|
generate_epc()
|
||||||
|
|
||||||
def test_draw_epc_event(self) -> None:
|
def test_draw_epc_event(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
_draw_epc_event,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_draw_epc_event(ax, 50, 50, "test event")
|
_draw_epc_event(ax, 50, 50, "test event")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_epc_function(self) -> None:
|
def test_draw_epc_function(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
_draw_epc_function,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_draw_epc_function(ax, 50, 50, "test function")
|
_draw_epc_function(ax, 50, 50, "test function")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_epc_connector(self) -> None:
|
def test_draw_epc_connector(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
_draw_epc_connector,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_draw_epc_connector(ax, 50, 50, "XOR")
|
_draw_epc_connector(ax, 50, 50, "XOR")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_epc_flow(self) -> None:
|
def test_draw_epc_flow(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
_draw_epc_flow,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 120)
|
ax.set_ylim(0, 120)
|
||||||
@ -267,10 +211,6 @@ class TestEPC:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_epc_branches(self) -> None:
|
def test_draw_epc_branches(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
_draw_epc_branches,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 120)
|
ax.set_ylim(0, 120)
|
||||||
@ -278,10 +218,6 @@ class TestEPC:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_epc_legend(self) -> None:
|
def test_draw_epc_legend(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_epc_fc import (
|
|
||||||
_draw_epc_legend,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 120)
|
ax.set_ylim(0, 120)
|
||||||
@ -296,44 +232,24 @@ class TestFlowchart:
|
|||||||
"""Test generate_flowchart and its sub-helpers."""
|
"""Test generate_flowchart and its sub-helpers."""
|
||||||
|
|
||||||
def test_generate_flowchart(self) -> None:
|
def test_generate_flowchart(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
|
||||||
generate_flowchart,
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_flowchart()
|
generate_flowchart()
|
||||||
|
|
||||||
def test_draw_fc_terminal(self) -> None:
|
def test_draw_fc_terminal(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
|
||||||
_draw_fc_terminal,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_draw_fc_terminal(ax, 50, 50, "START")
|
_draw_fc_terminal(ax, 50, 50, "START")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_fc_process_box(self) -> None:
|
def test_draw_fc_process_box(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
|
||||||
_draw_fc_process_box,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_draw_fc_process_box(ax, 50, 50, "Process")
|
_draw_fc_process_box(ax, 50, 50, "Process")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_fc_io_shape(self) -> None:
|
def test_draw_fc_io_shape(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
|
||||||
_draw_fc_io_shape,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
_draw_fc_io_shape(ax, 50, 50, "I/O")
|
_draw_fc_io_shape(ax, 50, 50, "I/O")
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_fc_elements(self) -> None:
|
def test_draw_fc_elements(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
|
||||||
_draw_fc_elements,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 110)
|
ax.set_ylim(0, 110)
|
||||||
@ -341,10 +257,6 @@ class TestFlowchart:
|
|||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def test_draw_fc_legend(self) -> None:
|
def test_draw_fc_legend(self) -> None:
|
||||||
from python_pkg.praca_magisterska_video.generate_images._process_fc import (
|
|
||||||
_draw_fc_legend,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.set_xlim(0, 100)
|
ax.set_xlim(0, 100)
|
||||||
ax.set_ylim(0, 110)
|
ax.set_ylim(0, 110)
|
||||||
|
|||||||
@ -48,7 +48,7 @@ class TestSplitQuestions:
|
|||||||
source_file = FakeFile(source_content)
|
source_file = FakeFile(source_content)
|
||||||
written_files: dict[str, FakeFile] = {}
|
written_files: dict[str, FakeFile] = {}
|
||||||
|
|
||||||
def fake_open(self_path: Path, *args: object, **kwargs: object) -> FakeFile:
|
def fake_open(self_path: Path, *_args: object, **_kwargs: object) -> FakeFile:
|
||||||
path_str = str(self_path)
|
path_str = str(self_path)
|
||||||
if "OBRONA_MAGISTERSKA_ODPOWIEDZI" in path_str:
|
if "OBRONA_MAGISTERSKA_ODPOWIEDZI" in path_str:
|
||||||
return source_file
|
return source_file
|
||||||
@ -59,7 +59,7 @@ class TestSplitQuestions:
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(Path, "open", fake_open),
|
patch.object(Path, "open", fake_open),
|
||||||
patch.object(Path, "mkdir", lambda *a, **kw: None),
|
patch.object(Path, "mkdir", lambda *_a, **_kw: None),
|
||||||
):
|
):
|
||||||
importlib.import_module(mod_name)
|
importlib.import_module(mod_name)
|
||||||
|
|
||||||
|
|||||||
@ -144,7 +144,7 @@ def test_get_file_metadata(sample_file: Path) -> None:
|
|||||||
_get_file_metadata,
|
_get_file_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
num, subject, content = _get_file_metadata(str(sample_file))
|
num, subject, _ = _get_file_metadata(str(sample_file))
|
||||||
assert num == "01"
|
assert num == "01"
|
||||||
assert subject == "Informatyka"
|
assert subject == "Informatyka"
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ def test_get_file_metadata_no_match(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
p = tmp_path / "readme.txt"
|
p = tmp_path / "readme.txt"
|
||||||
p.write_text("No Przedmiot", encoding="utf-8")
|
p.write_text("No Przedmiot", encoding="utf-8")
|
||||||
num, subject, content = _get_file_metadata(str(p))
|
num, subject, _ = _get_file_metadata(str(p))
|
||||||
assert num == "00"
|
assert num == "00"
|
||||||
assert subject == "Ogólne"
|
assert subject == "Ogólne"
|
||||||
|
|
||||||
|
|||||||
@ -207,7 +207,7 @@ def test_body_parts_long_para_truncation() -> None:
|
|||||||
# --- _extract_subsection_cards: empty parts / multiple parts ---
|
# --- _extract_subsection_cards: empty parts / multiple parts ---
|
||||||
|
|
||||||
|
|
||||||
def test_subsection_empty_answer_parts(tmp_path: Path) -> None:
|
def test_subsection_empty_answer_parts() -> None:
|
||||||
"""Subsection where _extract_body_parts returns [] (182->173)."""
|
"""Subsection where _extract_body_parts returns [] (182->173)."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_anki_final import (
|
from python_pkg.praca_magisterska_video.generate_images.generate_anki_final import (
|
||||||
_extract_subsection_cards,
|
_extract_subsection_cards,
|
||||||
@ -403,7 +403,7 @@ def test_main_function(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
call_count = 0
|
call_count = 0
|
||||||
|
|
||||||
def fake_extract(filepath: object) -> list[dict[str, str]]:
|
def fake_extract(_filepath: object) -> list[dict[str, str]]:
|
||||||
nonlocal call_count
|
nonlocal call_count
|
||||||
call_count += 1
|
call_count += 1
|
||||||
if call_count == 1:
|
if call_count == 1:
|
||||||
|
|||||||
@ -79,7 +79,7 @@ def test_get_metadata(sample_file: Path) -> None:
|
|||||||
_get_metadata,
|
_get_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
num, topic, title, main_q, content = _get_metadata(str(sample_file))
|
num, topic, _, main_q, content = _get_metadata(str(sample_file))
|
||||||
assert num == "01"
|
assert num == "01"
|
||||||
assert "test" in topic
|
assert "test" in topic
|
||||||
assert "main concept" in main_q
|
assert "main concept" in main_q
|
||||||
@ -92,7 +92,7 @@ def test_get_metadata_no_match(minimal_file: Path) -> None:
|
|||||||
_get_metadata,
|
_get_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
num, topic, title, main_q, content = _get_metadata(str(minimal_file))
|
num, topic, _, _, _ = _get_metadata(str(minimal_file))
|
||||||
assert num == "00"
|
assert num == "00"
|
||||||
assert topic == "unknown"
|
assert topic == "unknown"
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ def test_extract_main_card(sample_file: Path) -> None:
|
|||||||
_get_metadata,
|
_get_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
num, topic, title, main_q, content = _get_metadata(str(sample_file))
|
num, topic, _, main_q, content = _get_metadata(str(sample_file))
|
||||||
cards = _extract_main_card(content, main_q, "Informatyka", num, topic)
|
cards = _extract_main_card(content, main_q, "Informatyka", num, topic)
|
||||||
assert len(cards) == 1
|
assert len(cards) == 1
|
||||||
assert "main concept" in cards[0]["question"]
|
assert "main concept" in cards[0]["question"]
|
||||||
|
|||||||
@ -115,7 +115,7 @@ def test_main_card_def_outside_length(def_length_file: Path) -> None:
|
|||||||
_get_metadata,
|
_get_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
num, topic, title, main_q, content = _get_metadata(str(def_length_file))
|
num, topic, _, main_q, content = _get_metadata(str(def_length_file))
|
||||||
cards = _extract_main_card(content, main_q, "Informatyka", num, topic)
|
cards = _extract_main_card(content, main_q, "Informatyka", num, topic)
|
||||||
assert isinstance(cards, list)
|
assert isinstance(cards, list)
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ def test_sub_cards_short_body(subsection_file: Path) -> None:
|
|||||||
assert isinstance(cards, list)
|
assert isinstance(cards, list)
|
||||||
|
|
||||||
|
|
||||||
def test_sub_cards_no_answer_text(tmp_path: Path) -> None:
|
def test_sub_cards_no_answer_text() -> None:
|
||||||
"""Subsection where _extract_subsection_answer returns None (line 145)."""
|
"""Subsection where _extract_subsection_answer returns None (line 145)."""
|
||||||
from python_pkg.praca_magisterska_video.generate_images.generate_anki import (
|
from python_pkg.praca_magisterska_video.generate_images.generate_anki import (
|
||||||
_extract_sub_cards,
|
_extract_sub_cards,
|
||||||
@ -221,7 +221,7 @@ def test_main_function(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
call_count = 0
|
call_count = 0
|
||||||
|
|
||||||
def fake_extract(filepath: object) -> list[dict[str, str]]:
|
def fake_extract(_filepath: object) -> list[dict[str, str]]:
|
||||||
nonlocal call_count
|
nonlocal call_count
|
||||||
call_count += 1
|
call_count += 1
|
||||||
if call_count == 1:
|
if call_count == 1:
|
||||||
|
|||||||
@ -255,7 +255,7 @@ def test_main_error_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N
|
|||||||
return real_path(out_file)
|
return real_path(out_file)
|
||||||
return real_path(s)
|
return real_path(s)
|
||||||
|
|
||||||
def failing_process(filepath: str) -> list[dict[str, str]]:
|
def failing_process(_filepath: str) -> list[dict[str, str]]:
|
||||||
msg = "test error"
|
msg = "test error"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|||||||
@ -113,9 +113,11 @@ Some introductory text that is ignored completely.
|
|||||||
- **Gamma**
|
- **Gamma**
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_PLAIN_BODY = """\
|
_PLAIN_BODY = (
|
||||||
This is a plain first paragraph without any structured content and it is long enough to be captured by regex.
|
"This is a plain first paragraph without any"
|
||||||
"""
|
" structured content and it is long enough"
|
||||||
|
" to be captured by regex.\n"
|
||||||
|
)
|
||||||
|
|
||||||
_PARA_ONLY_MD = """\
|
_PARA_ONLY_MD = """\
|
||||||
# Pytanie 03: Para Only
|
# Pytanie 03: Para Only
|
||||||
@ -251,7 +253,7 @@ def test_read_file_metadata_matching(sample_file: Path) -> None:
|
|||||||
_read_file_metadata,
|
_read_file_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
content, base_tags, main_question = _read_file_metadata(sample_file)
|
_, base_tags, main_question = _read_file_metadata(sample_file)
|
||||||
assert "pyt01" in base_tags
|
assert "pyt01" in base_tags
|
||||||
assert "Informatyka" in base_tags
|
assert "Informatyka" in base_tags
|
||||||
assert main_question is not None
|
assert main_question is not None
|
||||||
@ -266,7 +268,7 @@ def test_read_file_metadata_no_match(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
p = tmp_path / "readme.txt"
|
p = tmp_path / "readme.txt"
|
||||||
p.write_text(_MINIMAL_MD, encoding="utf-8")
|
p.write_text(_MINIMAL_MD, encoding="utf-8")
|
||||||
content, base_tags, main_question = _read_file_metadata(p)
|
_, base_tags, main_question = _read_file_metadata(p)
|
||||||
assert "pyt00" in base_tags
|
assert "pyt00" in base_tags
|
||||||
assert "Og\u00f3lne" in base_tags
|
assert "Og\u00f3lne" in base_tags
|
||||||
assert main_question is None
|
assert main_question is None
|
||||||
|
|||||||
@ -224,7 +224,7 @@ def test_main_error_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N
|
|||||||
return real_path(out_file)
|
return real_path(out_file)
|
||||||
return real_path(s)
|
return real_path(s)
|
||||||
|
|
||||||
def failing_extract(filepath: object) -> list[dict[str, str]]:
|
def failing_extract(_filepath: object) -> list[dict[str, str]]:
|
||||||
msg = "test error"
|
msg = "test error"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
||||||
"""VideoClip spy capturing make_frame closures."""
|
"""VideoClip spy capturing make_frame closures."""
|
||||||
captured: list[tuple[object, float]] = []
|
captured: list[tuple[object, float]] = []
|
||||||
|
|
||||||
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
|
def spy(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
if callable(make_frame):
|
if callable(make_frame):
|
||||||
captured.append((make_frame, duration or 1.0))
|
captured.append((make_frame, duration or 1.0))
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
||||||
"""VideoClip spy capturing make_frame closures."""
|
"""VideoClip spy capturing make_frame closures."""
|
||||||
captured: list[tuple[object, float]] = []
|
captured: list[tuple[object, float]] = []
|
||||||
|
|
||||||
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
|
def spy(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
if callable(make_frame):
|
if callable(make_frame):
|
||||||
captured.append((make_frame, duration or 1.0))
|
captured.append((make_frame, duration or 1.0))
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
||||||
"""VideoClip spy capturing make_frame closures."""
|
"""VideoClip spy capturing make_frame closures."""
|
||||||
captured: list[tuple[object, float]] = []
|
captured: list[tuple[object, float]] = []
|
||||||
|
|
||||||
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
|
def spy(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
if callable(make_frame):
|
if callable(make_frame):
|
||||||
captured.append((make_frame, duration or 1.0))
|
captured.append((make_frame, duration or 1.0))
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
||||||
"""VideoClip spy capturing make_frame closures."""
|
"""VideoClip spy capturing make_frame closures."""
|
||||||
captured: list[tuple[object, float]] = []
|
captured: list[tuple[object, float]] = []
|
||||||
|
|
||||||
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
|
def spy(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
if callable(make_frame):
|
if callable(make_frame):
|
||||||
captured.append((make_frame, duration or 1.0))
|
captured.append((make_frame, duration or 1.0))
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
||||||
"""VideoClip spy capturing make_frame closures."""
|
"""VideoClip spy capturing make_frame closures."""
|
||||||
captured: list[tuple[object, float]] = []
|
captured: list[tuple[object, float]] = []
|
||||||
|
|
||||||
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
|
def spy(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
if callable(make_frame):
|
if callable(make_frame):
|
||||||
captured.append((make_frame, duration or 1.0))
|
captured.append((make_frame, duration or 1.0))
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
|
|||||||
@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
def _spy_vc() -> tuple[object, list[tuple[object, float]]]:
|
||||||
"""VideoClip spy capturing make_frame closures."""
|
"""VideoClip spy capturing make_frame closures."""
|
||||||
captured: list[tuple[object, float]] = []
|
captured: list[tuple[object, float]] = []
|
||||||
|
|
||||||
def spy(make_frame=None, duration=None, **_kw: object) -> MagicMock:
|
def spy(
|
||||||
|
make_frame: Callable[[float], np.ndarray] | None = None,
|
||||||
|
duration: float | None = None,
|
||||||
|
**_kw: object,
|
||||||
|
) -> MagicMock:
|
||||||
if callable(make_frame):
|
if callable(make_frame):
|
||||||
captured.append((make_frame, duration or 1.0))
|
captured.append((make_frame, duration or 1.0))
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
|
|||||||
@ -60,7 +60,7 @@ def test_make_frame_closure_returns_ndarray() -> None:
|
|||||||
|
|
||||||
captured: list[object] = []
|
captured: list[object] = []
|
||||||
|
|
||||||
def capturing_video_clip(make_frame: object = None, **kw: object) -> MagicMock:
|
def capturing_video_clip(make_frame: object = None, **_kw: object) -> MagicMock:
|
||||||
captured.append(make_frame)
|
captured.append(make_frame)
|
||||||
clip = MagicMock()
|
clip = MagicMock()
|
||||||
clip.with_fps.return_value = clip
|
clip.with_fps.return_value = clip
|
||||||
|
|||||||
@ -18,6 +18,7 @@ Usage
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
from collections import Counter
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
@ -70,8 +71,6 @@ def cmd_debug(args: argparse.Namespace) -> None:
|
|||||||
data = parse_image(args.image, threshold=args.threshold)
|
data = parse_image(args.image, threshold=args.threshold)
|
||||||
out = args.output or args.image.rsplit(".", 1)[0] + "_debug.png"
|
out = args.output or args.image.rsplit(".", 1)[0] + "_debug.png"
|
||||||
draw_debug(args.image, data, out)
|
draw_debug(args.image, data, out)
|
||||||
from collections import Counter
|
|
||||||
|
|
||||||
counts = Counter(sq["type"] for sq in data["squares"])
|
counts = Counter(sq["type"] for sq in data["squares"])
|
||||||
for _t, _n in counts.most_common():
|
for _t, _n in counts.most_common():
|
||||||
pass
|
pass
|
||||||
|
|||||||
9
python_pkg/puzzle_solver/tests/conftest.py
Normal file
9
python_pkg/puzzle_solver/tests/conftest.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"""Mock cv2/numpy if not installed before puzzle_solver tests."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
sys.modules.setdefault("cv2", MagicMock())
|
||||||
|
sys.modules.setdefault("numpy", MagicMock())
|
||||||
@ -3,16 +3,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, mock_open, patch
|
from unittest.mock import MagicMock, mock_open, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# Ensure cv2 and numpy are available as mocks before importing main
|
|
||||||
sys.modules.setdefault("cv2", MagicMock())
|
|
||||||
sys.modules.setdefault("numpy", MagicMock())
|
|
||||||
|
|
||||||
from python_pkg.puzzle_solver.main import (
|
from python_pkg.puzzle_solver.main import (
|
||||||
cmd_debug,
|
cmd_debug,
|
||||||
cmd_parse,
|
cmd_parse,
|
||||||
|
|||||||
@ -2,17 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from unittest.mock import MagicMock, mock_open, patch
|
from unittest.mock import MagicMock, mock_open, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# Install mock modules before any parse_image imports
|
|
||||||
_cv2_mock = MagicMock()
|
|
||||||
_np_mock = MagicMock()
|
|
||||||
sys.modules.setdefault("cv2", _cv2_mock)
|
|
||||||
sys.modules.setdefault("numpy", _np_mock)
|
|
||||||
|
|
||||||
from python_pkg.puzzle_solver.parse_image import (
|
from python_pkg.puzzle_solver.parse_image import (
|
||||||
_classify_by_fill,
|
_classify_by_fill,
|
||||||
_classify_interior_feature,
|
_classify_interior_feature,
|
||||||
|
|||||||
@ -2,16 +2,11 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
# Install mock modules before any parse_image imports
|
|
||||||
sys.modules.setdefault("cv2", MagicMock())
|
|
||||||
sys.modules.setdefault("numpy", MagicMock())
|
|
||||||
|
|
||||||
from python_pkg.puzzle_solver.parse_image import (
|
from python_pkg.puzzle_solver.parse_image import (
|
||||||
_assign_teleporter_and_kl_groups,
|
_assign_teleporter_and_kl_groups,
|
||||||
_build_output,
|
_build_output,
|
||||||
|
|||||||
@ -2,14 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
# Install mock modules before any parse_image imports
|
|
||||||
sys.modules.setdefault("cv2", MagicMock())
|
|
||||||
sys.modules.setdefault("numpy", MagicMock())
|
|
||||||
|
|
||||||
from python_pkg.puzzle_solver.parse_image import (
|
from python_pkg.puzzle_solver.parse_image import (
|
||||||
_assign_teleporter_and_kl_groups,
|
_assign_teleporter_and_kl_groups,
|
||||||
draw_debug,
|
draw_debug,
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from python_pkg.repo_explorer._discovery import (
|
from python_pkg.repo_explorer._discovery import (
|
||||||
@ -173,7 +172,7 @@ class TestGetDescription:
|
|||||||
readme.exists.return_value = True
|
readme.exists.return_value = True
|
||||||
readme.read_text.return_value = "# My Project\nDetails here"
|
readme.read_text.return_value = "# My Project\nDetails here"
|
||||||
|
|
||||||
def truediv(_self: Any, name: str) -> MagicMock:
|
def truediv(_self: object, name: str) -> MagicMock:
|
||||||
if name == "README.md":
|
if name == "README.md":
|
||||||
return readme
|
return readme
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
@ -187,7 +186,7 @@ class TestGetDescription:
|
|||||||
def test_readme_txt(self) -> None:
|
def test_readme_txt(self) -> None:
|
||||||
mock_path = MagicMock(spec=Path)
|
mock_path = MagicMock(spec=Path)
|
||||||
|
|
||||||
def truediv(_self: Any, name: str) -> MagicMock:
|
def truediv(_self: object, name: str) -> MagicMock:
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
if name == "README.txt":
|
if name == "README.txt":
|
||||||
m.exists.return_value = True
|
m.exists.return_value = True
|
||||||
@ -203,7 +202,7 @@ class TestGetDescription:
|
|||||||
def test_readme_lower(self) -> None:
|
def test_readme_lower(self) -> None:
|
||||||
mock_path = MagicMock(spec=Path)
|
mock_path = MagicMock(spec=Path)
|
||||||
|
|
||||||
def truediv(_self: Any, name: str) -> MagicMock:
|
def truediv(_self: object, name: str) -> MagicMock:
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
if name == "readme.md":
|
if name == "readme.md":
|
||||||
m.exists.return_value = True
|
m.exists.return_value = True
|
||||||
@ -220,7 +219,7 @@ class TestGetDescription:
|
|||||||
"""README exists but all lines strip to empty."""
|
"""README exists but all lines strip to empty."""
|
||||||
mock_path = MagicMock(spec=Path)
|
mock_path = MagicMock(spec=Path)
|
||||||
|
|
||||||
def truediv(_self: Any, name: str) -> MagicMock:
|
def truediv(_self: object, name: str) -> MagicMock:
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
if name == "README.md":
|
if name == "README.md":
|
||||||
m.exists.return_value = True
|
m.exists.return_value = True
|
||||||
@ -243,7 +242,7 @@ class TestGetDescription:
|
|||||||
run_sh = MagicMock(spec=Path)
|
run_sh = MagicMock(spec=Path)
|
||||||
run_sh.exists.return_value = True
|
run_sh.exists.return_value = True
|
||||||
|
|
||||||
def truediv(_self: Any, name: str) -> MagicMock:
|
def truediv(_self: object, name: str) -> MagicMock:
|
||||||
if name == "run.sh":
|
if name == "run.sh":
|
||||||
return run_sh
|
return run_sh
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
@ -261,7 +260,7 @@ class TestGetDescription:
|
|||||||
run_sh = MagicMock(spec=Path)
|
run_sh = MagicMock(spec=Path)
|
||||||
run_sh.exists.return_value = True
|
run_sh.exists.return_value = True
|
||||||
|
|
||||||
def truediv(_self: Any, name: str) -> MagicMock:
|
def truediv(_self: object, name: str) -> MagicMock:
|
||||||
if name == "run.sh":
|
if name == "run.sh":
|
||||||
return run_sh
|
return run_sh
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
@ -275,7 +274,7 @@ class TestGetDescription:
|
|||||||
def test_no_readme_no_run_sh(self) -> None:
|
def test_no_readme_no_run_sh(self) -> None:
|
||||||
mock_path = MagicMock(spec=Path)
|
mock_path = MagicMock(spec=Path)
|
||||||
|
|
||||||
def truediv(_self: Any, _name: str) -> MagicMock:
|
def truediv(_self: object, _name: str) -> MagicMock:
|
||||||
m = MagicMock(spec=Path)
|
m = MagicMock(spec=Path)
|
||||||
m.exists.return_value = False
|
m.exists.return_value = False
|
||||||
return m
|
return m
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from unittest.mock import MagicMock, patch
|
|||||||
from python_pkg.repo_explorer._execution import ExecutionMixin
|
from python_pkg.repo_explorer._execution import ExecutionMixin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
# ── Protocol stub coverage ───────────────────────────────────────────
|
# ── Protocol stub coverage ───────────────────────────────────────────
|
||||||
@ -45,7 +46,7 @@ class StubExecution(ExecutionMixin):
|
|||||||
self._path: Any = None
|
self._path: Any = None
|
||||||
self._after_calls: list[tuple[Any, ...]] = []
|
self._after_calls: list[tuple[Any, ...]] = []
|
||||||
|
|
||||||
def _selected_path(self) -> Any:
|
def _selected_path(self) -> Path | None:
|
||||||
return self._path
|
return self._path
|
||||||
|
|
||||||
def after(self, ms: int, *args: object) -> str:
|
def after(self, ms: int, *args: object) -> str:
|
||||||
@ -338,7 +339,11 @@ class TestReadPty:
|
|||||||
|
|
||||||
read_calls = [0]
|
read_calls = [0]
|
||||||
|
|
||||||
def fake_select(rlist: list[int], *_a: Any, **_kw: Any) -> Any:
|
def fake_select(
|
||||||
|
_rlist: list[int],
|
||||||
|
*_a: object,
|
||||||
|
**_kw: object,
|
||||||
|
) -> tuple[list[int], list[object], list[object]]:
|
||||||
read_calls[0] += 1
|
read_calls[0] += 1
|
||||||
if read_calls[0] == 1:
|
if read_calls[0] == 1:
|
||||||
# First call: return data (no newline → stays in buf)
|
# First call: return data (no newline → stays in buf)
|
||||||
@ -393,7 +398,11 @@ class TestReadPty:
|
|||||||
|
|
||||||
call_count = [0]
|
call_count = [0]
|
||||||
|
|
||||||
def fake_select(rlist: list[int], *_a: Any, **_kw: Any) -> Any:
|
def fake_select(
|
||||||
|
_rlist: list[int],
|
||||||
|
*_a: object,
|
||||||
|
**_kw: object,
|
||||||
|
) -> tuple[list[int], list[object], list[object]]:
|
||||||
call_count[0] += 1
|
call_count[0] += 1
|
||||||
if call_count[0] == 1:
|
if call_count[0] == 1:
|
||||||
return ([10], [], [])
|
return ([10], [], [])
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from unittest.mock import MagicMock
|
|||||||
from python_pkg.repo_explorer._execution import ExecutionMixin
|
from python_pkg.repo_explorer._execution import ExecutionMixin
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ class StubExecution(ExecutionMixin):
|
|||||||
self._path: Any = None
|
self._path: Any = None
|
||||||
self._after_calls: list[tuple[Any, ...]] = []
|
self._after_calls: list[tuple[Any, ...]] = []
|
||||||
|
|
||||||
def _selected_path(self) -> Any:
|
def _selected_path(self) -> Path | None:
|
||||||
return self._path
|
return self._path
|
||||||
|
|
||||||
def after(self, ms: int, *args: object) -> str:
|
def after(self, ms: int, *args: object) -> str:
|
||||||
|
|||||||
@ -4,13 +4,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
# ── Helper to create a RepoExplorer without a real display ───────────
|
# ── Helper to create a RepoExplorer without a real display ───────────
|
||||||
|
|
||||||
|
|
||||||
def _make_explorer(**overrides: Any) -> Any:
|
def _make_explorer(**overrides: object) -> object:
|
||||||
"""Build a RepoExplorer instance without a real Tk display.
|
"""Build a RepoExplorer instance without a real Tk display.
|
||||||
|
|
||||||
Mocks tk.Tk.__init__ and all GUI construction so no X server is needed.
|
Mocks tk.Tk.__init__ and all GUI construction so no X server is needed.
|
||||||
@ -108,83 +107,73 @@ class TestBuildStyle:
|
|||||||
|
|
||||||
|
|
||||||
class TestBuildUI:
|
class TestBuildUI:
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar")
|
def test_build_ui_with_terminal(self) -> None:
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Treeview")
|
with (
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.font.Font")
|
patch(
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Button")
|
"python_pkg.repo_explorer.repo_explorer.tk.StringVar"
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry")
|
) as mock_stringvar,
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator")
|
patch("python_pkg.repo_explorer.repo_explorer.tk.Text") as mock_text,
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Label")
|
patch(
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame")
|
"python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow"
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow")
|
) as mock_paned,
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.tk.Text")
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame"),
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.tk.StringVar")
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Label"),
|
||||||
def test_build_ui_with_terminal(
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator"),
|
||||||
self,
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry"),
|
||||||
mock_stringvar: MagicMock,
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Button"),
|
||||||
mock_text: MagicMock,
|
patch("python_pkg.repo_explorer.repo_explorer.font.Font"),
|
||||||
mock_paned: MagicMock,
|
patch(
|
||||||
mock_frame: MagicMock,
|
"python_pkg.repo_explorer.repo_explorer.ttk.Treeview"
|
||||||
mock_label: MagicMock,
|
) as mock_treeview,
|
||||||
mock_sep: MagicMock,
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar"),
|
||||||
mock_entry: MagicMock,
|
):
|
||||||
mock_button: MagicMock,
|
app = _make_explorer()
|
||||||
mock_font: MagicMock,
|
mock_sv = MagicMock()
|
||||||
mock_treeview: MagicMock,
|
mock_stringvar.return_value = mock_sv
|
||||||
mock_scrollbar: MagicMock,
|
paned = MagicMock()
|
||||||
) -> None:
|
mock_paned.return_value = paned
|
||||||
app = _make_explorer()
|
|
||||||
mock_sv = MagicMock()
|
|
||||||
mock_stringvar.return_value = mock_sv
|
|
||||||
paned = MagicMock()
|
|
||||||
mock_paned.return_value = paned
|
|
||||||
|
|
||||||
tree = MagicMock()
|
tree = MagicMock()
|
||||||
mock_treeview.return_value = tree
|
mock_treeview.return_value = tree
|
||||||
text = MagicMock()
|
text = MagicMock()
|
||||||
mock_text.return_value = text
|
mock_text.return_value = text
|
||||||
|
|
||||||
app.pack = MagicMock()
|
app.pack = MagicMock()
|
||||||
app._build_ui()
|
app._build_ui()
|
||||||
|
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar")
|
def test_build_ui_no_terminal(self) -> None:
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Treeview")
|
with (
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.font.Font")
|
patch(
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Button")
|
"python_pkg.repo_explorer.repo_explorer.tk.StringVar"
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry")
|
) as mock_stringvar,
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator")
|
patch("python_pkg.repo_explorer.repo_explorer.tk.Text") as mock_text,
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Label")
|
patch(
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame")
|
"python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow"
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.ttk.PanedWindow")
|
) as mock_paned,
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.tk.Text")
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Frame"),
|
||||||
@patch("python_pkg.repo_explorer.repo_explorer.tk.StringVar")
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Label"),
|
||||||
def test_build_ui_no_terminal(
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Separator"),
|
||||||
self,
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Entry"),
|
||||||
mock_stringvar: MagicMock,
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Button"),
|
||||||
mock_text: MagicMock,
|
patch("python_pkg.repo_explorer.repo_explorer.font.Font"),
|
||||||
mock_paned: MagicMock,
|
patch(
|
||||||
mock_frame: MagicMock,
|
"python_pkg.repo_explorer.repo_explorer.ttk.Treeview"
|
||||||
mock_label: MagicMock,
|
) as mock_treeview,
|
||||||
mock_sep: MagicMock,
|
patch("python_pkg.repo_explorer.repo_explorer.ttk.Scrollbar"),
|
||||||
mock_entry: MagicMock,
|
):
|
||||||
mock_button: MagicMock,
|
app = _make_explorer(terminal_args=[])
|
||||||
mock_font: MagicMock,
|
mock_sv = MagicMock()
|
||||||
mock_treeview: MagicMock,
|
mock_stringvar.return_value = mock_sv
|
||||||
mock_scrollbar: MagicMock,
|
paned = MagicMock()
|
||||||
) -> None:
|
mock_paned.return_value = paned
|
||||||
app = _make_explorer(terminal_args=[])
|
|
||||||
mock_sv = MagicMock()
|
|
||||||
mock_stringvar.return_value = mock_sv
|
|
||||||
paned = MagicMock()
|
|
||||||
mock_paned.return_value = paned
|
|
||||||
|
|
||||||
tree = MagicMock()
|
tree = MagicMock()
|
||||||
mock_treeview.return_value = tree
|
mock_treeview.return_value = tree
|
||||||
text = MagicMock()
|
text = MagicMock()
|
||||||
mock_text.return_value = text
|
mock_text.return_value = text
|
||||||
|
|
||||||
app.pack = MagicMock()
|
app.pack = MagicMock()
|
||||||
app._build_ui()
|
app._build_ui()
|
||||||
|
|
||||||
|
|
||||||
# ── _load_projects ───────────────────────────────────────────────────
|
# ── _load_projects ───────────────────────────────────────────────────
|
||||||
|
|||||||
@ -196,7 +196,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||||||
parser = _build_parser()
|
parser = _build_parser()
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
if not _trans._check_argos():
|
if not _trans.check_argos():
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
"Error: argostranslate is not installed.\n"
|
"Error: argostranslate is not installed.\n"
|
||||||
"Install it with: pip install argostranslate\n",
|
"Install it with: pip install argostranslate\n",
|
||||||
|
|||||||
@ -67,7 +67,7 @@ class ArgosAvailableMock:
|
|||||||
translator, "_ensure_language_pair", lambda _f, _t: None
|
translator, "_ensure_language_pair", lambda _f, _t: None
|
||||||
)
|
)
|
||||||
self._check_argos_patcher = patch.object(
|
self._check_argos_patcher = patch.object(
|
||||||
translator, "_check_argos", return_value=True
|
translator, "check_argos", return_value=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self._sys_modules_patcher.start()
|
self._sys_modules_patcher.start()
|
||||||
|
|||||||
@ -15,9 +15,9 @@ from python_pkg.word_frequency import translator
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def _mock_argos_unavailable() -> Generator[None, None, None]:
|
def mock_argos_unavailable() -> Generator[None, None, None]:
|
||||||
"""Mock argostranslate being unavailable (for legacy tests)."""
|
"""Mock argostranslate being unavailable (for legacy tests)."""
|
||||||
with patch.object(translator, "_check_argos", return_value=False):
|
with patch.object(translator, "check_argos", return_value=False):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -28,9 +28,11 @@ class TestGetCacheDir:
|
|||||||
"""Tests for get_cache_dir."""
|
"""Tests for get_cache_dir."""
|
||||||
|
|
||||||
def test_returns_default(self, tmp_path: Path) -> None:
|
def test_returns_default(self, tmp_path: Path) -> None:
|
||||||
with patch("python_pkg.word_frequency.cache.DEFAULT_CACHE_DIR", tmp_path):
|
with (
|
||||||
with patch.dict("os.environ", {}, clear=False):
|
patch("python_pkg.word_frequency.cache.DEFAULT_CACHE_DIR", tmp_path),
|
||||||
d = get_cache_dir()
|
patch.dict("os.environ", {}, clear=False),
|
||||||
|
):
|
||||||
|
d = get_cache_dir()
|
||||||
assert d == tmp_path
|
assert d == tmp_path
|
||||||
|
|
||||||
def test_respects_env_var(self, tmp_path: Path) -> None:
|
def test_respects_env_var(self, tmp_path: Path) -> None:
|
||||||
|
|||||||
@ -142,14 +142,14 @@ class TestAnkiDeckCache:
|
|||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
assert cache.get(dk) is None
|
assert cache.get(dk) is None
|
||||||
|
|
||||||
def test_get_hash_mismatch(self, tmp_path: Path) -> None:
|
def test_get_hash_mismatch(self, tmp_path: Path) -> None:
|
||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
cache.set(dk, "content", "hello", 1, 1)
|
cache.set(dk, "content", "hello", 1, 1)
|
||||||
# Modify file to change hash
|
# Modify file to change hash
|
||||||
fp.write_text("changed content", encoding="utf-8")
|
fp.write_text("changed content", encoding="utf-8")
|
||||||
@ -160,7 +160,7 @@ class TestAnkiDeckCache:
|
|||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
cache.set(dk, "content", "hello", 1, 1)
|
cache.set(dk, "content", "hello", 1, 1)
|
||||||
# Tamper with stored hash in metadata
|
# Tamper with stored hash in metadata
|
||||||
m = cache._load_metadata()
|
m = cache._load_metadata()
|
||||||
@ -174,7 +174,7 @@ class TestAnkiDeckCache:
|
|||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
cache.set(dk, "content", "hello", 1, 1)
|
cache.set(dk, "content", "hello", 1, 1)
|
||||||
# Remove all .txt files in cache dir
|
# Remove all .txt files in cache dir
|
||||||
for f in cache.cache_dir.glob("*.txt"):
|
for f in cache.cache_dir.glob("*.txt"):
|
||||||
@ -185,7 +185,7 @@ class TestAnkiDeckCache:
|
|||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
cache.set(dk, "content", "hello", 1, 1)
|
cache.set(dk, "content", "hello", 1, 1)
|
||||||
# Mock read_text to raise OSError
|
# Mock read_text to raise OSError
|
||||||
with patch("pathlib.Path.read_text", side_effect=OSError("read error")):
|
with patch("pathlib.Path.read_text", side_effect=OSError("read error")):
|
||||||
@ -212,7 +212,7 @@ class TestAnkiDeckCache:
|
|||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
cache.set(dk, "content", "hello", 1, 1)
|
cache.set(dk, "content", "hello", 1, 1)
|
||||||
cache.clear()
|
cache.clear()
|
||||||
assert cache.get(dk) is None
|
assert cache.get(dk) is None
|
||||||
@ -226,7 +226,7 @@ class TestAnkiDeckCache:
|
|||||||
cache = AnkiDeckCache(cache_dir=tmp_path)
|
cache = AnkiDeckCache(cache_dir=tmp_path)
|
||||||
fp = tmp_path / "text.txt"
|
fp = tmp_path / "text.txt"
|
||||||
fp.write_text("hello", encoding="utf-8")
|
fp.write_text("hello", encoding="utf-8")
|
||||||
dk = AnkiDeckKey(fp, 10, "es", False, True)
|
dk = AnkiDeckKey(fp, 10, "es", include_context=False, all_vocab=True)
|
||||||
cache.set(dk, "content", "hello", 1, 1)
|
cache.set(dk, "content", "hello", 1, 1)
|
||||||
stats = cache.stats()
|
stats = cache.stats()
|
||||||
assert stats["total_entries"] == 1
|
assert stats["total_entries"] == 1
|
||||||
|
|||||||
@ -118,19 +118,19 @@ class TestCaching:
|
|||||||
mock.return_value.set.assert_called_once()
|
mock.return_value.set.assert_called_once()
|
||||||
|
|
||||||
def test_get_cached_deck_force(self) -> None:
|
def test_get_cached_deck_force(self) -> None:
|
||||||
key = AnkiDeckKey(Path("x"), 10, "es", False, True)
|
key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True)
|
||||||
result = get_cached_deck(key, force=True)
|
result = get_cached_deck(key, force=True)
|
||||||
assert result is None
|
assert result is None
|
||||||
|
|
||||||
def test_get_cached_deck_delegates(self) -> None:
|
def test_get_cached_deck_delegates(self) -> None:
|
||||||
key = AnkiDeckKey(Path("x"), 10, "es", False, True)
|
key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True)
|
||||||
with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock:
|
with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock:
|
||||||
mock.return_value.get.return_value = ("c", "e", 2, 5)
|
mock.return_value.get.return_value = ("c", "e", 2, 5)
|
||||||
result = get_cached_deck(key)
|
result = get_cached_deck(key)
|
||||||
assert result == ("c", "e", 2, 5)
|
assert result == ("c", "e", 2, 5)
|
||||||
|
|
||||||
def test_cache_deck_delegates(self) -> None:
|
def test_cache_deck_delegates(self) -> None:
|
||||||
key = AnkiDeckKey(Path("x"), 10, "es", False, True)
|
key = AnkiDeckKey(Path("x"), 10, "es", include_context=False, all_vocab=True)
|
||||||
with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock:
|
with patch("python_pkg.word_frequency._generation.get_anki_deck_cache") as mock:
|
||||||
cache_deck(key, "content", "excerpt", 2, 5)
|
cache_deck(key, "content", "excerpt", 2, 5)
|
||||||
mock.return_value.set.assert_called_once()
|
mock.return_value.set.assert_called_once()
|
||||||
@ -215,7 +215,7 @@ VOCAB_DUMP_END
|
|||||||
"python_pkg.word_frequency._generation.get_anki_deck_cache"
|
"python_pkg.word_frequency._generation.get_anki_deck_cache"
|
||||||
) as mock_cache,
|
) as mock_cache,
|
||||||
):
|
):
|
||||||
content, excerpt, num_words, max_rank = generate_flashcards(
|
content, excerpt, _, _ = generate_flashcards(
|
||||||
fp,
|
fp,
|
||||||
5,
|
5,
|
||||||
FlashcardOptions(source_lang="en"),
|
FlashcardOptions(source_lang="en"),
|
||||||
@ -331,7 +331,7 @@ VOCAB_DUMP_END
|
|||||||
),
|
),
|
||||||
patch("python_pkg.word_frequency._generation.get_anki_deck_cache"),
|
patch("python_pkg.word_frequency._generation.get_anki_deck_cache"),
|
||||||
):
|
):
|
||||||
content, excerpt, num_words, max_rank = generate_flashcards(
|
content, _, _, _ = generate_flashcards(
|
||||||
fp, 5, FlashcardOptions(source_lang=None, no_translate=True)
|
fp, 5, FlashcardOptions(source_lang=None, no_translate=True)
|
||||||
)
|
)
|
||||||
assert content == "deck"
|
assert content == "deck"
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from python_pkg.word_frequency._learning_batch import (
|
from python_pkg.word_frequency._learning_batch import (
|
||||||
@ -106,8 +105,8 @@ class TestGenerateBatchSectionWithTranslation:
|
|||||||
|
|
||||||
def fake_batch(
|
def fake_batch(
|
||||||
words: list[str],
|
words: list[str],
|
||||||
from_lang: Any,
|
_from_lang: str | None,
|
||||||
to_lang: Any,
|
_to_lang: str | None,
|
||||||
) -> list[TranslationResult]:
|
) -> list[TranslationResult]:
|
||||||
return [
|
return [
|
||||||
TranslationResult(
|
TranslationResult(
|
||||||
|
|||||||
@ -29,7 +29,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def _mock_translation() -> Generator[MagicMock, None, None]:
|
def mock_translation() -> Generator[MagicMock, None, None]:
|
||||||
"""Mock translation to avoid requiring argostranslate."""
|
"""Mock translation to avoid requiring argostranslate."""
|
||||||
|
|
||||||
def fake_batch_translate(
|
def fake_batch_translate(
|
||||||
@ -262,7 +262,7 @@ class TestMain:
|
|||||||
"""Tests for main CLI function."""
|
"""Tests for main CLI function."""
|
||||||
|
|
||||||
def test_basic_text_input(
|
def test_basic_text_input(
|
||||||
self, caplog: pytest.LogCaptureFixture, _mock_translation: None
|
self, caplog: pytest.LogCaptureFixture, mock_translation: None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test with text input."""
|
"""Test with text input."""
|
||||||
with caplog.at_level(logging.INFO):
|
with caplog.at_level(logging.INFO):
|
||||||
@ -280,7 +280,7 @@ class TestMain:
|
|||||||
assert "LANGUAGE LEARNING LESSON" in caplog.text
|
assert "LANGUAGE LEARNING LESSON" in caplog.text
|
||||||
|
|
||||||
def test_file_input(
|
def test_file_input(
|
||||||
self, tmp_path: Path, caplog: pytest.LogCaptureFixture, _mock_translation: None
|
self, tmp_path: Path, caplog: pytest.LogCaptureFixture, mock_translation: None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test with file input."""
|
"""Test with file input."""
|
||||||
test_file = tmp_path / "test.txt"
|
test_file = tmp_path / "test.txt"
|
||||||
@ -300,7 +300,7 @@ class TestMain:
|
|||||||
assert exit_code == 0
|
assert exit_code == 0
|
||||||
assert "hello" in caplog.text.lower()
|
assert "hello" in caplog.text.lower()
|
||||||
|
|
||||||
def test_output_to_file(self, tmp_path: Path, _mock_translation: None) -> None:
|
def test_output_to_file(self, tmp_path: Path, mock_translation: None) -> None:
|
||||||
"""Test outputting to file."""
|
"""Test outputting to file."""
|
||||||
output_file = tmp_path / "lesson.txt"
|
output_file = tmp_path / "lesson.txt"
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ class TestMain:
|
|||||||
content = output_file.read_text(encoding="utf-8")
|
content = output_file.read_text(encoding="utf-8")
|
||||||
assert "LANGUAGE LEARNING LESSON" in content
|
assert "LANGUAGE LEARNING LESSON" in content
|
||||||
|
|
||||||
def test_custom_stopwords(self, tmp_path: Path, _mock_translation: None) -> None:
|
def test_custom_stopwords(self, tmp_path: Path, mock_translation: None) -> None:
|
||||||
"""Test with custom stopwords file."""
|
"""Test with custom stopwords file."""
|
||||||
stopwords_file = tmp_path / "stop.txt"
|
stopwords_file = tmp_path / "stop.txt"
|
||||||
stopwords_file.write_text("hello\n", encoding="utf-8")
|
stopwords_file.write_text("hello\n", encoding="utf-8")
|
||||||
@ -340,7 +340,7 @@ class TestMain:
|
|||||||
# "hello" should be filtered by custom stopwords
|
# "hello" should be filtered by custom stopwords
|
||||||
|
|
||||||
def test_multiple_batches_option(
|
def test_multiple_batches_option(
|
||||||
self, caplog: pytest.LogCaptureFixture, _mock_translation: None
|
self, caplog: pytest.LogCaptureFixture, mock_translation: None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test --batches option."""
|
"""Test --batches option."""
|
||||||
text = " ".join(f"word{i}" * (50 - i) for i in range(30))
|
text = " ".join(f"word{i}" * (50 - i) for i in range(30))
|
||||||
@ -385,7 +385,7 @@ class TestMain:
|
|||||||
assert exit_code == 1
|
assert exit_code == 1
|
||||||
|
|
||||||
def test_output_to_file_branch(
|
def test_output_to_file_branch(
|
||||||
self, tmp_path: Path, _mock_translation: None
|
self, tmp_path: Path, mock_translation: None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test --output to verify the file writing path."""
|
"""Test --output to verify the file writing path."""
|
||||||
out = tmp_path / "out.txt"
|
out = tmp_path / "out.txt"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from python_pkg.word_frequency._learning_constants import LessonConfig
|
from python_pkg.word_frequency._learning_constants import LessonConfig
|
||||||
@ -19,8 +18,8 @@ class TestDoTranslateBranch:
|
|||||||
|
|
||||||
def fake_batch(
|
def fake_batch(
|
||||||
words: list[str],
|
words: list[str],
|
||||||
from_lang: Any,
|
_from_lang: str | None,
|
||||||
to_lang: Any,
|
_to_lang: str | None,
|
||||||
) -> list[TranslationResult]:
|
) -> list[TranslationResult]:
|
||||||
return [
|
return [
|
||||||
TranslationResult(
|
TranslationResult(
|
||||||
@ -56,8 +55,8 @@ class TestDoTranslateBranch:
|
|||||||
|
|
||||||
def fake_batch(
|
def fake_batch(
|
||||||
words: list[str],
|
words: list[str],
|
||||||
from_lang: Any,
|
from_lang: str | None,
|
||||||
to_lang: Any,
|
to_lang: str | None,
|
||||||
) -> list[TranslationResult]:
|
) -> list[TranslationResult]:
|
||||||
return [
|
return [
|
||||||
TranslationResult(
|
TranslationResult(
|
||||||
|
|||||||
@ -109,7 +109,7 @@ VOCAB_DUMP_END
|
|||||||
Excerpt:
|
Excerpt:
|
||||||
"hello world foo"
|
"hello world foo"
|
||||||
"""
|
"""
|
||||||
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
|
_, length, max_rank, _ = parse_inverse_mode_output(output)
|
||||||
assert length == 3
|
assert length == 3
|
||||||
assert max_rank == 0
|
assert max_rank == 0
|
||||||
|
|
||||||
@ -122,17 +122,17 @@ Excerpt:
|
|||||||
|
|
||||||
def test_short_longest_excerpt_line(self) -> None:
|
def test_short_longest_excerpt_line(self) -> None:
|
||||||
output = "LONGEST EXCERPT: 0"
|
output = "LONGEST EXCERPT: 0"
|
||||||
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
|
_, length, _, _ = parse_inverse_mode_output(output)
|
||||||
assert length == 0
|
assert length == 0
|
||||||
|
|
||||||
def test_too_few_parts_in_longest_excerpt(self) -> None:
|
def test_too_few_parts_in_longest_excerpt(self) -> None:
|
||||||
output = "LONGEST EXCERPT:"
|
output = "LONGEST EXCERPT:"
|
||||||
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
|
_, length, _, _ = parse_inverse_mode_output(output)
|
||||||
assert length == 0
|
assert length == 0
|
||||||
|
|
||||||
def test_rarest_word_without_hash_number(self) -> None:
|
def test_rarest_word_without_hash_number(self) -> None:
|
||||||
output = "Rarest word used: unknown"
|
output = "Rarest word used: unknown"
|
||||||
excerpt, length, max_rank, vocab = parse_inverse_mode_output(output)
|
_, _, max_rank, _ = parse_inverse_mode_output(output)
|
||||||
assert max_rank == 0
|
assert max_rank == 0
|
||||||
|
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ class TestParseTargetLengthBlock:
|
|||||||
"[Length 3] Vocab needed: 2",
|
"[Length 3] Vocab needed: 2",
|
||||||
" Words: hello(#1)",
|
" Words: hello(#1)",
|
||||||
]
|
]
|
||||||
excerpt, words = _parse_target_length_block(lines, 3)
|
excerpt, _ = _parse_target_length_block(lines, 3)
|
||||||
assert excerpt == ""
|
assert excerpt == ""
|
||||||
|
|
||||||
def test_no_words_line(self) -> None:
|
def test_no_words_line(self) -> None:
|
||||||
|
|||||||
@ -195,7 +195,7 @@ class TestTranslateWordsBatch:
|
|||||||
mock_parent.package = mock_package_module
|
mock_parent.package = mock_package_module
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
|
|||||||
@ -158,7 +158,7 @@ class TestHandleTranslation:
|
|||||||
_cli._trans,
|
_cli._trans,
|
||||||
"translate_words_batch",
|
"translate_words_batch",
|
||||||
return_value=[
|
return_value=[
|
||||||
TranslationResult("hello", "hola", "en", "es", True),
|
TranslationResult("hello", "hola", "en", "es", success=True),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
@ -195,7 +195,7 @@ class TestHandleTranslation:
|
|||||||
_cli._trans,
|
_cli._trans,
|
||||||
"translate_words_batch",
|
"translate_words_batch",
|
||||||
return_value=[
|
return_value=[
|
||||||
TranslationResult("hello", "hola", "en", "es", True),
|
TranslationResult("hello", "hola", "en", "es", success=True),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
@ -219,8 +219,15 @@ class TestHandleTranslation:
|
|||||||
_cli._trans,
|
_cli._trans,
|
||||||
"translate_words_batch",
|
"translate_words_batch",
|
||||||
return_value=[
|
return_value=[
|
||||||
TranslationResult("hello", "hola", "en", "es", True),
|
TranslationResult("hello", "hola", "en", "es", success=True),
|
||||||
TranslationResult("xyz", "", "en", "es", False, "error"),
|
TranslationResult(
|
||||||
|
"xyz",
|
||||||
|
"",
|
||||||
|
"en",
|
||||||
|
"es",
|
||||||
|
success=False,
|
||||||
|
error="error",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
patch.object(
|
patch.object(
|
||||||
@ -237,7 +244,7 @@ class TestMain:
|
|||||||
"""Tests for main entry point."""
|
"""Tests for main entry point."""
|
||||||
|
|
||||||
def test_argos_not_available(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_argos_not_available(self, capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
with patch.object(_cli._trans, "_check_argos", return_value=False):
|
with patch.object(_cli._trans, "check_argos", return_value=False):
|
||||||
result = main(["--text", "hello", "--from", "en", "--to", "es"])
|
result = main(["--text", "hello", "--from", "en", "--to", "es"])
|
||||||
assert result == 1
|
assert result == 1
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
@ -245,7 +252,7 @@ class TestMain:
|
|||||||
|
|
||||||
def test_list_languages(self) -> None:
|
def test_list_languages(self) -> None:
|
||||||
with (
|
with (
|
||||||
patch.object(_cli._trans, "_check_argos", return_value=True),
|
patch.object(_cli._trans, "check_argos", return_value=True),
|
||||||
patch.object(
|
patch.object(
|
||||||
_cli._trans,
|
_cli._trans,
|
||||||
"get_installed_languages",
|
"get_installed_languages",
|
||||||
@ -257,7 +264,7 @@ class TestMain:
|
|||||||
|
|
||||||
def test_list_available(self) -> None:
|
def test_list_available(self) -> None:
|
||||||
with (
|
with (
|
||||||
patch.object(_cli._trans, "_check_argos", return_value=True),
|
patch.object(_cli._trans, "check_argos", return_value=True),
|
||||||
patch.object(_cli._trans, "get_available_packages", return_value=[]),
|
patch.object(_cli._trans, "get_available_packages", return_value=[]),
|
||||||
):
|
):
|
||||||
result = main(["--list-available"])
|
result = main(["--list-available"])
|
||||||
@ -265,7 +272,7 @@ class TestMain:
|
|||||||
|
|
||||||
def test_download(self, capsys: pytest.CaptureFixture[str]) -> None:
|
def test_download(self, capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
with (
|
with (
|
||||||
patch.object(_cli._trans, "_check_argos", return_value=True),
|
patch.object(_cli._trans, "check_argos", return_value=True),
|
||||||
patch.object(
|
patch.object(
|
||||||
_cli._trans,
|
_cli._trans,
|
||||||
"download_languages",
|
"download_languages",
|
||||||
@ -276,7 +283,7 @@ class TestMain:
|
|||||||
assert result == 0
|
assert result == 0
|
||||||
|
|
||||||
def test_no_input_shows_help(self) -> None:
|
def test_no_input_shows_help(self) -> None:
|
||||||
with patch.object(_cli._trans, "_check_argos", return_value=True):
|
with patch.object(_cli._trans, "check_argos", return_value=True):
|
||||||
result = main([])
|
result = main([])
|
||||||
assert result == 1
|
assert result == 1
|
||||||
|
|
||||||
@ -284,7 +291,7 @@ class TestMain:
|
|||||||
self, capsys: pytest.CaptureFixture[str]
|
self, capsys: pytest.CaptureFixture[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
with (
|
with (
|
||||||
patch.object(_cli._trans, "_check_argos", return_value=True),
|
patch.object(_cli._trans, "check_argos", return_value=True),
|
||||||
patch.object(
|
patch.object(
|
||||||
_cli._trans,
|
_cli._trans,
|
||||||
"read_file",
|
"read_file",
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
import tempfile
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ class TestEnsureLanguagePair:
|
|||||||
mock_pkg = MagicMock()
|
mock_pkg = MagicMock()
|
||||||
mock_pkg.from_code = "en"
|
mock_pkg.from_code = "en"
|
||||||
mock_pkg.to_code = "es"
|
mock_pkg.to_code = "es"
|
||||||
mock_pkg.download.return_value = "/tmp/pkg.argosmodel"
|
mock_pkg.download.return_value = tempfile.gettempdir() + "/pkg.argosmodel"
|
||||||
mock_argos = MagicMock()
|
mock_argos = MagicMock()
|
||||||
mock_argos.translate.get_installed_languages.return_value = [
|
mock_argos.translate.get_installed_languages.return_value = [
|
||||||
mock_from,
|
mock_from,
|
||||||
@ -262,7 +263,7 @@ class TestEnsureLanguagePair:
|
|||||||
mock_pkg = MagicMock()
|
mock_pkg = MagicMock()
|
||||||
mock_pkg.from_code = "en"
|
mock_pkg.from_code = "en"
|
||||||
mock_pkg.to_code = "es"
|
mock_pkg.to_code = "es"
|
||||||
mock_pkg.download.return_value = "/tmp/pkg"
|
mock_pkg.download.return_value = tempfile.gettempdir() + "/pkg"
|
||||||
mock_argos = MagicMock()
|
mock_argos = MagicMock()
|
||||||
mock_argos.translate.get_installed_languages.return_value = [mock_to]
|
mock_argos.translate.get_installed_languages.return_value = [mock_to]
|
||||||
mock_argos.package.get_available_packages.return_value = [mock_pkg]
|
mock_argos.package.get_available_packages.return_value = [mock_pkg]
|
||||||
@ -275,14 +276,14 @@ class TestFormatTranslations:
|
|||||||
|
|
||||||
def test_failed_with_no_error(self) -> None:
|
def test_failed_with_no_error(self) -> None:
|
||||||
results = [
|
results = [
|
||||||
TranslationResult("xyz", "", "en", "es", False),
|
TranslationResult("xyz", "", "en", "es", success=False),
|
||||||
]
|
]
|
||||||
output = format_translations(results)
|
output = format_translations(results)
|
||||||
assert "[Failed]" in output
|
assert "[Failed]" in output
|
||||||
|
|
||||||
def test_all_failed_max_trans(self) -> None:
|
def test_all_failed_max_trans(self) -> None:
|
||||||
results = [
|
results = [
|
||||||
TranslationResult("xyz", "", "en", "es", False, "err"),
|
TranslationResult("xyz", "", "en", "es", success=False, error="err"),
|
||||||
]
|
]
|
||||||
output = format_translations(results)
|
output = format_translations(results)
|
||||||
assert "Translation" in output
|
assert "Translation" in output
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ if TYPE_CHECKING:
|
|||||||
class TestGetInstalledLanguages:
|
class TestGetInstalledLanguages:
|
||||||
"""Tests for get_installed_languages function."""
|
"""Tests for get_installed_languages function."""
|
||||||
|
|
||||||
def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None:
|
def test_argos_unavailable(self, mock_argos_unavailable: None) -> None:
|
||||||
"""Test when argos is unavailable."""
|
"""Test when argos is unavailable."""
|
||||||
result = get_installed_languages()
|
result = get_installed_languages()
|
||||||
assert result == []
|
assert result == []
|
||||||
@ -56,7 +57,7 @@ class TestGetInstalledLanguages:
|
|||||||
mock_parent.package = mock_package_module
|
mock_parent.package = mock_package_module
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
@ -79,7 +80,7 @@ class TestGetInstalledLanguages:
|
|||||||
class TestGetAvailablePackages:
|
class TestGetAvailablePackages:
|
||||||
"""Tests for get_available_packages function."""
|
"""Tests for get_available_packages function."""
|
||||||
|
|
||||||
def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None:
|
def test_argos_unavailable(self, mock_argos_unavailable: None) -> None:
|
||||||
"""Test when argos is unavailable."""
|
"""Test when argos is unavailable."""
|
||||||
result = get_available_packages()
|
result = get_available_packages()
|
||||||
assert result == []
|
assert result == []
|
||||||
@ -91,7 +92,7 @@ class TestGetAvailablePackages:
|
|||||||
class TestDownloadLanguages:
|
class TestDownloadLanguages:
|
||||||
"""Tests for download_languages function."""
|
"""Tests for download_languages function."""
|
||||||
|
|
||||||
def test_argos_unavailable(self, _mock_argos_unavailable: None) -> None:
|
def test_argos_unavailable(self, mock_argos_unavailable: None) -> None:
|
||||||
"""Test when argos is unavailable."""
|
"""Test when argos is unavailable."""
|
||||||
result = download_languages(["en", "es"])
|
result = download_languages(["en", "es"])
|
||||||
assert result == {}
|
assert result == {}
|
||||||
@ -124,7 +125,7 @@ class TestReadFile:
|
|||||||
class TestMain:
|
class TestMain:
|
||||||
"""Tests for main CLI function."""
|
"""Tests for main CLI function."""
|
||||||
|
|
||||||
def test_argos_unavailable_error(self, _mock_argos_unavailable: None) -> None:
|
def test_argos_unavailable_error(self, mock_argos_unavailable: None) -> None:
|
||||||
"""Test error when argos not installed."""
|
"""Test error when argos not installed."""
|
||||||
result = main(["--text", "hello", "--from", "en", "--to", "es"])
|
result = main(["--text", "hello", "--from", "en", "--to", "es"])
|
||||||
assert result == 1
|
assert result == 1
|
||||||
@ -139,7 +140,7 @@ class TestMain:
|
|||||||
mock_parent.package = mock_package_module
|
mock_parent.package = mock_package_module
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
@ -172,7 +173,7 @@ class TestMain:
|
|||||||
mock_parent.package = mock_package_module
|
mock_parent.package = mock_package_module
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
@ -326,7 +327,7 @@ class TestGetAvailablePackagesWithArgos:
|
|||||||
mock_parent.translate = mock_translate
|
mock_parent.translate = mock_translate
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
@ -348,7 +349,7 @@ class TestDownloadLanguagesFull:
|
|||||||
pkg = MagicMock()
|
pkg = MagicMock()
|
||||||
pkg.from_code = "en"
|
pkg.from_code = "en"
|
||||||
pkg.to_code = "es"
|
pkg.to_code = "es"
|
||||||
pkg.download.return_value = "/tmp/fake.argosmodel"
|
pkg.download.return_value = tempfile.gettempdir() + "/fake.argosmodel"
|
||||||
|
|
||||||
mock_package = MagicMock()
|
mock_package = MagicMock()
|
||||||
mock_package.update_package_index.return_value = None
|
mock_package.update_package_index.return_value = None
|
||||||
@ -359,7 +360,7 @@ class TestDownloadLanguagesFull:
|
|||||||
mock_parent.translate = mock_translate
|
mock_parent.translate = mock_translate
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
@ -384,7 +385,7 @@ class TestDownloadLanguagesFull:
|
|||||||
mock_parent.translate = mock_translate
|
mock_parent.translate = mock_translate
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
@ -414,7 +415,7 @@ class TestDownloadLanguagesFull:
|
|||||||
mock_parent.translate = mock_translate
|
mock_parent.translate = mock_translate
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(translator, "_check_argos", return_value=True),
|
patch.object(translator, "check_argos", return_value=True),
|
||||||
patch.object(translator, "argostranslate", mock_parent, create=True),
|
patch.object(translator, "argostranslate", mock_parent, create=True),
|
||||||
patch.dict(
|
patch.dict(
|
||||||
"sys.modules",
|
"sys.modules",
|
||||||
|
|||||||
@ -65,7 +65,7 @@ logger = logging.getLogger(__name__)
|
|||||||
_BATCH_SIZE = 100
|
_BATCH_SIZE = 100
|
||||||
|
|
||||||
|
|
||||||
def _check_argos() -> bool:
|
def check_argos() -> bool:
|
||||||
"""Check if argostranslate is available."""
|
"""Check if argostranslate is available."""
|
||||||
return argostranslate is not None
|
return argostranslate is not None
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ def get_installed_languages() -> list[tuple[str, str]]:
|
|||||||
Returns:
|
Returns:
|
||||||
List of (code, name) tuples for installed languages.
|
List of (code, name) tuples for installed languages.
|
||||||
"""
|
"""
|
||||||
if not _check_argos():
|
if not check_argos():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
languages = argostranslate.translate.get_installed_languages()
|
languages = argostranslate.translate.get_installed_languages()
|
||||||
@ -89,7 +89,7 @@ def get_available_packages() -> list[tuple[str, str, str, str]]:
|
|||||||
Returns:
|
Returns:
|
||||||
List of (from_code, from_name, to_code, to_name) tuples.
|
List of (from_code, from_name, to_code, to_name) tuples.
|
||||||
"""
|
"""
|
||||||
if not _check_argos():
|
if not check_argos():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
argostranslate.package.update_package_index()
|
argostranslate.package.update_package_index()
|
||||||
@ -111,7 +111,7 @@ def download_languages(lang_codes: Sequence[str]) -> dict[str, bool]:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict mapping "from->to" to success boolean.
|
Dict mapping "from->to" to success boolean.
|
||||||
"""
|
"""
|
||||||
if not _check_argos():
|
if not check_argos():
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
results: dict[str, bool] = {}
|
results: dict[str, bool] = {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user