Skip to content

Commit 56e8469

Browse files
committed
test: let StorageHelpers inherit from TestCase
We don't want to re-define addCleanup ourself so we simply inherit unittest.TestCase and type everything which uses addCleanup.
1 parent f2bed39 commit 56e8469

File tree

1 file changed

+30
-21
lines changed

1 file changed

+30
-21
lines changed

test/common/storagelib.py

+30-21
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@
1919
import os.path
2020
import re
2121
import textwrap
22+
import unittest
23+
from collections.abc import Mapping
2224
from typing import Any
2325

2426
from machine import testvm
2527
from testlib import Browser, Error, MachineCase, wait
2628

29+
DialogValues = Mapping[str, str | bool]
30+
2731

2832
def from_udisks_ascii(codepoints: list[int]) -> str:
2933
return ''.join(map(chr, codepoints[:-1]))
3034

3135

32-
class StorageHelpers:
36+
class StorageHelpers(unittest.TestCase):
3337
"""Mix-in class for using in tests that derive from something else than MachineCase or StorageCase"""
3438
machine: testvm.Machine
3539
browser: Browser
@@ -70,7 +74,7 @@ def add_ram_disk(self, size: int = 50, delay: int | None = None):
7074

7175
return dev
7276

73-
def add_loopback_disk(self, size=50, name=None):
77+
def add_loopback_disk(self, size: int = 50, name: str | None = None) -> str:
7478
"""Add per-test loopback disk
7579
7680
The disk gets removed automatically when the test ends. This is safe for @nondestructive tests.
@@ -103,7 +107,7 @@ def add_loopback_disk(self, size=50, name=None):
103107

104108
return dev
105109

106-
def add_targetd_loopback_disk(self, index, size=50):
110+
def add_targetd_loopback_disk(self, index: str, size: int = 50) -> str:
107111
"""Add per-test loopback device that can be forcefully removed.
108112
"""
109113

@@ -135,12 +139,12 @@ def force_remove_disk(self, device: str) -> None:
135139
# the removal trips up PCP and our usage graphs
136140
self.allow_browser_errors("direct: instance name lookup failed.*")
137141

138-
def addCleanupVG(self, vgname):
142+
def addCleanupVG(self, vgname: str) -> None:
139143
"""Ensure the given VG is removed after the test"""
140144

141145
self.addCleanup(self.machine.execute, f"if [ -d /dev/{vgname} ]; then vgremove --force {vgname}; fi")
142146

143-
def addCleanupMount(self, mount_point):
147+
def addCleanupMount(self, mount_point: str) -> None:
144148
self.addCleanup(self.machine.execute,
145149
f"if mountpoint -q {mount_point}; then umount {mount_point}; fi")
146150

@@ -174,14 +178,16 @@ def dialog_val(self, field: str) -> Any:
174178
else:
175179
return self.browser.val(sel)
176180

177-
def dialog_set_val(self, field, val):
181+
def dialog_set_val(self, field: str, val: str | bool | dict[str, bool]):
178182
sel = self.dialog_field(field)
179183
ftype = self.browser.attr(sel, "data-field-type")
180184
if ftype == "checkbox":
185+
assert isinstance(val, bool)
181186
self.browser.set_checked(sel, val)
182187
elif ftype == "select-spaces":
183-
for label in val:
184-
self.browser.set_checked(f'{sel} :contains("{label}") input', val)
188+
assert isinstance(val, dict)
189+
for label, value in val.items():
190+
self.browser.set_checked(f'{sel} :contains("{label}") input', value)
185191
elif ftype == "size-slider":
186192
self.browser.set_val(sel + " .size-unit select", "1000000")
187193
self.browser.set_input_text(sel + " .size-text input", str(val))
@@ -191,11 +197,13 @@ def dialog_set_val(self, field, val):
191197
elif ftype == "select-radio":
192198
self.browser.click(sel + f" input[data-data='{val}']")
193199
elif ftype == "text-input":
200+
assert isinstance(val, str)
194201
self.browser.set_input_text(sel, val)
195202
elif ftype == "text-input-checked":
196203
if not val:
197204
self.browser.set_checked(sel + " input[type=checkbox]", val=False)
198205
else:
206+
assert isinstance(val, str)
199207
self.browser.set_checked(sel + " input[type=checkbox]", val=True)
200208
self.browser.set_input_text(sel + " [type=text]", val)
201209
elif ftype == "combobox":
@@ -216,7 +224,7 @@ def dialog_combobox_choices(self, field: str) -> None:
216224
def dialog_is_present(self, field: str, label: str) -> bool:
217225
return self.browser.is_present(f'{self.dialog_field(field)} :contains("{label}") input')
218226

219-
def dialog_wait_val(self, field: str, val: str, unit: str | None = None) -> None:
227+
def dialog_wait_val(self, field: str, val: str | bool, unit: str | None = None) -> None:
220228
if unit is None:
221229
unit = "1000000"
222230

@@ -259,13 +267,13 @@ def dialog_wait_close(self) -> None:
259267
with self.browser.wait_timeout(max(self.browser.timeout, 60)):
260268
self.browser.wait_not_present('#dialog')
261269

262-
def dialog_check(self, expect):
270+
def dialog_check(self, expect: Mapping[str, str]) -> bool:
263271
for f in expect:
264272
if not self.dialog_val(f) == expect[f]:
265273
return False
266274
return True
267275

268-
def dialog_set_vals(self, values):
276+
def dialog_set_vals(self, values: DialogValues):
269277
# Sometimes a certain field needs to be set before other
270278
# fields come into existence and thus the order matters that
271279
# we set the fields in. The tests however just give us a
@@ -274,7 +282,7 @@ def dialog_set_vals(self, values):
274282
# can and then starting over. As long as we make progress in
275283
# each iteration, everything is good.
276284
failed = {}
277-
last_error = Exception
285+
last_error = None
278286
for f in values:
279287
try:
280288
self.dialog_set_val(f, values[f])
@@ -284,10 +292,10 @@ def dialog_set_vals(self, values):
284292
if failed:
285293
if len(failed) < len(values):
286294
self.dialog_set_vals(failed)
287-
else:
295+
elif last_error is not None:
288296
raise last_error
289297

290-
def dialog(self, values, expect=None, secondary=False):
298+
def dialog(self, values: DialogValues, expect: DialogValues | None = None, secondary: bool = False):
291299
if expect is None:
292300
expect = {}
293301
self.dialog_wait_open()
@@ -327,7 +335,7 @@ def teardown():
327335
self.dialog_wait_close()
328336
self.retry(setup, check, teardown)
329337

330-
def dialog_apply_with_retry(self, expected_errors=None):
338+
def dialog_apply_with_retry(self, expected_errors: list[str] | None = None) -> None:
331339
def step():
332340
try:
333341
self.dialog_apply()
@@ -405,10 +413,10 @@ def configuration_field(self, dev: str, tab: str, field: str) -> str:
405413
return from_udisks_ascii(entry[1][field])
406414
return ""
407415

408-
def assert_in_configuration(self, dev, tab, field, text):
416+
def assert_in_configuration(self, dev: str, tab: str, field: str, text: str) -> None:
409417
self.assertIn(text, self.configuration_field(dev, tab, field))
410418

411-
def assert_not_in_configuration(self, dev, tab, field, text):
419+
def assert_not_in_configuration(self, dev: str, tab: str, field: str, text: str) -> None:
412420
self.assertNotIn(text, self.configuration_field(dev, tab, field))
413421

414422
def child_configuration_field(self, dev: str, tab: str, field: str) -> str:
@@ -426,10 +434,10 @@ def child_configuration_field(self, dev: str, tab: str, field: str) -> str:
426434
return from_udisks_ascii(entry[1][field])
427435
return ""
428436

429-
def assert_in_child_configuration(self, dev, tab, field, text):
437+
def assert_in_child_configuration(self, dev: str, tab: str, field: str, text: str) -> None:
430438
self.assertIn(text, self.child_configuration_field(dev, tab, field))
431439

432-
def lvol_child_configuration_field(self, lvol, tab, field):
440+
def lvol_child_configuration_field(self, lvol: str, tab: str, field: str) -> str:
433441
udisk_objects = self.udisks_objects()
434442
for path in udisk_objects:
435443
if "org.freedesktop.UDisks2.LogicalVolume" in udisk_objects[path]:
@@ -443,7 +451,7 @@ def lvol_child_configuration_field(self, lvol, tab, field):
443451
return from_udisks_ascii(entry[1][field])
444452
return ""
445453

446-
def assert_in_lvol_child_configuration(self, lvol, tab, field, text):
454+
def assert_in_lvol_child_configuration(self, lvol: str, tab: str, field: str, text: str) -> None:
447455
self.assertIn(text, self.lvol_child_configuration_field(lvol, tab, field))
448456

449457
def setup_systemd_password_agent(self, password):
@@ -607,7 +615,8 @@ def click_card_row(self, title: str, index: int | None = None, name: str | None
607615
# We need to click on a <td> element since that's where the handlers are...
608616
self.browser.click(self.card_row(title, index, name, location) + " td:nth-child(1)")
609617

610-
def card_row_col(self, title, row_index=None, col_index=None, row_name=None, row_location=None):
618+
def card_row_col(self, title: str, row_index: int | None = None, col_index: int | None = None,
619+
row_name: str | None = None, row_location: str | None = None):
611620
return self.card_row(title, row_index, row_name, row_location) + f" td:nth-child({col_index})"
612621

613622
def card_desc(self, card_title: str, desc_title: str) -> str:

0 commit comments

Comments
 (0)