Skip to content

Commit 76b60de

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 76b60de

10 files changed

Lines changed: 102 additions & 66 deletions

File tree

conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import lib.config as global_config
1414
from lib import pxe
1515
from lib.common import (
16+
Defer,
1617
callable_marker,
1718
DiskDevName,
1819
HostAddress,
@@ -760,3 +761,36 @@ def cifs_iso_sr(host, cifs_iso_device_config):
760761
yield sr
761762
# teardown
762763
sr.forget()
764+
765+
@pytest.fixture()
766+
def defer(request: pytest.FixtureRequest) -> Defer:
767+
"""
768+
A Go-inspired cleanup fixture that registers functions to be executed
769+
after the test completes.
770+
771+
This fixture provides a functional alternative to 'yield' fixtures and
772+
'try...finally' blocks. It is particularly useful for managing resources
773+
that must remain 'alive' during post-mortem debugging (e.g., --pdb), as
774+
registered finalizers only execute after the debugger session exits.
775+
776+
Execution Order:
777+
Finalizers are executed in LIFO (Last-In, First-Out) order. The last
778+
function deferred will be the first one executed during teardown.
779+
780+
Usage:
781+
def test_example(defer):
782+
resource = create_resource()
783+
defer(lambda: resource.cleanup())
784+
785+
# If an assertion fails here, 'resource' is still available
786+
# for inspection in --pdb.
787+
assert resource.is_valid()
788+
789+
Args:
790+
request: The internal pytest request object used to register finalizers.
791+
792+
Returns:
793+
The 'request.addfinalizer' method, allowing for immediate registration
794+
of teardown logic.
795+
"""
796+
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 & 13 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 GiB, strtobool, wait_for, wait_for_not
8+
from lib.common import Defer, GiB, strtobool, wait_for, wait_for_not
99
from lib.host import Host
1010
from lib.sr import SR
1111
from lib.vdi import VDI, ImageFormat
@@ -181,9 +181,9 @@ def install_randstream(vm: 'VM'):
181181

182182
CoalesceOperation = Literal['snapshot', 'clone']
183183

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

188188
dev = f'/dev/{vbd.param_get("device")}'
189189
vm.ssh(f"randstream generate -v {dev}")
@@ -192,7 +192,7 @@ def coalesce_integrity(vm: VM, vdi: VDI, vdi_op: CoalesceOperation, request: pyt
192192
match vdi_op:
193193
case 'clone': new_vdi = vdi.clone()
194194
case 'snapshot': new_vdi = vdi.snapshot()
195-
request.addfinalizer(lambda: new_vdi.destroy() if new_vdi is not None else None)
195+
defer(lambda: new_vdi.destroy() if new_vdi is not None else None)
196196

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

202202
XVACompression = Literal['none', 'gzip', 'zstd']
203203

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

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

224224
imported_vm = vm.host.import_vm(xva_path, vm.vdis[0].sr.uuid)
225-
request.addfinalizer(lambda: imported_vm.destroy())
225+
defer(lambda: imported_vm.destroy())
226226
imported_vm.start()
227227
imported_vm.wait_for_vm_running_and_ssh_up()
228228
imported_vm.ssh("randstream validate -v --expected-checksum 24e905d6 /root/data")
229229

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

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

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

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

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

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

262262
vm.ssh(f"randstream validate -v --size 200MiB --expected-checksum c6310c52 {dev}")
263263
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

tests/storage/zfs/test_zfs_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.sr import SR
1111
from lib.vdi import VDI
1212
from lib.vm import VM
@@ -107,20 +107,20 @@ def test_snapshot(self, vm_on_zfs_sr):
107107
@pytest.mark.small_vm
108108
@pytest.mark.parametrize("vdi_op", ["snapshot", "clone"])
109109
def test_coalesce(
110-
self, storage_test_vm: VM, vdi_on_zfs_sr: VDI, vdi_op: CoalesceOperation, request: pytest.FixtureRequest
110+
self, storage_test_vm: VM, vdi_on_zfs_sr: VDI, vdi_op: CoalesceOperation, defer: Defer
111111
):
112-
coalesce_integrity(storage_test_vm, vdi_on_zfs_sr, vdi_op, request)
112+
coalesce_integrity(storage_test_vm, vdi_on_zfs_sr, vdi_op, defer)
113113

114114
@pytest.mark.small_vm
115115
@pytest.mark.parametrize("compression", ["none", "gzip", "zstd"])
116-
def test_xva_export_import(self, vm_on_zfs_sr: VM, compression: XVACompression, request: pytest.FixtureRequest):
117-
xva_export_import(vm_on_zfs_sr, compression, request)
116+
def test_xva_export_import(self, vm_on_zfs_sr: VM, compression: XVACompression, defer: Defer):
117+
xva_export_import(vm_on_zfs_sr, compression, defer)
118118

119119
@pytest.mark.small_vm
120120
def test_vdi_export_import(
121-
self, storage_test_vm: VM, zfs_sr: SR, image_format: ImageFormat, request: pytest.FixtureRequest
121+
self, storage_test_vm: VM, zfs_sr: SR, image_format: ImageFormat, defer: Defer
122122
):
123-
vdi_export_import(storage_test_vm, zfs_sr, image_format, request)
123+
vdi_export_import(storage_test_vm, zfs_sr, image_format, defer)
124124

125125
# *** tests with reboots (longer tests).
126126

0 commit comments

Comments
 (0)