Skip to content

Commit 80ec1b3

Browse files
committed
tests: add the defer fixture
to ease the resource management in some complex tests Signed-off-by: Gaëtan Lehmann <gaetan.lehmann@vates.tech>
1 parent 89449cd commit 80ec1b3

10 files changed

Lines changed: 106 additions & 75 deletions

File tree

conftest.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,18 @@
1313
import lib.config as global_config
1414
from lib import pxe
1515
from lib.common import (
16-
callable_marker,
16+
Defer,
1717
DiskDevName,
1818
HostAddress,
19+
callable_marker,
1920
is_uuid,
2021
prefix_object_name,
21-
setup_formatted_and_mounted_disk,
2222
shortened_nodeid,
23-
teardown_formatted_and_mounted_disk,
2423
vm_image,
2524
wait_for,
2625
)
27-
from lib.netutil import is_ipv6
2826
from lib.host import Host
27+
from lib.netutil import is_ipv6
2928
from lib.pool import Pool
3029
from lib.sr import SR
3130
from lib.vm import VM, vm_cache_key_from_def
@@ -34,8 +33,6 @@
3433
# Import package-scoped fixtures. Although we need to define them in a separate file so that we can
3534
# then import them in individual packages to fix the buggy package scope handling by pytest, we also
3635
# need to import them in the global conftest.py so that they are recognized as fixtures.
37-
from pkgfixtures import formatted_and_mounted_ext4_disk, sr_disk_wiped
38-
3936
from typing import Dict, Generator, Iterable
4037

4138
# Do we cache VMs?
@@ -321,7 +318,7 @@ def xfail_on_xcpng_8_3(host, request):
321318
@pytest.fixture(scope='session')
322319
def host_no_ipv6(host):
323320
if is_ipv6(host.hostname_or_ip):
324-
pytest.skip(f"This test requires an IPv4 XCP-ng")
321+
pytest.skip("This test requires an IPv4 XCP-ng")
325322

326323
@pytest.fixture(scope="session")
327324
def shared_sr(host):
@@ -442,9 +439,7 @@ def vm_ref(request):
442439
logging.info(">> No VM specified on CLI, and no default found in test definition. Using global default.")
443440
ref = 'mini-linux-x86_64-bios'
444441

445-
if is_uuid(ref):
446-
return ref
447-
elif ref.startswith('http'):
442+
if is_uuid(ref) or ref.startswith('http'):
448443
return ref
449444
else:
450445
return vm_image(ref)
@@ -760,3 +755,36 @@ def cifs_iso_sr(host, cifs_iso_device_config):
760755
yield sr
761756
# teardown
762757
sr.forget()
758+
759+
@pytest.fixture()
760+
def defer(request: pytest.FixtureRequest) -> Defer:
761+
"""
762+
A Go-inspired cleanup fixture that registers functions to be executed
763+
after the test completes.
764+
765+
This fixture provides a functional alternative to 'yield' fixtures and
766+
'try...finally' blocks. It is particularly useful for managing resources
767+
that must remain 'alive' during post-mortem debugging (e.g., --pdb), as
768+
registered finalizers only execute after the debugger session exits.
769+
770+
Execution Order:
771+
Finalizers are executed in LIFO (Last-In, First-Out) order. The last
772+
function deferred will be the first one executed during teardown.
773+
774+
Usage:
775+
def test_example(defer):
776+
resource = create_resource()
777+
defer(lambda: resource.cleanup())
778+
779+
# If an assertion fails here, 'resource' is still available
780+
# for inspection in --pdb.
781+
assert resource.is_valid()
782+
783+
Args:
784+
request: The internal pytest request object used to register finalizers.
785+
786+
Returns:
787+
The 'request.addfinalizer' method, allowing for immediate registration
788+
of teardown logic.
789+
"""
790+
return request.addfinalizer

lib/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,3 +367,5 @@ def _param_clear(host, xe_prefix, uuid, param_name):
367367
""" Common implementation for param_clear. """
368368
args = {'uuid': uuid, 'param-name': param_name}
369369
host.xe(f'{xe_prefix}-param-clear', args)
370+
371+
Defer = Callable[[Callable[[], object]], None]

tests/storage/ext/test_ext_sr.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66

77
from lib.commands import SSHCommandFailed
8-
from lib.common import vm_image, wait_for
8+
from lib.common import Defer, vm_image, wait_for
99
from lib.fistpoint import FistPoint
1010
from lib.host import Host
1111
from lib.sr import SR
@@ -88,20 +88,20 @@ def test_snapshot(self, vm_on_ext_sr):
8888
@pytest.mark.small_vm
8989
@pytest.mark.parametrize("vdi_op", ["snapshot", "clone"])
9090
def test_coalesce(
91-
self, storage_test_vm: VM, vdi_on_ext_sr: VDI, vdi_op: CoalesceOperation, request: pytest.FixtureRequest
91+
self, storage_test_vm: VM, vdi_on_ext_sr: VDI, vdi_op: CoalesceOperation, defer: Defer
9292
):
93-
coalesce_integrity(storage_test_vm, vdi_on_ext_sr, vdi_op, request)
93+
coalesce_integrity(storage_test_vm, vdi_on_ext_sr, vdi_op, defer)
9494

9595
@pytest.mark.small_vm
9696
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
97-
def test_xva_export_import(self, vm_on_ext_sr: VM, compression: XVACompression, request: pytest.FixtureRequest):
98-
xva_export_import(vm_on_ext_sr, compression, request)
97+
def test_xva_export_import(self, vm_on_ext_sr: VM, compression: XVACompression, defer: Defer):
98+
xva_export_import(vm_on_ext_sr, compression, defer)
9999

100100
@pytest.mark.small_vm
101101
def test_vdi_export_import(
102-
self, storage_test_vm: VM, ext_sr: SR, image_format: ImageFormat, request: pytest.FixtureRequest
102+
self, storage_test_vm: VM, ext_sr: SR, image_format: ImageFormat, defer: Defer
103103
):
104-
vdi_export_import(storage_test_vm, ext_sr, image_format, request)
104+
vdi_export_import(storage_test_vm, ext_sr, image_format, defer)
105105

106106
# *** tests with reboots (longer tests).
107107

tests/storage/lvm/test_lvm_sr.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66

77
from lib.commands import SSHCommandFailed
8-
from lib.common import vm_image, wait_for
8+
from lib.common import Defer, vm_image, wait_for
99
from lib.fistpoint import FistPoint
1010
from lib.host import Host
1111
from lib.sr import SR
@@ -141,20 +141,20 @@ def test_failing_resize_on_inflate_after_setSizePhys(self, host, lvm_sr, vm_on_l
141141
@pytest.mark.small_vm
142142
@pytest.mark.parametrize("vdi_op", ["snapshot", "clone"])
143143
def test_coalesce(
144-
self, storage_test_vm: VM, vdi_on_lvm_sr: VDI, vdi_op: CoalesceOperation, request: pytest.FixtureRequest
144+
self, storage_test_vm: VM, vdi_on_lvm_sr: VDI, vdi_op: CoalesceOperation, defer: Defer
145145
):
146-
coalesce_integrity(storage_test_vm, vdi_on_lvm_sr, vdi_op, request)
146+
coalesce_integrity(storage_test_vm, vdi_on_lvm_sr, vdi_op, defer)
147147

148148
@pytest.mark.small_vm
149149
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
150-
def test_xva_export_import(self, vm_on_lvm_sr: VM, compression: XVACompression, request: pytest.FixtureRequest):
151-
xva_export_import(vm_on_lvm_sr, compression, request)
150+
def test_xva_export_import(self, vm_on_lvm_sr: VM, compression: XVACompression, defer: Defer):
151+
xva_export_import(vm_on_lvm_sr, compression, defer)
152152

153153
@pytest.mark.small_vm
154154
def test_vdi_export_import(
155-
self, storage_test_vm: VM, lvm_sr: SR, image_format: ImageFormat, request: pytest.FixtureRequest
155+
self, storage_test_vm: VM, lvm_sr: SR, image_format: ImageFormat, defer: Defer
156156
):
157-
vdi_export_import(storage_test_vm, lvm_sr, image_format, request)
157+
vdi_export_import(storage_test_vm, lvm_sr, image_format, defer)
158158

159159
# *** tests with reboots (longer tests).
160160

tests/storage/lvmoiscsi/test_lvmoiscsi_sr.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from lib.common import vm_image, wait_for
3+
from lib.common import Defer, vm_image, wait_for
44
from lib.sr import SR
55
from lib.vdi import VDI
66
from lib.vm import VM
@@ -69,22 +69,22 @@ def test_coalesce(
6969
storage_test_vm: 'VM',
7070
vdi_on_lvmoiscsi_sr: 'VDI',
7171
vdi_op: CoalesceOperation,
72-
request: pytest.FixtureRequest,
72+
defer: Defer,
7373
):
74-
coalesce_integrity(storage_test_vm, vdi_on_lvmoiscsi_sr, vdi_op, request)
74+
coalesce_integrity(storage_test_vm, vdi_on_lvmoiscsi_sr, vdi_op, defer)
7575

7676
@pytest.mark.small_vm
7777
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
7878
def test_xva_export_import(
79-
self, vm_on_lvmoiscsi_sr: VM, compression: XVACompression, request: pytest.FixtureRequest
79+
self, vm_on_lvmoiscsi_sr: VM, compression: XVACompression, defer: Defer
8080
):
81-
xva_export_import(vm_on_lvmoiscsi_sr, compression, request)
81+
xva_export_import(vm_on_lvmoiscsi_sr, compression, defer)
8282

8383
@pytest.mark.small_vm
8484
def test_vdi_export_import(
85-
self, storage_test_vm: VM, lvmoiscsi_sr: SR, image_format: ImageFormat, request: pytest.FixtureRequest
85+
self, storage_test_vm: VM, lvmoiscsi_sr: SR, image_format: ImageFormat, defer: Defer
8686
):
87-
vdi_export_import(storage_test_vm, lvmoiscsi_sr, image_format, request)
87+
vdi_export_import(storage_test_vm, lvmoiscsi_sr, image_format, defer)
8888

8989
# *** tests with reboots (longer tests).
9090

tests/storage/nfs/test_nfs_sr.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44

55
from lib.commands import SSHCommandFailed
6-
from lib.common import vm_image, wait_for
6+
from lib.common import Defer, vm_image, wait_for
77
from lib.sr import SR
88
from lib.vdi import VDI
99
from lib.vm import VM
@@ -116,24 +116,24 @@ def test_snapshot(self, dispatch_nfs):
116116
@pytest.mark.parametrize('dispatch_nfs', ['vdi_on_nfs_sr', 'vdi_on_nfs4_sr'], indirect=True)
117117
@pytest.mark.parametrize('vdi_op', ['snapshot', 'clone'])
118118
def test_coalesce(
119-
self, storage_test_vm: VM, dispatch_nfs: VDI, vdi_op: CoalesceOperation, request: pytest.FixtureRequest
119+
self, storage_test_vm: VM, dispatch_nfs: VDI, vdi_op: CoalesceOperation, defer: Defer
120120
):
121-
coalesce_integrity(storage_test_vm, dispatch_nfs, vdi_op, request)
121+
coalesce_integrity(storage_test_vm, dispatch_nfs, vdi_op, defer)
122122

123123
@pytest.mark.small_vm
124124
# Make sure this fixture is called before the parametrized one
125125
@pytest.mark.usefixtures('vm_ref')
126126
@pytest.mark.parametrize('dispatch_nfs', ['vm_on_nfs_sr', 'vm_on_nfs4_sr'], indirect=True)
127127
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
128-
def test_xva_export_import(self, dispatch_nfs: VM, compression: XVACompression, request: pytest.FixtureRequest):
129-
xva_export_import(dispatch_nfs, compression, request)
128+
def test_xva_export_import(self, dispatch_nfs: VM, compression: XVACompression, defer: Defer):
129+
xva_export_import(dispatch_nfs, compression, defer)
130130

131131
@pytest.mark.small_vm
132132
@pytest.mark.parametrize('dispatch_nfs', ['nfs_sr', 'nfs4_sr'], indirect=True)
133133
def test_vdi_export_import(
134-
self, storage_test_vm: VM, dispatch_nfs: SR, image_format: ImageFormat, request: pytest.FixtureRequest
134+
self, storage_test_vm: VM, dispatch_nfs: SR, image_format: ImageFormat, defer: Defer
135135
):
136-
vdi_export_import(storage_test_vm, dispatch_nfs, image_format, request)
136+
vdi_export_import(storage_test_vm, dispatch_nfs, image_format, defer)
137137

138138
# *** tests with reboots (longer tests).
139139

tests/storage/storage.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import logging
66

7+
from conftest import Defer
78
from lib.commands import SSHCommandFailed
89
from lib.common import GiB, strtobool, wait_for, wait_for_not
910
from lib.host import Host
@@ -181,9 +182,9 @@ def install_randstream(vm: 'VM'):
181182

182183
CoalesceOperation = Literal['snapshot', 'clone']
183184

184-
def coalesce_integrity(vm: VM, vdi: VDI, vdi_op: CoalesceOperation, request: pytest.FixtureRequest):
185+
def coalesce_integrity(vm: VM, vdi: VDI, vdi_op: CoalesceOperation, defer: Defer):
185186
vbd = vm.connect_vdi(vdi)
186-
request.addfinalizer(lambda: vm.disconnect_vdi(vdi))
187+
defer(lambda: vm.disconnect_vdi(vdi))
187188

188189
dev = f'/dev/{vbd.param_get("device")}'
189190
vm.ssh(f"randstream generate -v {dev}")
@@ -192,7 +193,7 @@ def coalesce_integrity(vm: VM, vdi: VDI, vdi_op: CoalesceOperation, request: pyt
192193
match vdi_op:
193194
case 'clone': new_vdi = vdi.clone()
194195
case 'snapshot': new_vdi = vdi.snapshot()
195-
request.addfinalizer(lambda: new_vdi.destroy() if new_vdi is not None else None)
196+
defer(lambda: new_vdi.destroy() if new_vdi is not None else None)
196197

197198
vm.ssh(f"randstream generate -v --seed 1 --size 128Mi {dev}")
198199
vm.ssh(f"randstream validate -v --expected-checksum ad2ca9af {dev}")
@@ -201,7 +202,7 @@ def coalesce_integrity(vm: VM, vdi: VDI, vdi_op: CoalesceOperation, request: pyt
201202

202203
XVACompression = Literal['none', 'gzip', 'zstd']
203204

204-
def xva_export_import(vm: VM, compression: XVACompression, request: pytest.FixtureRequest):
205+
def xva_export_import(vm: VM, compression: XVACompression, defer: Defer):
205206
# The tests using this function are using specific fixtures to create the VM on the expected SR
206207
# In consequence, we can't use the storage_test_vm, so we have to start the VM explicitly and install randstream
207208
vm.start()
@@ -214,25 +215,25 @@ def xva_export_import(vm: VM, compression: XVACompression, request: pytest.Fixtu
214215
vm.shutdown(verify=True)
215216

216217
xva_path = f'/tmp/{vm.uuid}.xva'
217-
request.addfinalizer(lambda: vm.host.ssh(f'rm -f {xva_path}'))
218+
defer(lambda: vm.host.ssh(f'rm -f {xva_path}'))
218219
vm.export(xva_path, compression)
219220
# check that the zero blocks are not part of the result. Most of the data is from the random stream, so
220221
# compression has little effect. We just check the result is between 500 and 700 MiB
221222
size_mb = int(vm.host.ssh(f'du -sm --apparent-size {xva_path}').split()[0])
222223
assert 500 < size_mb < 700, f"unexpected xva size: {size_mb}"
223224

224225
imported_vm = vm.host.import_vm(xva_path, vm.vdis[0].sr.uuid)
225-
request.addfinalizer(lambda: imported_vm.destroy())
226+
defer(lambda: imported_vm.destroy())
226227
imported_vm.start()
227228
imported_vm.wait_for_vm_running_and_ssh_up()
228229
imported_vm.ssh("randstream validate -v --expected-checksum 24e905d6 /root/data")
229230

230-
def vdi_export_import(vm: VM, sr: SR, image_format: ImageFormat, request: pytest.FixtureRequest):
231+
def vdi_export_import(vm: VM, sr: SR, image_format: ImageFormat, defer: Defer):
231232
vdi_src = sr.create_vdi(image_format=image_format)
232-
request.addfinalizer(lambda: vdi_src.destroy() if vdi_src is not None else None)
233+
defer(lambda: vdi_src.destroy() if vdi_src is not None else None)
233234

234235
vbd = vm.connect_vdi(vdi_src)
235-
request.addfinalizer(lambda: vm.disconnect_vdi(vdi_src) if vdi_src is not None else None)
236+
defer(lambda: vm.disconnect_vdi(vdi_src) if vdi_src is not None else None)
236237
dev = f'/dev/{vbd.param_get("device")}'
237238

238239
# generate 2 blocks of data of 200MiB, at position 0 and at position 500MiB
@@ -244,7 +245,7 @@ def vdi_export_import(vm: VM, sr: SR, image_format: ImageFormat, request: pytest
244245
vm.disconnect_vdi(vdi_src)
245246

246247
image_path = f'/tmp/{vdi_src.uuid}.{image_format}'
247-
request.addfinalizer(lambda: vm.host.ssh(f'rm -f {image_path}'))
248+
defer(lambda: vm.host.ssh(f'rm -f {image_path}'))
248249

249250
vm.host.xe('vdi-export', {'uuid': vdi_src.uuid, 'filename': image_path, 'format': image_format})
250251
vdi_src = vdi_src.destroy()
@@ -253,11 +254,11 @@ def vdi_export_import(vm: VM, sr: SR, image_format: ImageFormat, request: pytest
253254
size_mb = int(vm.host.ssh(f'du -sm --apparent-size {image_path}').split()[0])
254255
assert 400 < size_mb < 410, f"unexpected image size: {size_mb}"
255256
vdi_dest = sr.create_vdi(image_format=image_format)
256-
request.addfinalizer(lambda: vdi_dest.destroy())
257+
defer(lambda: vdi_dest.destroy())
257258

258259
vm.host.xe('vdi-import', {'uuid': vdi_dest.uuid, 'filename': image_path, 'format': image_format})
259260
vm.connect_vdi(vdi_dest, 'xvdb')
260-
request.addfinalizer(lambda: vm.disconnect_vdi(vdi_dest))
261+
defer(lambda: vm.disconnect_vdi(vdi_dest))
261262

262263
vm.ssh(f"randstream validate -v --size 200MiB --expected-checksum c6310c52 {dev}")
263264
vm.ssh(f"randstream validate -v --position 500MiB --size 200MiB --expected-checksum 1cb4218e {dev}")

tests/storage/xfs/test_xfs_sr.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import time
77

88
from lib.commands import SSHCommandFailed
9-
from lib.common import vm_image, wait_for
9+
from lib.common import Defer, vm_image, wait_for
1010
from lib.host import Host
1111
from lib.sr import SR
1212
from lib.vdi import VDI
@@ -105,20 +105,20 @@ def test_snapshot(self, vm_on_xfs_sr):
105105
@pytest.mark.small_vm
106106
@pytest.mark.parametrize("vdi_op", ["snapshot", "clone"])
107107
def test_coalesce(
108-
self, storage_test_vm: VM, vdi_on_xfs_sr: VDI, vdi_op: CoalesceOperation, request: pytest.FixtureRequest
108+
self, storage_test_vm: VM, vdi_on_xfs_sr: VDI, vdi_op: CoalesceOperation, defer: Defer
109109
):
110-
coalesce_integrity(storage_test_vm, vdi_on_xfs_sr, vdi_op, request)
110+
coalesce_integrity(storage_test_vm, vdi_on_xfs_sr, vdi_op, defer)
111111

112112
@pytest.mark.small_vm
113113
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
114-
def test_xva_export_import(self, vm_on_xfs_sr: VM, compression: XVACompression, request: pytest.FixtureRequest):
115-
xva_export_import(vm_on_xfs_sr, compression, request)
114+
def test_xva_export_import(self, vm_on_xfs_sr: VM, compression: XVACompression, defer: Defer):
115+
xva_export_import(vm_on_xfs_sr, compression, defer)
116116

117117
@pytest.mark.small_vm
118118
def test_vdi_export_import(
119-
self, storage_test_vm: VM, xfs_sr: SR, image_format: ImageFormat, request: pytest.FixtureRequest
119+
self, storage_test_vm: VM, xfs_sr: SR, image_format: ImageFormat, defer: Defer
120120
):
121-
vdi_export_import(storage_test_vm, xfs_sr, image_format, request)
121+
vdi_export_import(storage_test_vm, xfs_sr, image_format, defer)
122122

123123
# *** tests with reboots (longer tests).
124124

0 commit comments

Comments
 (0)