Skip to content

Commit 8e33ad2

Browse files
committed
test: type storagelib partially
Storagelib is shared between projects so useful to have typed. Typing this mixin is a bit tricky as the documented approach is to use a Protocol class which provides the required "borrowed" functions/properties. Sadly this requires a bit of unavoidable duplication of typing.
1 parent 4e028a3 commit 8e33ad2

File tree

1 file changed

+57
-42
lines changed

1 file changed

+57
-42
lines changed

test/common/storagelib.py

+57-42
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,33 @@
1919
import os.path
2020
import re
2121
import textwrap
22+
from typing import Any, Protocol
2223

23-
from testlib import Error, MachineCase, wait
24+
from machine import testvm
25+
from testlib import Browser, Error, MachineCase, wait
2426

2527

26-
def from_udisks_ascii(codepoints):
28+
def from_udisks_ascii(codepoints: list[int]) -> str:
2729
return ''.join(map(chr, codepoints[:-1]))
2830

2931

30-
class StorageHelpers:
32+
class StorageMachineProtocol(Protocol):
33+
"""Classes that StorageHelpers is mixed into need to implement this"""
34+
35+
machine: testvm.Machine
36+
browser: Browser
37+
38+
def allow_browser_errors(self, *patterns: str) -> None: ...
39+
40+
41+
class StorageHelpers(StorageMachineProtocol):
3142
"""Mix-in class for using in tests that derive from something else than MachineCase or StorageCase"""
43+
machine: testvm.Machine
44+
browser: Browser
45+
46+
def allow_browser_errors(self, *patterns: str) -> None: ...
3247

33-
def inode(self, f):
48+
def inode(self, f: str) -> str:
3449
return self.machine.execute("stat -L '%s' -c %%i" % f)
3550

3651
def retry(self, setup, check, teardown):
@@ -45,7 +60,7 @@ def step():
4560

4661
self.browser.wait(step)
4762

48-
def add_ram_disk(self, size=50, delay=None):
63+
def add_ram_disk(self, size: int = 50, delay: int | None = None):
4964
"""Add per-test RAM disk
5065
5166
The disk gets removed automatically when the test ends. This is safe for @nondestructive tests.
@@ -119,7 +134,7 @@ def add_targetd_loopback_disk(self, index, size=50):
119134
raise Error("Device not found")
120135
return dev
121136

122-
def force_remove_disk(self, device):
137+
def force_remove_disk(self, device: str) -> None:
123138
"""Act like the given device gets physically removed.
124139
125140
This circumvents all the normal EBUSY failures, and thus can be used for testing
@@ -140,22 +155,22 @@ def addCleanupMount(self, mount_point):
140155

141156
# Dialogs
142157

143-
def dialog_wait_open(self):
158+
def dialog_wait_open(self) -> None:
144159
self.browser.wait_visible('#dialog')
145160

146-
def dialog_wait_alert(self, text1, text2=None):
161+
def dialog_wait_alert(self, text1: str, text2: str | None = None) -> None:
147162
def has_alert_title():
148163
t = self.browser.text('#dialog .pf-v5-c-alert__title')
149164
return text1 in t or (text2 is not None and text2 in t)
150165
self.browser.wait(has_alert_title)
151166

152-
def dialog_wait_title(self, text):
167+
def dialog_wait_title(self, text: str) -> None:
153168
self.browser.wait_in_text('#dialog .pf-v5-c-modal-box__title', text)
154169

155-
def dialog_field(self, field):
170+
def dialog_field(self, field: str) -> str:
156171
return f'#dialog [data-field="{field}"]'
157172

158-
def dialog_val(self, field):
173+
def dialog_val(self, field: str) -> Any:
159174
sel = self.dialog_field(field)
160175
ftype = self.browser.attr(sel, "data-field-type")
161176
if ftype == "text-input-checked":
@@ -198,7 +213,7 @@ def dialog_set_val(self, field, val):
198213
else:
199214
self.browser.set_val(sel, val)
200215

201-
def dialog_combobox_choices(self, field):
216+
def dialog_combobox_choices(self, field: str) -> None:
202217
return self.browser.call_js_func("""(function (sel) {
203218
var lis = ph_find(sel).querySelectorAll('li');
204219
var result = [];
@@ -207,10 +222,10 @@ def dialog_combobox_choices(self, field):
207222
return result;
208223
})""", self.dialog_field(field))
209224

210-
def dialog_is_present(self, field, label):
225+
def dialog_is_present(self, field: str, label: str) -> bool:
211226
return self.browser.is_present(f'{self.dialog_field(field)} :contains("{label}") input')
212227

213-
def dialog_wait_val(self, field, val, unit=None):
228+
def dialog_wait_val(self, field: str, val: str, unit: str | None = None) -> None:
214229
if unit is None:
215230
unit = "1000000"
216231

@@ -226,29 +241,29 @@ def dialog_wait_val(self, field, val, unit=None):
226241
else:
227242
self.browser.wait_val(sel, val)
228243

229-
def dialog_wait_error(self, field, val):
244+
def dialog_wait_error(self, field: str, val: str) -> None:
230245
# XXX - allow for more than one error
231246
self.browser.wait_in_text('#dialog .pf-v5-c-form__helper-text .pf-m-error', val)
232247

233-
def dialog_wait_not_present(self, field):
248+
def dialog_wait_not_present(self, field: str) -> None:
234249
self.browser.wait_not_present(self.dialog_field(field))
235250

236-
def dialog_wait_apply_enabled(self):
251+
def dialog_wait_apply_enabled(self) -> None:
237252
self.browser.wait_attr('#dialog button.apply:nth-of-type(1)', "disabled", None)
238253

239-
def dialog_wait_apply_disabled(self):
254+
def dialog_wait_apply_disabled(self) -> None:
240255
self.browser.wait_visible('#dialog button.apply:nth-of-type(1)[disabled]')
241256

242-
def dialog_apply(self):
257+
def dialog_apply(self) -> None:
243258
self.browser.click('#dialog button.apply:nth-of-type(1)')
244259

245-
def dialog_apply_secondary(self):
260+
def dialog_apply_secondary(self) -> None:
246261
self.browser.click('#dialog button.apply:nth-of-type(2)')
247262

248-
def dialog_cancel(self):
263+
def dialog_cancel(self) -> None:
249264
self.browser.click('#dialog button.cancel')
250265

251-
def dialog_wait_close(self):
266+
def dialog_wait_close(self) -> None:
252267
# file system operations often take longer than 10s
253268
with self.browser.wait_timeout(max(self.browser.timeout, 60)):
254269
self.browser.wait_not_present('#dialog')
@@ -386,7 +401,7 @@ def udisks_objects(self):
386401
"org.freedesktop.DBus.ObjectManager",
387402
"GetManagedObjects", "", [])))""")]))
388403

389-
def configuration_field(self, dev, tab, field):
404+
def configuration_field(self, dev: str, tab: str, field: str) -> str:
390405
managerObjects = self.udisks_objects()
391406
for path in managerObjects:
392407
if "org.freedesktop.UDisks2.Block" in managerObjects[path]:
@@ -405,7 +420,7 @@ def assert_in_configuration(self, dev, tab, field, text):
405420
def assert_not_in_configuration(self, dev, tab, field, text):
406421
self.assertNotIn(text, self.configuration_field(dev, tab, field))
407422

408-
def child_configuration_field(self, dev, tab, field):
423+
def child_configuration_field(self, dev: str, tab: str, field: str) -> str:
409424
udisks_objects = self.udisks_objects()
410425
for path in udisks_objects:
411426
if "org.freedesktop.UDisks2.Encrypted" in udisks_objects[path]:
@@ -579,16 +594,16 @@ def encrypt_root(self, passphrase):
579594

580595
# Cards and tables
581596

582-
def card(self, title):
597+
def card(self, title: str) -> str:
583598
return f"[data-test-card-title='{title}']"
584599

585-
def card_parent_link(self):
600+
def card_parent_link(self) -> str:
586601
return ".pf-v5-c-breadcrumb__item:nth-last-child(2) > a"
587602

588-
def card_header(self, title):
603+
def card_header(self, title: str) -> str:
589604
return self.card(title) + " .pf-v5-c-card__header"
590605

591-
def card_row(self, title, index=None, name=None, location=None):
606+
def card_row(self, title: str, index: int | None = None, name: str | None = None, location: str | None = None) -> str:
592607
if index is not None:
593608
return self.card(title) + f" tbody tr:nth-child({index})"
594609
elif name is not None:
@@ -597,58 +612,58 @@ def card_row(self, title, index=None, name=None, location=None):
597612
else:
598613
return self.card(title) + f" tbody [data-test-row-location='{location}']"
599614

600-
def click_card_row(self, title, index=None, name=None, location=None):
615+
def click_card_row(self, title: str, index: int | None = None, name: str | None = None, location: str | None = None) -> None:
601616
# We need to click on a <td> element since that's where the handlers are...
602617
self.browser.click(self.card_row(title, index, name, location) + " td:nth-child(1)")
603618

604619
def card_row_col(self, title, row_index=None, col_index=None, row_name=None, row_location=None):
605620
return self.card_row(title, row_index, row_name, row_location) + f" td:nth-child({col_index})"
606621

607-
def card_desc(self, card_title, desc_title):
622+
def card_desc(self, card_title: str, desc_title: str) -> str:
608623
return self.card(card_title) + f" [data-test-desc-title='{desc_title}'] [data-test-value=true]"
609624

610-
def card_desc_action(self, card_title, desc_title):
625+
def card_desc_action(self, card_title: str, desc_title: str) -> str:
611626
return self.card(card_title) + f" [data-test-desc-title='{desc_title}'] [data-test-action=true] button"
612627

613-
def card_button(self, card_title, button_title):
628+
def card_button(self, card_title: str, button_title: str) -> str:
614629
return self.card(card_title) + f" button:contains('{button_title}')"
615630

616-
def dropdown_toggle(self, parent):
631+
def dropdown_toggle(self, parent: str) -> str:
617632
return parent + " .pf-v5-c-menu-toggle"
618633

619-
def dropdown_action(self, parent, title):
634+
def dropdown_action(self, parent: str, title: str) -> str:
620635
return parent + f" .pf-v5-c-menu button:contains('{title}')"
621636

622-
def dropdown_description(self, parent, title):
637+
def dropdown_description(self, parent: str, title: str) -> str:
623638
return parent + f" .pf-v5-c-menu button:contains('{title}') .pf-v5-c-menu__item-description"
624639

625-
def click_dropdown(self, parent, title):
640+
def click_dropdown(self, parent: str, title: str) -> None:
626641
self.browser.click(self.dropdown_toggle(parent))
627642
self.browser.click(self.dropdown_action(parent, title))
628643

629-
def click_card_dropdown(self, card_title, button_title):
644+
def click_card_dropdown(self, card_title: str, button_title: str) -> None:
630645
self.click_dropdown(self.card_header(card_title), button_title)
631646

632-
def click_devices_dropdown(self, title):
647+
def click_devices_dropdown(self, title: str) -> None:
633648
self.click_card_dropdown("Storage", title)
634649

635-
def check_dropdown_action_disabled(self, parent, title, expected_text):
650+
def check_dropdown_action_disabled(self, parent: str, title: str, expected_text: str) -> None:
636651
self.browser.click(self.dropdown_toggle(parent))
637652
self.browser.wait_visible(self.dropdown_action(parent, title) + "[disabled]")
638653
self.browser.wait_text(self.dropdown_description(parent, title), expected_text)
639654
self.browser.click(self.dropdown_toggle(parent))
640655

641-
def wait_mounted(self, card_title):
656+
def wait_mounted(self, card_title: str) -> None:
642657
with self.browser.wait_timeout(30):
643658
self.browser.wait_not_in_text(self.card_desc(card_title, "Mount point"),
644659
"The filesystem is not mounted.")
645660

646-
def wait_not_mounted(self, card_title):
661+
def wait_not_mounted(self, card_title: str) -> None:
647662
with self.browser.wait_timeout(30):
648663
self.browser.wait_in_text(self.card_desc(card_title, "Mount point"),
649664
"The filesystem is not mounted.")
650665

651-
def wait_card_button_disabled(self, card_title, button_title):
666+
def wait_card_button_disabled(self, card_title: str, button_title: str) -> None:
652667
with self.browser.wait_timeout(30):
653668
self.browser.wait_visible(self.card_button(card_title, button_title) + ":disabled")
654669

0 commit comments

Comments
 (0)