Skip to content

Commit 0c4692a

Browse files
committed
tests: add clean tests and use mocker instead of mock
1 parent ea7fe24 commit 0c4692a

1 file changed

Lines changed: 146 additions & 89 deletions

File tree

tests/unit_tests/test_instance.py

Lines changed: 146 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Tests related to pycloudlib.instance module."""
22

33
from itertools import repeat
4-
from unittest import mock
54

65
import pytest
76
from paramiko import SSHException
@@ -15,31 +14,31 @@
1514

1615

1716
@pytest.fixture
18-
def concrete_instance_cls():
17+
def concrete_instance_cls(mocker):
1918
"""Return a BaseInstance subclass which can be instantiated.
2019
2120
Source: https://stackoverflow.com/a/28738073
2221
"""
23-
with mock.patch.object(BaseInstance, "__abstractmethods__", set()):
24-
yield BaseInstance
22+
mocker.patch.object(BaseInstance, "__abstractmethods__", set())
23+
return BaseInstance
2524

2625

2726
class TestWait:
2827
"""Tests covering pycloudlib.instance.Instance.wait."""
2928

30-
def test_wait(self, concrete_instance_cls):
29+
def test_wait(self, concrete_instance_cls, mocker):
3130
"""Test wait calls the two methods it should with correct passthrough.
3231
3332
(`None` is used to test the default.)
3433
"""
3534
instance = concrete_instance_cls(key_pair=None)
36-
with mock.patch.multiple(
35+
mocks = mocker.patch.multiple(
3736
instance,
38-
_wait_for_instance_start=mock.DEFAULT,
39-
_wait_for_execute=mock.DEFAULT,
40-
_wait_for_cloudinit=mock.DEFAULT,
41-
) as mocks:
42-
instance.wait()
37+
_wait_for_instance_start=mocker.DEFAULT,
38+
_wait_for_execute=mocker.DEFAULT,
39+
_wait_for_cloudinit=mocker.DEFAULT,
40+
)
41+
instance.wait()
4342

4443
assert 1 == mocks["_wait_for_instance_start"].call_count
4544
assert 1 == mocks["_wait_for_execute"].call_count
@@ -52,25 +51,24 @@ def test_wait(self, concrete_instance_cls):
5251
pytest.param(SSHException, id="exception"),
5352
],
5453
)
55-
@mock.patch.object(BaseInstance, "execute")
56-
@mock.patch("pycloudlib.instance.time.sleep")
57-
@mock.patch("pycloudlib.instance.time.time")
58-
@mock.patch("logging.Logger.debug")
5954
def test_wait_execute_failure(
6055
self,
61-
m_debug,
62-
m_time,
63-
m_sleep,
64-
m_execute,
6556
execute_effect,
6657
concrete_instance_cls,
58+
mocker,
6759
):
6860
"""Test wait calls when execute command fails."""
61+
mocker.patch("logging.Logger.debug")
62+
mocker.patch("logging.Logger.info")
63+
m_time = mocker.patch("pycloudlib.instance.time.time")
64+
m_sleep = mocker.patch("pycloudlib.instance.time.sleep")
65+
m_execute = mocker.patch.object(BaseInstance, "execute")
66+
6967
instance = concrete_instance_cls(key_pair=None)
70-
m_time.side_effect = [1, 1, 2, 10 * 60, 10 * 60 + 1]
68+
m_time.side_effect = [1, 1, 2, 10 * 60 + 1]
7169
m_execute.side_effect = execute_effect
7270
expected_msg = "Instance can't be reached after 10 minutes. Failed to obtain new boot id"
73-
expected_call_args = [mock.call("cat /proc/sys/kernel/random/boot_id", no_log=True)] * 2
71+
expected_call_args = [mocker.call("cat /proc/sys/kernel/random/boot_id", no_log=True)] * 2
7472

7573
with pytest.raises(PycloudlibTimeoutError) as excinfo:
7674
instance.wait()
@@ -80,89 +78,77 @@ def test_wait_execute_failure(
8078
assert expected_call_args == m_execute.call_args_list
8179

8280

83-
@mock.patch("pycloudlib.instance.BaseInstance._do_restart")
84-
@mock.patch("pycloudlib.instance.BaseInstance.get_boot_id")
85-
@mock.patch("pycloudlib.instance.BaseInstance.wait")
86-
@mock.patch("pycloudlib.instance.BaseInstance.wait_for_restart")
8781
class TestRestart:
8882
"""Test base restart behavior."""
8983

9084
@pytest.fixture(autouse=True)
91-
def unchecked_mocks(self):
85+
def setup_mocks(self, mocker):
9286
"""Mock things we don't want as test parameters."""
93-
with mock.patch("pycloudlib.instance.BaseInstance._sync_filesystem"):
94-
yield
87+
mocker.patch("pycloudlib.instance.BaseInstance._sync_filesystem")
88+
self.m_wait_for_restart = mocker.patch("pycloudlib.instance.BaseInstance.wait_for_restart")
89+
self.m_wait = mocker.patch("pycloudlib.instance.BaseInstance.wait")
90+
self.m_boot_id = mocker.patch("pycloudlib.instance.BaseInstance.get_boot_id")
91+
self.m_do_restart = mocker.patch("pycloudlib.instance.BaseInstance._do_restart")
9592

9693
def test_no_wait(
9794
self,
98-
m_wait_for_restart,
99-
m_wait,
100-
m_boot_id,
101-
m_do_restart,
10295
concrete_instance_cls,
10396
):
10497
"""Test wait=False."""
10598
instance = concrete_instance_cls(key_pair=None)
10699
instance.restart(wait=False)
107-
assert m_do_restart.call_count == 1
108-
assert m_boot_id.call_count == 0
109-
assert m_wait_for_restart.call_count == 0
110-
assert m_wait.call_count == 0
100+
assert self.m_do_restart.call_count == 1
101+
assert self.m_boot_id.call_count == 0
102+
assert self.m_wait_for_restart.call_count == 0
103+
assert self.m_wait.call_count == 0
111104

112105
def test_instance_not_reachable(
113106
self,
114-
m_wait_for_restart,
115-
m_wait,
116-
m_boot_id,
117-
m_do_restart,
118107
concrete_instance_cls,
119108
):
120109
"""Test when instance is not reachable."""
121110
instance = concrete_instance_cls(key_pair=None)
122-
m_boot_id.side_effect = SSHException
111+
self.m_boot_id.side_effect = SSHException
123112
instance.restart(wait=True)
124-
assert m_do_restart.call_count == 1
125-
assert m_wait_for_restart.call_count == 0
126-
assert m_wait.call_count == 1
113+
assert self.m_do_restart.call_count == 1
114+
assert self.m_wait_for_restart.call_count == 0
115+
assert self.m_wait.call_count == 1
127116

128117
def test_instance_reachable(
129118
self,
130-
m_wait_for_restart,
131-
m_wait,
132-
m_boot_id,
133-
m_do_restart,
134119
concrete_instance_cls,
135120
):
136121
"""Test when instance is reachable."""
137122
instance = concrete_instance_cls(key_pair=None)
138-
m_boot_id.side_effect = Result("11111111-1111-1111-1111-111111111111", "", 0)
123+
self.m_boot_id.side_effect = Result("11111111-1111-1111-1111-111111111111", "", 0)
139124
instance.restart(wait=True)
140-
assert m_do_restart.call_count == 1
141-
assert m_wait_for_restart.call_count == 1
142-
assert m_wait.call_count == 0
125+
assert self.m_do_restart.call_count == 1
126+
assert self.m_wait_for_restart.call_count == 1
127+
assert self.m_wait.call_count == 0
143128

144129

145130
class TestWaitForRestart:
146131
"""Tests covering pycloudlib.instance.Instance.wait_for_restart."""
147132

148-
@mock.patch.object(
149-
BaseInstance,
150-
"execute",
151-
side_effect=[
152-
Result("11111111-1111-1111-1111-111111111111", "", 0),
153-
Result("11111111-1111-1111-1111-111111111111", "", 0),
154-
Result("11111111-1111-1111-1111-111111111111", "", 0),
155-
Result("11111111-1111-1111-1111-111111111111", "", 0),
156-
Result("22222222-2222-2222-2222-222222222222", "", 0),
157-
],
158-
)
159-
@mock.patch.object(BaseInstance, "_wait_for_cloudinit")
160-
@mock.patch("pycloudlib.instance.time.sleep")
161-
@mock.patch("pycloudlib.instance.time.time", return_value=1)
162133
def test_wait_for_restart(
163-
self, _m_time, _m_sleep, _m_wait_ci, m_execute, concrete_instance_cls
134+
self, concrete_instance_cls, mocker
164135
):
165136
"""Test wait calls _wait_for_execute and waits till differing."""
137+
mocker.patch("pycloudlib.instance.time.time", return_value=1)
138+
mocker.patch("pycloudlib.instance.time.sleep")
139+
mocker.patch.object(BaseInstance, "_wait_for_cloudinit")
140+
m_execute = mocker.patch.object(
141+
BaseInstance,
142+
"execute",
143+
side_effect=[
144+
Result("11111111-1111-1111-1111-111111111111", "", 0),
145+
Result("11111111-1111-1111-1111-111111111111", "", 0),
146+
Result("11111111-1111-1111-1111-111111111111", "", 0),
147+
Result("11111111-1111-1111-1111-111111111111", "", 0),
148+
Result("22222222-2222-2222-2222-222222222222", "", 0),
149+
],
150+
)
151+
166152
instance = concrete_instance_cls(key_pair=None)
167153
instance.wait_for_restart(old_boot_id="11111111-1111-1111-1111-111111111111")
168154
assert m_execute.call_count == 5
@@ -174,25 +160,24 @@ def test_wait_for_restart(
174160
repeat(Result("11111111-1111-1111-1111-111111111111", "", 0)),
175161
],
176162
)
177-
@mock.patch.object(BaseInstance, "execute")
178-
@mock.patch("pycloudlib.instance.time.sleep")
179-
@mock.patch("pycloudlib.instance.time.time")
180-
@mock.patch("logging.Logger.debug")
181163
def test_boot_id_failure(
182164
self,
183-
m_debug,
184-
m_time,
185-
m_sleep,
186-
m_execute,
187165
execute_side_effect,
188166
concrete_instance_cls,
167+
mocker,
189168
):
190169
"""Test wait calls when execute command fails."""
170+
mocker.patch("logging.Logger.debug")
171+
mocker.patch("logging.Logger.info")
172+
m_time = mocker.patch("pycloudlib.instance.time.time")
173+
m_sleep = mocker.patch("pycloudlib.instance.time.sleep")
174+
m_execute = mocker.patch.object(BaseInstance, "execute")
175+
191176
m_execute.side_effect = execute_side_effect
192177
instance = concrete_instance_cls(key_pair=None)
193-
m_time.side_effect = [1, 1, 2, 10 * 60, 10 * 60 + 1]
178+
m_time.side_effect = [1, 1, 2, 10 * 60 + 1]
194179
expected_msg = "Instance can't be reached after 10 minutes. Failed to obtain new boot id"
195-
expected_call_args = [mock.call("cat /proc/sys/kernel/random/boot_id", no_log=True)] * 2
180+
expected_call_args = [mocker.call("cat /proc/sys/kernel/random/boot_id", no_log=True)] * 2
196181

197182
with pytest.raises(PycloudlibTimeoutError) as excinfo:
198183
instance.wait_for_restart(old_boot_id="11111111-1111-1111-1111-111111111111")
@@ -207,44 +192,116 @@ def test_boot_id_failure(
207192
class TestWaitForCloudinit:
208193
"""Tests covering pycloudlib.instance.Instance._wait_for_cloudinit."""
209194

210-
def test_with_wait_available(self, concrete_instance_cls):
195+
def test_with_wait_available(self, concrete_instance_cls, mocker):
211196
"""Test the happy path for instances with `status --wait`."""
212197
instance = concrete_instance_cls(key_pair=None)
213-
with mock.patch.object(instance, "execute") as m_execute:
214-
instance._wait_for_cloudinit()
198+
m_execute = mocker.patch.object(instance, "execute")
199+
instance._wait_for_cloudinit()
215200

216201
assert (
217-
mock.call(
202+
mocker.call(
218203
["cloud-init", "status", "--wait", "--long"],
219204
description="waiting for start",
220205
)
221206
== m_execute.call_args
222207
)
223208

224-
@mock.patch("time.sleep")
225-
def test_wait_on_target_not_active(self, _m_sleep, concrete_instance_cls):
209+
def test_wait_on_target_not_active(self, concrete_instance_cls, mocker):
226210
"""Test that we wait for cloud-init is-active before calling status."""
211+
mocker.patch("time.sleep")
227212
instance = concrete_instance_cls(key_pair=None)
228-
with mock.patch.object(
213+
m_execute = mocker.patch.object(
229214
instance,
230215
"execute",
231216
side_effect=[Result("", "", 0)] + [Result("", "", 1)] * 500,
232-
) as m_execute:
233-
instance._wait_for_cloudinit()
217+
)
218+
219+
instance._wait_for_cloudinit()
234220
expected = [
235-
mock.call("command -v systemctl"),
221+
mocker.call("command -v systemctl"),
236222
*(
237223
[
238-
mock.call(
224+
mocker.call(
239225
["systemctl", "is-active", "cloud-init.target"],
240226
no_log=True,
241227
)
242228
]
243229
* 300
244230
),
245-
mock.call(
231+
mocker.call(
246232
["cloud-init", "status", "--wait", "--long"],
247233
description="waiting for start",
248234
),
249235
]
250236
assert expected == m_execute.call_args_list
237+
238+
239+
class TestClean:
240+
"""Tests covering pycloudlib.instance.BaseInstance.clean."""
241+
242+
def test_clean_with_c_all_support(self, concrete_instance_cls, mocker):
243+
"""Test clean method when cloud-init supports -c all option."""
244+
instance = concrete_instance_cls(key_pair=None)
245+
m_execute = mocker.patch.object(
246+
instance,
247+
"execute",
248+
side_effect=[
249+
Result("", "", 0), # cloud-init clean --logs --machine-id -c all
250+
Result("", "", 0), # rm -rf /var/log/syslog
251+
],
252+
)
253+
instance.clean()
254+
255+
expected_calls = [
256+
mocker.call("sudo cloud-init clean --logs --machine-id -c all"),
257+
mocker.call("sudo rm -rf /var/log/syslog"),
258+
]
259+
assert expected_calls == m_execute.call_args_list
260+
261+
def test_clean_without_arg_support(self, concrete_instance_cls, mocker):
262+
"""Test clean method when cloud-init doesn't support -c all option."""
263+
instance = concrete_instance_cls(key_pair=None)
264+
m_execute = mocker.patch.object(
265+
instance,
266+
"execute",
267+
side_effect=[
268+
Result(
269+
"",
270+
"cloud-init clean: error: unrecognized arguments: -c all",
271+
2,
272+
), # First attempt fails
273+
Result("", "", 0), # Fallback without -c all
274+
Result("", "", 0), # rm -rf /var/log/syslog
275+
],
276+
)
277+
instance.clean()
278+
279+
expected_calls = [
280+
mocker.call("sudo cloud-init clean --logs --machine-id -c all"),
281+
mocker.call("sudo cloud-init clean --logs"),
282+
mocker.call("sudo rm -rf /var/log/syslog"),
283+
]
284+
assert expected_calls == m_execute.call_args_list
285+
286+
def test_clean_unexpected_error(self, concrete_instance_cls, mocker):
287+
"""Test clean method does not fallback on unexpected error."""
288+
instance = concrete_instance_cls(key_pair=None)
289+
m_execute = mocker.patch.object(
290+
instance,
291+
"execute",
292+
side_effect=[
293+
Result(
294+
"",
295+
"cloud-init clean: error: permission denied",
296+
1,
297+
), # Different error
298+
Result("", "", 0), # rm -rf /var/log/syslog
299+
],
300+
)
301+
instance.clean()
302+
303+
expected_calls = [
304+
mocker.call("sudo cloud-init clean --logs --machine-id -c all"),
305+
mocker.call("sudo rm -rf /var/log/syslog"),
306+
]
307+
assert expected_calls == m_execute.call_args_list

0 commit comments

Comments
 (0)