Skip to content

Commit 62f890c

Browse files
committed
Add unit tests for wait_loop
Closes #29
1 parent 27eaba8 commit 62f890c

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed

tests/test_wait_loop.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import logging
2+
import shutil
3+
from tempfile import mkdtemp
4+
from unittest.mock import Mock, call
5+
6+
import nginx_config_reloader
7+
from nginx_config_reloader import ListenTargetTerminated, wait_loop
8+
from tests.testcase import TestCase
9+
10+
11+
class TestWaitLoop(TestCase):
12+
def setUp(self):
13+
self.source = mkdtemp()
14+
self.mock_logger = Mock(spec_set=logging.Logger)
15+
self.nginx_config_reloader = self.set_up_patch(
16+
"nginx_config_reloader.NginxConfigReloader"
17+
)
18+
self.mock_handler = Mock()
19+
self.nginx_config_reloader.return_value = self.mock_handler
20+
self.set_up_patch("nginx_config_reloader.SYSTEM_BUS")
21+
self.set_up_patch("nginx_config_reloader.NginxConfigReloaderInterface")
22+
self.set_up_patch("nginx_config_reloader.threading.Thread")
23+
self.time_sleep = self.set_up_patch("nginx_config_reloader.time.sleep")
24+
self.after_loop = self.set_up_patch("nginx_config_reloader.after_loop")
25+
26+
def tearDown(self):
27+
shutil.rmtree(self.source, ignore_errors=True)
28+
29+
def test_wait_loop_creates_nginx_config_reloader_handler(self):
30+
self._run_wait_loop_with_keyboard_interrupt()
31+
32+
self.nginx_config_reloader.assert_called_once_with(
33+
logger=self.mock_logger,
34+
no_magento_config=False,
35+
no_custom_config=False,
36+
dir_to_watch=self.source,
37+
use_systemd=False,
38+
)
39+
40+
def test_wait_loop_creates_handler_with_custom_arguments(self):
41+
self._run_wait_loop_with_keyboard_interrupt(
42+
no_magento_config=True,
43+
no_custom_config=True,
44+
use_systemd=True,
45+
)
46+
47+
self.nginx_config_reloader.assert_called_once_with(
48+
logger=self.mock_logger,
49+
no_magento_config=True,
50+
no_custom_config=True,
51+
dir_to_watch=self.source,
52+
use_systemd=True,
53+
)
54+
55+
def test_wait_loop_sets_up_dbus_when_no_dbus_is_false(self):
56+
system_bus = self.set_up_patch("nginx_config_reloader.SYSTEM_BUS")
57+
interface_class = self.set_up_patch(
58+
"nginx_config_reloader.NginxConfigReloaderInterface"
59+
)
60+
thread_class = self.set_up_patch("nginx_config_reloader.threading.Thread")
61+
62+
self._run_wait_loop_with_keyboard_interrupt(no_dbus=False)
63+
64+
interface_class.assert_called_once_with(self.mock_handler)
65+
system_bus.publish_object.assert_called_once()
66+
system_bus.register_service.assert_called_once()
67+
thread_class.assert_called_once_with(
68+
target=nginx_config_reloader.dbus_event_loop
69+
)
70+
thread_class.return_value.start.assert_called_once()
71+
72+
def test_wait_loop_skips_dbus_setup_when_no_dbus_is_true(self):
73+
system_bus = self.set_up_patch("nginx_config_reloader.SYSTEM_BUS")
74+
interface_class = self.set_up_patch(
75+
"nginx_config_reloader.NginxConfigReloaderInterface"
76+
)
77+
thread_class = self.set_up_patch("nginx_config_reloader.threading.Thread")
78+
79+
self._run_wait_loop_with_keyboard_interrupt(no_dbus=True)
80+
81+
interface_class.assert_not_called()
82+
system_bus.publish_object.assert_not_called()
83+
system_bus.register_service.assert_not_called()
84+
thread_class.assert_not_called()
85+
86+
def test_wait_loop_waits_for_directory_to_appear(self):
87+
# Return False twice, then True on subsequent calls
88+
exists_mock = self.set_up_patch(
89+
"nginx_config_reloader.os.path.exists", side_effect=[False, False, True]
90+
)
91+
92+
self._run_wait_loop_with_keyboard_interrupt()
93+
94+
self.assertEqual(exists_mock.call_count, 3)
95+
# Should have called sleep(5) twice while waiting for directory
96+
sleep_calls = [c for c in self.time_sleep.call_args_list if c == call(5)]
97+
self.assertEqual(len(sleep_calls), 2)
98+
99+
def test_wait_loop_logs_warning_when_directory_not_found(self):
100+
# Return False once, then True
101+
self.set_up_patch(
102+
"nginx_config_reloader.os.path.exists", side_effect=[False, True]
103+
)
104+
105+
self._run_wait_loop_with_keyboard_interrupt()
106+
107+
self.mock_logger.warning.assert_any_call(
108+
f"Configuration dir {self.source} not found, waiting..."
109+
)
110+
111+
def test_wait_loop_calls_reload_with_send_signal_false(self):
112+
self._run_wait_loop_with_keyboard_interrupt()
113+
114+
self.mock_handler.reload.assert_called_once_with(send_signal=False)
115+
116+
def test_wait_loop_starts_observer(self):
117+
self._run_wait_loop_with_keyboard_interrupt()
118+
119+
self.mock_handler.start_observer.assert_called_once()
120+
121+
def test_wait_loop_calls_after_loop_in_loop(self):
122+
loop_count = [0]
123+
124+
def mock_after_loop(handler):
125+
loop_count[0] += 1
126+
if loop_count[0] >= 3:
127+
raise KeyboardInterrupt
128+
129+
self.after_loop.side_effect = mock_after_loop
130+
131+
wait_loop(
132+
logger=self.mock_logger,
133+
dir_to_watch=self.source,
134+
no_dbus=True,
135+
)
136+
137+
self.assertEqual(self.after_loop.call_count, 3)
138+
self.after_loop.assert_called_with(self.mock_handler)
139+
140+
def test_wait_loop_sleeps_one_second_between_after_loop_calls(self):
141+
loop_count = [0]
142+
143+
def mock_sleep(seconds):
144+
if seconds == 1:
145+
loop_count[0] += 1
146+
if loop_count[0] >= 2:
147+
raise KeyboardInterrupt
148+
149+
self.time_sleep.side_effect = mock_sleep
150+
151+
wait_loop(
152+
logger=self.mock_logger,
153+
dir_to_watch=self.source,
154+
no_dbus=True,
155+
)
156+
157+
self.assertGreaterEqual(loop_count[0], 2)
158+
159+
def test_wait_loop_handles_listen_target_terminated(self):
160+
call_count = [0]
161+
162+
def mock_start_observer():
163+
call_count[0] += 1
164+
if call_count[0] == 1:
165+
raise ListenTargetTerminated
166+
raise KeyboardInterrupt
167+
168+
self.mock_handler.start_observer.side_effect = mock_start_observer
169+
170+
wait_loop(
171+
logger=self.mock_logger,
172+
dir_to_watch=self.source,
173+
no_dbus=True,
174+
)
175+
176+
# Should have tried to start observer twice
177+
self.assertEqual(self.mock_handler.start_observer.call_count, 2)
178+
# Should have stopped observer after ListenTargetTerminated
179+
self.mock_handler.stop_observer.assert_called()
180+
# Should have logged warning
181+
self.mock_logger.warning.assert_any_call(
182+
"Configuration dir lost, waiting for it to reappear"
183+
)
184+
185+
def test_wait_loop_stops_observer_on_keyboard_interrupt(self):
186+
self._run_wait_loop_with_keyboard_interrupt()
187+
188+
self.mock_handler.stop_observer.assert_called_once()
189+
190+
def test_wait_loop_logs_info_on_keyboard_interrupt(self):
191+
self._run_wait_loop_with_keyboard_interrupt()
192+
193+
self.mock_logger.info.assert_any_call("Shutting down observer.")
194+
195+
def test_wait_loop_logs_info_when_starting_to_listen(self):
196+
self._run_wait_loop_with_keyboard_interrupt()
197+
198+
self.mock_logger.info.assert_any_call(f"Listening for changes to {self.source}")
199+
200+
def test_wait_loop_reloads_config_after_listen_target_terminated(self):
201+
call_count = [0]
202+
203+
def mock_start_observer():
204+
call_count[0] += 1
205+
if call_count[0] == 1:
206+
raise ListenTargetTerminated
207+
raise KeyboardInterrupt
208+
209+
self.mock_handler.start_observer.side_effect = mock_start_observer
210+
211+
wait_loop(
212+
logger=self.mock_logger,
213+
dir_to_watch=self.source,
214+
no_dbus=True,
215+
)
216+
217+
# reload should be called twice - once initially and once after recovery
218+
self.assertEqual(self.mock_handler.reload.call_count, 2)
219+
220+
def _run_wait_loop_with_keyboard_interrupt(self, **kwargs):
221+
"""Helper to run wait_loop that exits on first after_loop call."""
222+
self.after_loop.side_effect = KeyboardInterrupt
223+
224+
default_kwargs = {
225+
"dir_to_watch": self.source,
226+
"no_dbus": True,
227+
}
228+
default_kwargs.update(kwargs)
229+
230+
wait_loop(logger=self.mock_logger, **default_kwargs)

0 commit comments

Comments
 (0)