mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 17:03:05 +02:00
- Add comprehensive tests for all packages (3572 tests, 100% branch coverage) - Split oversized test files to stay under 500-line limit - Add per-file ruff ignores for test-appropriate suppressions - Fix _cache_decks.py to properly convert JSON lists to tuples - Add session-scoped conftest fixture for logging handler cleanup (Python 3.14) - Update ruff pre-commit hook to v0.15.2 - Add codespell ignore words for test data - Add generated output files to .gitignore
344 lines
9.5 KiB
Python
344 lines
9.5 KiB
Python
"""Tests for Q9 diagram generation (concurrency: processes & threads)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import matplotlib as mpl
|
|
import matplotlib.pyplot as plt
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _patch_savefig(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Prevent matplotlib from writing files to disk."""
|
|
monkeypatch.setattr(mpl.figure.Figure, "savefig", lambda *_a, **_kw: None)
|
|
monkeypatch.setattr(plt, "savefig", lambda *_a, **_kw: None)
|
|
|
|
|
|
# =====================================================================
|
|
# _q9_common
|
|
# =====================================================================
|
|
class TestQ9Common:
|
|
"""Tests for _q9_common constants and helpers."""
|
|
|
|
def test_constants_exist(self) -> None:
|
|
from _q9_common import (
|
|
BG,
|
|
DPI,
|
|
FS,
|
|
FS_LABEL,
|
|
FS_SMALL,
|
|
FS_TITLE,
|
|
GRAY1,
|
|
GRAY2,
|
|
GRAY3,
|
|
GRAY4,
|
|
GRAY5,
|
|
LN,
|
|
OCCUPIED_SLOTS,
|
|
OUTPUT_DIR,
|
|
)
|
|
|
|
assert DPI == 300
|
|
assert BG == "white"
|
|
assert LN == "black"
|
|
assert FS == 8
|
|
assert FS_TITLE == 11
|
|
assert OCCUPIED_SLOTS == 2
|
|
assert isinstance(FS_SMALL, float)
|
|
assert isinstance(FS_LABEL, int)
|
|
assert isinstance(GRAY1, str)
|
|
assert isinstance(GRAY2, str)
|
|
assert isinstance(GRAY3, str)
|
|
assert isinstance(GRAY4, str)
|
|
assert isinstance(GRAY5, str)
|
|
assert isinstance(OUTPUT_DIR, str)
|
|
|
|
def test_draw_box_rounded(self) -> None:
|
|
from _q9_common import draw_box
|
|
|
|
fig, ax = plt.subplots()
|
|
draw_box(ax, 1.0, 2.0, 3.0, 1.0, "test")
|
|
assert len(ax.patches) == 1
|
|
plt.close(fig)
|
|
|
|
def test_draw_box_not_rounded(self) -> None:
|
|
from _q9_common import draw_box
|
|
|
|
fig, ax = plt.subplots()
|
|
draw_box(ax, 0.0, 0.0, 2.0, 1.0, "rect", rounded=False)
|
|
assert len(ax.patches) == 1
|
|
plt.close(fig)
|
|
|
|
def test_draw_box_custom_edgecolor_linestyle(self) -> None:
|
|
from _q9_common import draw_box
|
|
|
|
fig, ax = plt.subplots()
|
|
draw_box(
|
|
ax,
|
|
0.0,
|
|
0.0,
|
|
2.0,
|
|
1.0,
|
|
"custom",
|
|
edgecolor="red",
|
|
linestyle="--",
|
|
rounded=True,
|
|
)
|
|
assert len(ax.patches) == 1
|
|
plt.close(fig)
|
|
|
|
def test_draw_box_not_rounded_custom_linestyle(self) -> None:
|
|
from _q9_common import draw_box
|
|
|
|
fig, ax = plt.subplots()
|
|
draw_box(
|
|
ax,
|
|
0.0,
|
|
0.0,
|
|
2.0,
|
|
1.0,
|
|
"dashed",
|
|
edgecolor="blue",
|
|
linestyle="--",
|
|
rounded=False,
|
|
)
|
|
assert len(ax.patches) == 1
|
|
plt.close(fig)
|
|
|
|
def test_draw_arrow(self) -> None:
|
|
from _q9_common import draw_arrow
|
|
|
|
fig, ax = plt.subplots()
|
|
draw_arrow(ax, 0.0, 0.0, 1.0, 1.0)
|
|
plt.close(fig)
|
|
|
|
def test_draw_double_arrow(self) -> None:
|
|
from _q9_common import draw_double_arrow
|
|
|
|
fig, ax = plt.subplots()
|
|
draw_double_arrow(ax, 0.0, 0.0, 1.0, 1.0)
|
|
plt.close(fig)
|
|
|
|
def test_save_fig(self) -> None:
|
|
from _q9_common import save_fig
|
|
|
|
fig, _ax = plt.subplots()
|
|
save_fig(fig, "test_output.png")
|
|
|
|
def test_draw_table(self) -> None:
|
|
from _q9_common import draw_table
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(-5, 2)
|
|
headers = ["A", "B", "C"]
|
|
rows = [["1", "2", "3"], ["4", "5", "6"]]
|
|
draw_table(ax, headers, rows, 0, 1, [2.0, 3.0, 3.0], row_h=0.5)
|
|
assert len(ax.patches) > 0
|
|
plt.close(fig)
|
|
|
|
def test_draw_table_custom_fills(self) -> None:
|
|
from _q9_common import draw_table
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(-5, 2)
|
|
headers = ["A", "B"]
|
|
rows = [["1", "2"], ["3", "4"]]
|
|
draw_table(
|
|
ax,
|
|
headers,
|
|
rows,
|
|
0,
|
|
1,
|
|
[2.0, 3.0],
|
|
row_fills=["#FF0000", "#00FF00"],
|
|
header_fontsize=10,
|
|
)
|
|
plt.close(fig)
|
|
|
|
def test_draw_table_no_header_fontsize(self) -> None:
|
|
from _q9_common import draw_table
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(-5, 2)
|
|
draw_table(
|
|
ax,
|
|
["H1"],
|
|
[["V1"]],
|
|
0,
|
|
1,
|
|
[3.0],
|
|
header_fontsize=None,
|
|
)
|
|
plt.close(fig)
|
|
|
|
|
|
# =====================================================================
|
|
# _q9_basics
|
|
# =====================================================================
|
|
class TestQ9Basics:
|
|
"""Tests for the 6 basic process/thread diagrams."""
|
|
|
|
def test_gen_process_vs_thread(self) -> None:
|
|
from _q9_basics import gen_process_vs_thread
|
|
|
|
gen_process_vs_thread()
|
|
|
|
def test_gen_memory_layout(self) -> None:
|
|
from _q9_basics import gen_memory_layout
|
|
|
|
gen_memory_layout()
|
|
|
|
def test_gen_process_states(self) -> None:
|
|
from _q9_basics import gen_process_states
|
|
|
|
gen_process_states()
|
|
|
|
def test_gen_thread_structure(self) -> None:
|
|
from _q9_basics import gen_thread_structure
|
|
|
|
gen_thread_structure()
|
|
|
|
def test_gen_pcb_structure(self) -> None:
|
|
from _q9_basics import gen_pcb_structure
|
|
|
|
gen_pcb_structure()
|
|
|
|
def test_gen_speed_comparison(self) -> None:
|
|
from _q9_basics import gen_speed_comparison
|
|
|
|
gen_speed_comparison()
|
|
|
|
|
|
# =====================================================================
|
|
# _q9_classic_sync
|
|
# =====================================================================
|
|
class TestQ9ClassicSync:
|
|
"""Tests for classic sync problems."""
|
|
|
|
def test_draw_bounded_buffer_panel(self) -> None:
|
|
from _q9_classic_sync import _draw_bounded_buffer_panel
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_bounded_buffer_panel(ax)
|
|
assert len(ax.patches) > 0
|
|
plt.close(fig)
|
|
|
|
def test_draw_readers_writers_panel(self) -> None:
|
|
from _q9_classic_sync import _draw_readers_writers_panel
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_readers_writers_panel(ax)
|
|
assert len(ax.patches) > 0
|
|
plt.close(fig)
|
|
|
|
def test_draw_philosophers_panel(self) -> None:
|
|
from _q9_classic_sync import _draw_philosophers_panel
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_philosophers_panel(ax)
|
|
assert len(ax.patches) > 0
|
|
plt.close(fig)
|
|
|
|
def test_gen_classic_problems(self) -> None:
|
|
from _q9_classic_sync import gen_classic_problems
|
|
|
|
gen_classic_problems()
|
|
|
|
def test_gen_sync_comparison(self) -> None:
|
|
from _q9_classic_sync import gen_sync_comparison
|
|
|
|
gen_sync_comparison()
|
|
|
|
def test_gen_semaphore_concept(self) -> None:
|
|
from _q9_classic_sync import gen_semaphore_concept
|
|
|
|
gen_semaphore_concept()
|
|
|
|
|
|
# =====================================================================
|
|
# _q9_ipc
|
|
# =====================================================================
|
|
class TestQ9Ipc:
|
|
"""Tests for IPC mechanism diagrams."""
|
|
|
|
def test_gen_scenario_table(self) -> None:
|
|
from _q9_ipc import gen_scenario_table
|
|
|
|
gen_scenario_table()
|
|
|
|
def test_gen_ipc_details(self) -> None:
|
|
from _q9_ipc import gen_ipc_details
|
|
|
|
gen_ipc_details()
|
|
|
|
def test_gen_ipc_table(self) -> None:
|
|
from _q9_ipc import gen_ipc_table
|
|
|
|
gen_ipc_table()
|
|
|
|
|
|
# =====================================================================
|
|
# _q9_race_deadlock
|
|
# =====================================================================
|
|
class TestQ9RaceDeadlock:
|
|
"""Tests for race condition, deadlock, and starvation diagrams."""
|
|
|
|
def test_gen_race_condition(self) -> None:
|
|
from _q9_race_deadlock import gen_race_condition
|
|
|
|
gen_race_condition()
|
|
|
|
def test_gen_deadlock_scenario(self) -> None:
|
|
from _q9_race_deadlock import gen_deadlock_scenario
|
|
|
|
gen_deadlock_scenario()
|
|
|
|
def test_gen_coffman_strategies(self) -> None:
|
|
from _q9_race_deadlock import gen_coffman_strategies
|
|
|
|
gen_coffman_strategies()
|
|
|
|
def test_gen_starvation_priority(self) -> None:
|
|
from _q9_race_deadlock import gen_starvation_priority
|
|
|
|
gen_starvation_priority()
|
|
|
|
|
|
# =====================================================================
|
|
# generate_q9_all_diagrams
|
|
# =====================================================================
|
|
class TestGenerateQ9AllDiagrams:
|
|
"""Tests for the Q9 diagram generation entrypoint."""
|
|
|
|
def test_module_exports(self) -> None:
|
|
from generate_q9_all_diagrams import __all__
|
|
|
|
expected = [
|
|
"gen_classic_problems",
|
|
"gen_coffman_strategies",
|
|
"gen_deadlock_scenario",
|
|
"gen_ipc_details",
|
|
"gen_ipc_table",
|
|
"gen_memory_layout",
|
|
"gen_pcb_structure",
|
|
"gen_process_states",
|
|
"gen_process_vs_thread",
|
|
"gen_race_condition",
|
|
"gen_scenario_table",
|
|
"gen_semaphore_concept",
|
|
"gen_speed_comparison",
|
|
"gen_starvation_priority",
|
|
"gen_sync_comparison",
|
|
"gen_thread_structure",
|
|
]
|
|
assert sorted(__all__) == sorted(expected)
|
|
|
|
def test_all_functions_callable(self) -> None:
|
|
import generate_q9_all_diagrams as mod
|
|
|
|
for name in mod.__all__:
|
|
assert callable(getattr(mod, name))
|