19
19
import os .path
20
20
import re
21
21
import textwrap
22
+ import unittest
23
+ from collections .abc import Mapping
22
24
from typing import Any
23
25
24
26
from machine import testvm
25
27
from testlib import Browser , Error , MachineCase , wait
26
28
29
+ DialogValues = Mapping [str , str | bool ]
30
+
27
31
28
32
def from_udisks_ascii (codepoints : list [int ]) -> str :
29
33
return '' .join (map (chr , codepoints [:- 1 ]))
30
34
31
35
32
- class StorageHelpers :
36
+ class StorageHelpers ( unittest . TestCase ) :
33
37
"""Mix-in class for using in tests that derive from something else than MachineCase or StorageCase"""
34
38
machine : testvm .Machine
35
39
browser : Browser
@@ -70,7 +74,7 @@ def add_ram_disk(self, size: int = 50, delay: int | None = None):
70
74
71
75
return dev
72
76
73
- def add_loopback_disk (self , size = 50 , name = None ):
77
+ def add_loopback_disk (self , size : int = 50 , name : str | None = None ) -> str :
74
78
"""Add per-test loopback disk
75
79
76
80
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):
103
107
104
108
return dev
105
109
106
- def add_targetd_loopback_disk (self , index , size = 50 ):
110
+ def add_targetd_loopback_disk (self , index : str , size : int = 50 ) -> str :
107
111
"""Add per-test loopback device that can be forcefully removed.
108
112
"""
109
113
@@ -135,12 +139,12 @@ def force_remove_disk(self, device: str) -> None:
135
139
# the removal trips up PCP and our usage graphs
136
140
self .allow_browser_errors ("direct: instance name lookup failed.*" )
137
141
138
- def addCleanupVG (self , vgname ) :
142
+ def addCleanupVG (self , vgname : str ) -> None :
139
143
"""Ensure the given VG is removed after the test"""
140
144
141
145
self .addCleanup (self .machine .execute , f"if [ -d /dev/{ vgname } ]; then vgremove --force { vgname } ; fi" )
142
146
143
- def addCleanupMount (self , mount_point ) :
147
+ def addCleanupMount (self , mount_point : str ) -> None :
144
148
self .addCleanup (self .machine .execute ,
145
149
f"if mountpoint -q { mount_point } ; then umount { mount_point } ; fi" )
146
150
@@ -174,14 +178,16 @@ def dialog_val(self, field: str) -> Any:
174
178
else :
175
179
return self .browser .val (sel )
176
180
177
- def dialog_set_val (self , field , val ):
181
+ def dialog_set_val (self , field : str , val : str | bool | dict [ str , bool ] ):
178
182
sel = self .dialog_field (field )
179
183
ftype = self .browser .attr (sel , "data-field-type" )
180
184
if ftype == "checkbox" :
185
+ assert isinstance (val , bool )
181
186
self .browser .set_checked (sel , val )
182
187
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 )
185
191
elif ftype == "size-slider" :
186
192
self .browser .set_val (sel + " .size-unit select" , "1000000" )
187
193
self .browser .set_input_text (sel + " .size-text input" , str (val ))
@@ -191,11 +197,13 @@ def dialog_set_val(self, field, val):
191
197
elif ftype == "select-radio" :
192
198
self .browser .click (sel + f" input[data-data='{ val } ']" )
193
199
elif ftype == "text-input" :
200
+ assert isinstance (val , str )
194
201
self .browser .set_input_text (sel , val )
195
202
elif ftype == "text-input-checked" :
196
203
if not val :
197
204
self .browser .set_checked (sel + " input[type=checkbox]" , val = False )
198
205
else :
206
+ assert isinstance (val , str )
199
207
self .browser .set_checked (sel + " input[type=checkbox]" , val = True )
200
208
self .browser .set_input_text (sel + " [type=text]" , val )
201
209
elif ftype == "combobox" :
@@ -216,7 +224,7 @@ def dialog_combobox_choices(self, field: str) -> None:
216
224
def dialog_is_present (self , field : str , label : str ) -> bool :
217
225
return self .browser .is_present (f'{ self .dialog_field (field )} :contains("{ label } ") input' )
218
226
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 :
220
228
if unit is None :
221
229
unit = "1000000"
222
230
@@ -259,13 +267,13 @@ def dialog_wait_close(self) -> None:
259
267
with self .browser .wait_timeout (max (self .browser .timeout , 60 )):
260
268
self .browser .wait_not_present ('#dialog' )
261
269
262
- def dialog_check (self , expect ) :
270
+ def dialog_check (self , expect : Mapping [ str , str ]) -> bool :
263
271
for f in expect :
264
272
if not self .dialog_val (f ) == expect [f ]:
265
273
return False
266
274
return True
267
275
268
- def dialog_set_vals (self , values ):
276
+ def dialog_set_vals (self , values : DialogValues ):
269
277
# Sometimes a certain field needs to be set before other
270
278
# fields come into existence and thus the order matters that
271
279
# we set the fields in. The tests however just give us a
@@ -274,7 +282,7 @@ def dialog_set_vals(self, values):
274
282
# can and then starting over. As long as we make progress in
275
283
# each iteration, everything is good.
276
284
failed = {}
277
- last_error = Exception
285
+ last_error = None
278
286
for f in values :
279
287
try :
280
288
self .dialog_set_val (f , values [f ])
@@ -284,10 +292,10 @@ def dialog_set_vals(self, values):
284
292
if failed :
285
293
if len (failed ) < len (values ):
286
294
self .dialog_set_vals (failed )
287
- else :
295
+ elif last_error is not None :
288
296
raise last_error
289
297
290
- def dialog (self , values , expect = None , secondary = False ):
298
+ def dialog (self , values : DialogValues , expect : DialogValues | None = None , secondary : bool = False ):
291
299
if expect is None :
292
300
expect = {}
293
301
self .dialog_wait_open ()
@@ -327,7 +335,7 @@ def teardown():
327
335
self .dialog_wait_close ()
328
336
self .retry (setup , check , teardown )
329
337
330
- def dialog_apply_with_retry (self , expected_errors = None ):
338
+ def dialog_apply_with_retry (self , expected_errors : list [ str ] | None = None ) -> None :
331
339
def step ():
332
340
try :
333
341
self .dialog_apply ()
@@ -405,10 +413,10 @@ def configuration_field(self, dev: str, tab: str, field: str) -> str:
405
413
return from_udisks_ascii (entry [1 ][field ])
406
414
return ""
407
415
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 :
409
417
self .assertIn (text , self .configuration_field (dev , tab , field ))
410
418
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 :
412
420
self .assertNotIn (text , self .configuration_field (dev , tab , field ))
413
421
414
422
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:
426
434
return from_udisks_ascii (entry [1 ][field ])
427
435
return ""
428
436
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 :
430
438
self .assertIn (text , self .child_configuration_field (dev , tab , field ))
431
439
432
- def lvol_child_configuration_field (self , lvol , tab , field ) :
440
+ def lvol_child_configuration_field (self , lvol : str , tab : str , field : str ) -> str :
433
441
udisk_objects = self .udisks_objects ()
434
442
for path in udisk_objects :
435
443
if "org.freedesktop.UDisks2.LogicalVolume" in udisk_objects [path ]:
@@ -443,7 +451,7 @@ def lvol_child_configuration_field(self, lvol, tab, field):
443
451
return from_udisks_ascii (entry [1 ][field ])
444
452
return ""
445
453
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 :
447
455
self .assertIn (text , self .lvol_child_configuration_field (lvol , tab , field ))
448
456
449
457
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
607
615
# We need to click on a <td> element since that's where the handlers are...
608
616
self .browser .click (self .card_row (title , index , name , location ) + " td:nth-child(1)" )
609
617
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 ):
611
620
return self .card_row (title , row_index , row_name , row_location ) + f" td:nth-child({ col_index } )"
612
621
613
622
def card_desc (self , card_title : str , desc_title : str ) -> str :
0 commit comments