tests: Use python selector in the parallel-vm.py main loop

This gets rid of the loop that was polling for things to do every 0.25
seconds and instead, reacts to any data from VMs as soon as it becomes
available. This avoids unnecessary operations when no new data is
available and avoids unnecessary waits when new data becomes available
more quickly.

Signed-off-by: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2019-12-27 17:12:34 +02:00
parent 0075df74df
commit c64b6f62cd

View File

@ -11,6 +11,7 @@ import curses
import fcntl import fcntl
import logging import logging
import os import os
import selectors
import subprocess import subprocess
import sys import sys
import time import time
@ -87,7 +88,7 @@ def get_failed(vm):
failed += vm[i]['failed'] failed += vm[i]['failed']
return failed return failed
def vm_read_stdout(vm, i, test_queue): def vm_read_stdout(vm, test_queue):
global total_started, total_passed, total_failed, total_skipped global total_started, total_passed, total_failed, total_skipped
global rerun_failures global rerun_failures
global first_run_failures global first_run_failures
@ -102,7 +103,7 @@ def vm_read_stdout(vm, i, test_queue):
if e.errno == errno.EAGAIN: if e.errno == errno.EAGAIN:
return False return False
raise raise
logger.debug("VM[%d] stdout.read[%s]" % (i, out)) logger.debug("VM[%d] stdout.read[%s]" % (vm['idx'], out))
pending = vm['pending'] + out pending = vm['pending'] + out
lines = [] lines = []
while True: while True:
@ -111,7 +112,7 @@ def vm_read_stdout(vm, i, test_queue):
break break
line = pending[0:pos].rstrip() line = pending[0:pos].rstrip()
pending = pending[(pos + 1):] pending = pending[(pos + 1):]
logger.debug("VM[%d] stdout full line[%s]" % (i, line)) logger.debug("VM[%d] stdout full line[%s]" % (vm['idx'], line))
if line.startswith("READY"): if line.startswith("READY"):
vm['starting'] = False vm['starting'] = False
vm['started'] = True vm['started'] = True
@ -124,14 +125,15 @@ def vm_read_stdout(vm, i, test_queue):
total_failed += 1 total_failed += 1
vals = line.split(' ') vals = line.split(' ')
if len(vals) < 2: if len(vals) < 2:
logger.info("VM[%d] incomplete FAIL line: %s" % (i, line)) logger.info("VM[%d] incomplete FAIL line: %s" % (vm['idx'],
line))
name = line name = line
else: else:
name = vals[1] name = vals[1]
logger.debug("VM[%d] test case failed: %s" % (i, name)) logger.debug("VM[%d] test case failed: %s" % (vm['idx'], name))
vm['failed'].append(name) vm['failed'].append(name)
if name != vm['current_name']: if name != vm['current_name']:
logger.info("VM[%d] test result mismatch: %s (expected %s)" % (i, name, vm['current_name'])) logger.info("VM[%d] test result mismatch: %s (expected %s)" % (vm['idx'], name, vm['current_name']))
else: else:
count = vm['current_count'] count = vm['current_count']
if count == 0: if count == 0:
@ -142,7 +144,7 @@ def vm_read_stdout(vm, i, test_queue):
elif line.startswith("NOT-FOUND"): elif line.startswith("NOT-FOUND"):
ready = True ready = True
total_failed += 1 total_failed += 1
logger.info("VM[%d] test case not found" % i) logger.info("VM[%d] test case not found" % vm['idx'])
elif line.startswith("SKIP"): elif line.startswith("SKIP"):
ready = True ready = True
total_skipped += 1 total_skipped += 1
@ -159,7 +161,8 @@ def vm_read_stdout(vm, i, test_queue):
vm['pending'] = pending vm['pending'] = pending
return ready return ready
def start_vm(vm): def start_vm(vm, sel):
logger.info("VM[%d] starting up" % (vm['idx'] + 1))
vm['starting'] = True vm['starting'] = True
vm['proc'] = subprocess.Popen(vm['cmd'], vm['proc'] = subprocess.Popen(vm['cmd'],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
@ -170,6 +173,7 @@ def start_vm(vm):
fd = stream.fileno() fd = stream.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL) fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
sel.register(stream, selectors.EVENT_READ, vm)
def num_vm_starting(): def num_vm_starting():
count = 0 count = 0
@ -178,6 +182,99 @@ def num_vm_starting():
count += 1 count += 1
return count return count
def vm_read_stderr(vm):
try:
err = vm['proc'].stderr.read()
if err != None:
err = err.decode()
if len(err) > 0:
vm['err'] += err
logger.info("VM[%d] stderr.read[%s]" % (vm['idx'], err))
except IOError as e:
if e.errno != errno.EAGAIN:
raise
def vm_next_step(_vm, scr, test_queue):
scr.move(_vm['idx'] + 1, 10)
scr.clrtoeol()
if not test_queue:
_vm['proc'].stdin.write(b'\n')
_vm['proc'].stdin.flush()
scr.addstr("shutting down")
logger.info("VM[%d] shutting down" % _vm['idx'])
return
(name, count) = test_queue.pop(0)
_vm['current_name'] = name
_vm['current_count'] = count
_vm['proc'].stdin.write(name.encode() + b'\n')
_vm['proc'].stdin.flush()
scr.addstr(name)
logger.debug("VM[%d] start test %s" % (_vm['idx'], name))
def check_vm_start(scr, sel, test_queue):
running = False
updated = False
for i in range(num_servers):
if not vm[i]['proc']:
# Either not yet started or already stopped VM
if test_queue and vm[i]['cmd'] and num_vm_starting() < 2:
scr.move(i + 1, 10)
scr.clrtoeol()
scr.addstr(i + 1, 10, "starting VM")
updated = True
start_vm(vm[i], sel)
else:
continue
running = True
return running, updated
def vm_terminated(_vm, scr, sel, test_queue):
updated = False
for stream in [_vm['proc'].stdout, _vm['proc'].stderr]:
sel.unregister(stream)
_vm['proc'] = None
scr.move(_vm['idx'] + 1, 10)
scr.clrtoeol()
log = '{}/{}.srv.{}/console'.format(dir, timestamp, _vm['idx'] + 1)
with open(log, 'r') as f:
if "Kernel panic" in f.read():
scr.addstr("kernel panic")
logger.info("VM[%d] kernel panic" % _vm['idx'])
updated = True
if test_queue:
num_vm = 0
for i in range(num_servers):
if _vm['proc']:
num_vm += 1
if len(test_queue) > num_vm:
scr.addstr("unexpected exit")
logger.info("VM[%d] unexpected exit" % i)
updated = True
return updated
def update_screen(scr, total_tests):
scr.move(num_servers + 1, 10)
scr.clrtoeol()
scr.addstr("{} %".format(int(100.0 * (total_passed + total_failed + total_skipped) / total_tests)))
scr.addstr(num_servers + 1, 20,
"TOTAL={} STARTED={} PASS={} FAIL={} SKIP={}".format(total_tests, total_started, total_passed, total_failed, total_skipped))
failed = get_failed(vm)
if len(failed) > 0:
scr.move(num_servers + 2, 0)
scr.clrtoeol()
scr.addstr("Failed test cases: ")
count = 0
for f in failed:
count += 1
if count > 30:
scr.addstr('...')
scr.clrtoeol()
break
scr.addstr(f)
scr.addstr(' ')
scr.refresh()
def show_progress(scr): def show_progress(scr):
global num_servers global num_servers
global vm global vm
@ -188,10 +285,11 @@ def show_progress(scr):
global total_started, total_passed, total_failed, total_skipped global total_started, total_passed, total_failed, total_skipped
global rerun_failures global rerun_failures
sel = selectors.DefaultSelector()
total_tests = len(tests) total_tests = len(tests)
logger.info("Total tests: %d" % total_tests) logger.info("Total tests: %d" % total_tests)
test_queue = [(t, 0) for t in tests] test_queue = [(t, 0) for t in tests]
start_vm(vm[0]) start_vm(vm[0], sel)
scr.leaveok(1) scr.leaveok(1)
scr.addstr(0, 0, "Parallel test execution status", curses.A_BOLD) scr.addstr(0, 0, "Parallel test execution status", curses.A_BOLD)
@ -204,105 +302,27 @@ def show_progress(scr):
scr.refresh() scr.refresh()
while True: while True:
running = False
updated = False updated = False
events = sel.select(timeout=1)
for i in range(num_servers): for key, mask in events:
if not vm[i]['proc']: _vm = key.data
if test_queue and vm[i]['cmd'] and num_vm_starting() < 2: if not _vm['proc']:
scr.move(i + 1, 10)
scr.clrtoeol()
scr.addstr(i + 1, 10, "starting VM")
updated = True
start_vm(vm[i])
continue continue
if vm[i]['proc'].poll() is not None: vm_read_stderr(_vm)
vm[i]['proc'] = None if vm_read_stdout(_vm, test_queue):
scr.move(i + 1, 10) vm_next_step(_vm, scr, test_queue)
scr.clrtoeol()
log = '{}/{}.srv.{}/console'.format(dir, timestamp, i + 1)
with open(log, 'r') as f:
if "Kernel panic" in f.read():
scr.addstr("kernel panic")
logger.info("VM[%d] kernel panic" % i)
updated = True
if test_queue:
num_vm = 0
for i in range(num_servers):
if vm[i]['proc']:
num_vm += 1
if len(test_queue) > num_vm:
scr.addstr("unexpected exit")
logger.info("VM[%d] unexpected exit" % i)
updated = True
continue
running = True
try:
err = vm[i]['proc'].stderr.read()
if err != None:
err = err.decode()
vm[i]['err'] += err
logger.info("VM[%d] stderr.read[%s]" % (i, err))
except IOError as e:
if e.errno != errno.EAGAIN:
raise
if vm_read_stdout(vm[i], i, test_queue):
scr.move(i + 1, 10)
scr.clrtoeol()
updated = True updated = True
if not test_queue: vm_read_stderr(_vm)
vm[i]['proc'].stdin.write(b'\n') if _vm['proc'].poll() is not None:
vm[i]['proc'].stdin.flush() if vm_terminated(_vm, scr, sel, test_queue):
scr.addstr("shutting down") updated = True
logger.info("VM[%d] shutting down" % i)
continue
else:
(name, count) = test_queue.pop(0)
vm[i]['current_name'] = name
vm[i]['current_count'] = count
vm[i]['proc'].stdin.write(name.encode() + b'\n')
vm[i]['proc'].stdin.flush()
scr.addstr(name)
logger.debug("VM[%d] start test %s" % (i, name))
try:
err = vm[i]['proc'].stderr.read()
if err != None:
err = err.decode()
vm[i]['err'] += err
logger.debug("VM[%d] stderr.read[%s]" % (i, err))
except IOError as e:
if e.errno != errno.EAGAIN:
raise
running, run_update = check_vm_start(scr, sel, test_queue)
if updated or run_update:
update_screen(scr, total_tests)
if not running: if not running:
break break
sel.close()
if updated:
scr.move(num_servers + 1, 10)
scr.clrtoeol()
scr.addstr("{} %".format(int(100.0 * (total_passed + total_failed + total_skipped) / total_tests)))
scr.addstr(num_servers + 1, 20, "TOTAL={} STARTED={} PASS={} FAIL={} SKIP={}".format(total_tests, total_started, total_passed, total_failed, total_skipped))
failed = get_failed(vm)
if len(failed) > 0:
scr.move(num_servers + 2, 0)
scr.clrtoeol()
scr.addstr("Failed test cases: ")
count = 0
for f in failed:
count += 1
if count > 30:
scr.addstr('...')
scr.clrtoeol()
break
scr.addstr(f)
scr.addstr(' ')
scr.refresh()
time.sleep(0.25)
for i in range(num_servers): for i in range(num_servers):
if not vm[i]['proc']: if not vm[i]['proc']:
@ -436,9 +456,6 @@ def main():
vm = {} vm = {}
for i in range(0, num_servers): for i in range(0, num_servers):
print("\rStarting virtual machine {}/{}".format(i + 1, num_servers),
end='')
logger.info("Starting virtual machine {}/{}".format(i + 1, num_servers))
cmd = [os.path.join(scriptsdir, 'vm-run.sh'), cmd = [os.path.join(scriptsdir, 'vm-run.sh'),
'--timestamp', str(timestamp), '--timestamp', str(timestamp),
'--ext', 'srv.%d' % (i + 1), '--ext', 'srv.%d' % (i + 1),
@ -446,6 +463,7 @@ def main():
if args.telnet: if args.telnet:
cmd += ['--telnet', str(args.telnet + i)] cmd += ['--telnet', str(args.telnet + i)]
vm[i] = {} vm[i] = {}
vm[i]['idx'] = i
vm[i]['starting'] = False vm[i]['starting'] = False
vm[i]['started'] = False vm[i]['started'] = False
vm[i]['cmd'] = cmd vm[i]['cmd'] = cmd