mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/herbert/cryptodev-2.6.git
synced 2026-04-23 05:56:14 -04:00
This implements what I was describing in [1]. When writing a test
author can schedule cleanup / undo actions right after the creation
completes, eg:
cmd("touch /tmp/file")
defer(cmd, "rm /tmp/file")
defer() takes the function name as first argument, and the rest are
arguments for that function. defer()red functions are called in
inverse order after test exits. It's also possible to capture them
and execute earlier (in which case they get automatically de-queued).
undo = defer(cmd, "rm /tmp/file")
# ... some unsafe code ...
undo.exec()
As a nice safety all exceptions from defer()ed calls are captured,
printed, and ignored (they do make the test fail, however).
This addresses the common problem of exceptions in cleanup paths
often being unhandled, leading to potential leaks.
There is a global action queue, flushed by ksft_run(). We could support
function level defers too, I guess, but there's no immediate need..
Link: https://lore.kernel.org/all/877cedb2ki.fsf@nvidia.com/ # [1]
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Reviewed-by: Petr Machata <petrm@nvidia.com>
Link: https://patch.msgid.link/20240627185502.3069139-3-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
186 lines
4.4 KiB
Python
186 lines
4.4 KiB
Python
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
import builtins
|
|
import inspect
|
|
import sys
|
|
import time
|
|
import traceback
|
|
from .consts import KSFT_MAIN_NAME
|
|
from .utils import global_defer_queue
|
|
|
|
KSFT_RESULT = None
|
|
KSFT_RESULT_ALL = True
|
|
|
|
|
|
class KsftFailEx(Exception):
|
|
pass
|
|
|
|
|
|
class KsftSkipEx(Exception):
|
|
pass
|
|
|
|
|
|
class KsftXfailEx(Exception):
|
|
pass
|
|
|
|
|
|
def ksft_pr(*objs, **kwargs):
|
|
print("#", *objs, **kwargs)
|
|
|
|
|
|
def _fail(*args):
|
|
global KSFT_RESULT
|
|
KSFT_RESULT = False
|
|
|
|
frame = inspect.stack()[2]
|
|
ksft_pr("At " + frame.filename + " line " + str(frame.lineno) + ":")
|
|
ksft_pr(*args)
|
|
|
|
|
|
def ksft_eq(a, b, comment=""):
|
|
global KSFT_RESULT
|
|
if a != b:
|
|
_fail("Check failed", a, "!=", b, comment)
|
|
|
|
|
|
def ksft_true(a, comment=""):
|
|
if not a:
|
|
_fail("Check failed", a, "does not eval to True", comment)
|
|
|
|
|
|
def ksft_in(a, b, comment=""):
|
|
if a not in b:
|
|
_fail("Check failed", a, "not in", b, comment)
|
|
|
|
|
|
def ksft_ge(a, b, comment=""):
|
|
if a < b:
|
|
_fail("Check failed", a, "<", b, comment)
|
|
|
|
|
|
def ksft_lt(a, b, comment=""):
|
|
if a >= b:
|
|
_fail("Check failed", a, ">=", b, comment)
|
|
|
|
|
|
class ksft_raises:
|
|
def __init__(self, expected_type):
|
|
self.exception = None
|
|
self.expected_type = expected_type
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if exc_type is None:
|
|
_fail(f"Expected exception {str(self.expected_type.__name__)}, none raised")
|
|
elif self.expected_type != exc_type:
|
|
_fail(f"Expected exception {str(self.expected_type.__name__)}, raised {str(exc_type.__name__)}")
|
|
self.exception = exc_val
|
|
# Suppress the exception if its the expected one
|
|
return self.expected_type == exc_type
|
|
|
|
|
|
def ksft_busy_wait(cond, sleep=0.005, deadline=1, comment=""):
|
|
end = time.monotonic() + deadline
|
|
while True:
|
|
if cond():
|
|
return
|
|
if time.monotonic() > end:
|
|
_fail("Waiting for condition timed out", comment)
|
|
return
|
|
time.sleep(sleep)
|
|
|
|
|
|
def ktap_result(ok, cnt=1, case="", comment=""):
|
|
global KSFT_RESULT_ALL
|
|
KSFT_RESULT_ALL = KSFT_RESULT_ALL and ok
|
|
|
|
res = ""
|
|
if not ok:
|
|
res += "not "
|
|
res += "ok "
|
|
res += str(cnt) + " "
|
|
res += KSFT_MAIN_NAME
|
|
if case:
|
|
res += "." + str(case.__name__)
|
|
if comment:
|
|
res += " # " + comment
|
|
print(res)
|
|
|
|
|
|
def ksft_flush_defer():
|
|
global KSFT_RESULT
|
|
|
|
i = 0
|
|
qlen_start = len(global_defer_queue)
|
|
while global_defer_queue:
|
|
i += 1
|
|
entry = global_defer_queue.pop()
|
|
try:
|
|
entry.exec_only()
|
|
except:
|
|
ksft_pr(f"Exception while handling defer / cleanup (callback {i} of {qlen_start})!")
|
|
tb = traceback.format_exc()
|
|
for line in tb.strip().split('\n'):
|
|
ksft_pr("Defer Exception|", line)
|
|
KSFT_RESULT = False
|
|
|
|
|
|
def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
|
|
cases = cases or []
|
|
|
|
if globs and case_pfx:
|
|
for key, value in globs.items():
|
|
if not callable(value):
|
|
continue
|
|
for prefix in case_pfx:
|
|
if key.startswith(prefix):
|
|
cases.append(value)
|
|
break
|
|
|
|
totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
|
|
|
|
print("KTAP version 1")
|
|
print("1.." + str(len(cases)))
|
|
|
|
global KSFT_RESULT
|
|
cnt = 0
|
|
for case in cases:
|
|
KSFT_RESULT = True
|
|
cnt += 1
|
|
comment = ""
|
|
cnt_key = ""
|
|
|
|
try:
|
|
case(*args)
|
|
except KsftSkipEx as e:
|
|
comment = "SKIP " + str(e)
|
|
cnt_key = 'skip'
|
|
except KsftXfailEx as e:
|
|
comment = "XFAIL " + str(e)
|
|
cnt_key = 'xfail'
|
|
except Exception as e:
|
|
tb = traceback.format_exc()
|
|
for line in tb.strip().split('\n'):
|
|
ksft_pr("Exception|", line)
|
|
KSFT_RESULT = False
|
|
cnt_key = 'fail'
|
|
|
|
ksft_flush_defer()
|
|
|
|
if not cnt_key:
|
|
cnt_key = 'pass' if KSFT_RESULT else 'fail'
|
|
|
|
ktap_result(KSFT_RESULT, cnt, case, comment=comment)
|
|
totals[cnt_key] += 1
|
|
|
|
print(
|
|
f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0"
|
|
)
|
|
|
|
|
|
def ksft_exit():
|
|
global KSFT_RESULT_ALL
|
|
sys.exit(0 if KSFT_RESULT_ALL else 1)
|