test-runner: implement non_block_wait

There was a common bit of code all over test-runner and utilities
which would wait for 'something' in a loop. At best these loops
would do the right thing and use the GLib.iteration call as to not
block the main loop, and at worst would not use it and just busy
wait.

Namespace.non_block_wait unifies all these into a single API to
a) do the wait correctly and b) prevent duplicate code.
This commit is contained in:
James Prestwood 2021-08-13 09:24:35 -07:00 committed by Denis Kenzior
parent 2af0166970
commit 23cf6cb2e4
1 changed files with 65 additions and 15 deletions

View File

@ -233,7 +233,7 @@ class Process:
if not wait and not check:
return
self.pid.wait(timeout=5)
Namespace.non_block_wait(self.wait_for_process, 10, 1)
self.killed = True
self.ret = self.pid.returncode
@ -245,6 +245,13 @@ class Process:
if check and self.ret != 0:
raise subprocess.CalledProcessError(returncode=self.ret, cmd=self.args)
def wait_for_process(self, timeout):
try:
self.pid.wait(timeout)
return True
except:
return False
def process_io(self, source):
data = source.read()
@ -312,12 +319,7 @@ class Process:
self.killed = True
def wait_for_socket(self, socket, wait):
waited = 0
while not os.path.exists(socket):
sleep(0.5)
waited += 0.5
if waited > wait:
raise Exception("Timed out waiting for socket")
Namespace.non_block_wait(os.path.exists, wait, socket)
def __str__(self):
return str(self.args) + '\n'
@ -641,7 +643,7 @@ class Namespace:
p = self.start_process(['dbus-daemon', '--config-file=%s' % self.dbus_cfg],
wait=False, cleanup=self._cleanup_dbus)
p.wait_for_socket(self.dbus_address.split('=')[1], wait=5)
p.wait_for_socket(self.dbus_address.split('=')[1], 5)
self._bus = dbus.bus.BusConnection(address_or_type=self.dbus_address)
@ -699,15 +701,62 @@ class Namespace:
return False
def wait_for_dbus_service(self, service):
tries = 0
@staticmethod
def non_block_wait(func, timeout, *args, exception=True):
'''
Convenience function for waiting in a non blocking
manor using GLibs context iteration i.e. does not block
the main loop while waiting.
'func' will be called at least once and repeatedly until
either it returns success, throws an exception, or the
'timeout' expires.
'timeout' is the ultimate timeout in seconds
'*args' will be passed to 'func'
If 'exception' is an Exception type it will be raised.
If 'exception' is True a generic TimeoutError will be raised.
Any other value will not result in an exception.
'''
# Simple class for signaling the wait timeout
class Bool:
def __init__(self, value):
self.value = value
def wait_timeout_cb(done):
done.value = True
return False
mainloop = GLib.MainLoop()
done = Bool(False)
timeout = GLib.timeout_add_seconds(timeout, wait_timeout_cb, done)
context = mainloop.get_context()
while True:
context.iteration(may_block=False)
try:
ret = func(*args)
if ret:
GLib.source_remove(timeout)
return ret
except Exception as e:
GLib.source_remove(timeout)
raise e
while not self._bus.name_has_owner(service):
if tries > 200:
raise TimeoutError('DBus service %s did not appear', service)
tries += 1
sleep(0.1)
if done.value == True:
if isinstance(exception, Exception):
raise exception
elif type(exception) == bool and exception:
raise TimeoutError("Timeout on non_block_wait")
else:
return
def __str__(self):
ret = 'Namespace: %s\n' % self.name
ret += 'Processes:\n'
@ -765,7 +814,8 @@ class TestContext(Namespace):
args.extend(['--no-register'])
self.start_process(args)
self.wait_for_dbus_service('net.connman.hwsim')
self.non_block_wait(self._bus.name_has_owner, 20, 'net.connman.hwsim',
exception=TimeoutError('net.connman.hwsim did not appear'))
for i in range(nradios):
name = 'rad%u' % i