-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_tktrio.py
More file actions
191 lines (140 loc) · 5.43 KB
/
test_tktrio.py
File metadata and controls
191 lines (140 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
from __future__ import annotations
from collections.abc import Callable
from functools import partial
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import pytest
import trio
from idlemypyextension.tktrio import RunStatus, TkTrioRunner
if TYPE_CHECKING:
import tkinter as tk
from collections.abc import Callable, Generator
from typing_extensions import TypeVarTuple, Unpack
PosArgT = TypeVarTuple("PosArgT")
@pytest.fixture(autouse=True)
def mock_extension_log() -> Generator[MagicMock, None, None]:
"""Fixture to override extension_log with an empty function."""
with patch(
"idlemypyextension.utils.extension_log",
return_value=None,
) as mock_log:
yield mock_log
class FakeTK:
"""Fake tkinter root."""
__slots__ = ("__dict__", "should_stop", "tasks")
def __init__(self) -> None:
"""Initialize self."""
self.tasks: list[Callable[..., object]] = []
self.should_stop = False
def after_idle(
self,
function: Callable[[Unpack[PosArgT]], object],
*args: Unpack[PosArgT],
) -> None:
"""Add function to run queue."""
self.tasks.append(partial(function, *args))
def after(
self,
_delay: int,
function: Callable[[Unpack[PosArgT]], object],
*args: Unpack[PosArgT],
) -> None:
"""Add function to run queue."""
self.tasks.append(partial(function, *args))
def update(self) -> None:
"""Run one task from queue."""
if self.should_stop:
raise RuntimeError(f"{self.should_stop = }")
if self.tasks:
self.tasks.pop(0)()
def destroy(self) -> None:
"""Mark update to fail if called again."""
self.should_stop = True
def withdraw(self) -> None:
"""Fake hide window."""
def protocol(
self,
name: str | None,
func: Callable[[], None] | None = None,
) -> None:
"""Fake protocol."""
@pytest.fixture
def mock_tk() -> FakeTK:
"""Fixture to create a mock tkinter root."""
return FakeTK()
@pytest.fixture
def trio_runner(mock_tk: tk.Tk) -> TkTrioRunner:
"""Fixture to create a TkTrioRunner instance."""
with patch(
"idlemypyextension.tktrio.is_tk_wm_and_misc_subclass",
return_value=True,
):
return TkTrioRunner(mock_tk, None)
def test_initialization(trio_runner: TkTrioRunner) -> None:
"""Test initialization of TkTrioRunner."""
assert trio_runner.run_status == RunStatus.NO_TASK
assert trio_runner.root is not None
def test_new_gives_copy(mock_tk: tk.Tk) -> None:
with patch(
"idlemypyextension.tktrio.is_tk_wm_and_misc_subclass",
return_value=True,
):
runner = TkTrioRunner(mock_tk, None)
runner2 = TkTrioRunner(mock_tk, None)
assert runner is runner2
def test_invalid_initialization() -> None:
"""Test initialization with invalid root."""
with pytest.raises(
ValueError,
match=r"^Must be subclass of both tk\.Misc and tk\.Wm$",
):
TkTrioRunner(None, None) # type: ignore[arg-type]
def test_schedule_task_not_threadsafe(trio_runner: TkTrioRunner) -> None:
"""Test scheduling a task in the Tkinter event loop."""
mock_function = MagicMock()
trio_runner.schedule_task_not_threadsafe(mock_function)
# Process the scheduled tasks
trio_runner.root.update()
mock_function.assert_called_once()
def test_cancel_current_task(trio_runner: TkTrioRunner) -> None:
"""Test canceling the current task."""
assert trio_runner.run_status != RunStatus.TRIO_RUNNING
async def test() -> None:
while True:
await trio.lowlevel.checkpoint()
trio_runner(test)
while trio_runner.run_status == RunStatus.TRIO_STARTING:
trio_runner.root.update()
nursery = trio_runner.nursery
trio_runner.cancel_current_task()
assert trio_runner.run_status == RunStatus.TRIO_RUNNING_CANCELED
while str(trio_runner.run_status) != str(RunStatus.NO_TASK):
trio_runner.root.update()
assert nursery.cancel_scope.cancel_called
trio_runner.cancel_current_task()
def test_get_del_window_proto(trio_runner: TkTrioRunner) -> None:
"""Test the WM_DELETE_WINDOW protocol."""
new_protocol = MagicMock()
shutdown_function = trio_runner.get_del_window_proto(new_protocol)
# Call the shutdown function
shutdown_function()
# Check if the new protocol was called
new_protocol.assert_called_once()
def test_show_warning_trio_already_running(trio_runner: TkTrioRunner) -> None:
"""Test showing warning when Trio is already running."""
with patch("tkinter.messagebox.showerror") as mock_showerror:
trio_runner.show_warning_trio_already_running()
mock_showerror.assert_called_once_with(
title="Error: Trio is already running",
message="Trio is already running from somewhere else, please try again later.",
parent=trio_runner.root,
)
def test_no_start_trio_is_stopping(trio_runner: TkTrioRunner) -> None:
"""Test showing warning when Trio is stopping."""
with patch("tkinter.messagebox.showwarning") as mock_showwarning:
trio_runner.no_start_trio_is_stopping()
mock_showwarning.assert_called_once_with(
title="Warning: Trio is stopping a previous run",
message="Trio is in the process of stopping, please try again later.",
parent=trio_runner.root,
)