ksft.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. # SPDX-License-Identifier: GPL-2.0
  2. import functools
  3. import inspect
  4. import signal
  5. import sys
  6. import time
  7. import traceback
  8. from collections import namedtuple
  9. from .consts import KSFT_MAIN_NAME
  10. from . import utils
  11. KSFT_RESULT = None
  12. KSFT_RESULT_ALL = True
  13. KSFT_DISRUPTIVE = True
  14. class KsftFailEx(Exception):
  15. pass
  16. class KsftSkipEx(Exception):
  17. pass
  18. class KsftXfailEx(Exception):
  19. pass
  20. class KsftTerminate(KeyboardInterrupt):
  21. pass
  22. def ksft_pr(*objs, **kwargs):
  23. """
  24. Print logs to stdout.
  25. Behaves like print() but log lines will be prefixed
  26. with # to prevent breaking the TAP output formatting.
  27. Extra arguments (on top of what print() supports):
  28. line_pfx - add extra string before each line
  29. """
  30. sep = kwargs.pop("sep", " ")
  31. pfx = kwargs.pop("line_pfx", "")
  32. pfx = "#" + (" " + pfx if pfx else "")
  33. kwargs["flush"] = True
  34. text = sep.join(str(obj) for obj in objs)
  35. prefixed = f"\n{pfx} ".join(text.split('\n'))
  36. print(pfx, prefixed, **kwargs)
  37. def _fail(*args):
  38. global KSFT_RESULT
  39. KSFT_RESULT = False
  40. stack = inspect.stack()
  41. started = False
  42. for frame in reversed(stack[2:]):
  43. # Start printing from the test case function
  44. if not started:
  45. if frame.function == 'ksft_run':
  46. started = True
  47. continue
  48. ksft_pr("Check| At " + frame.filename + ", line " + str(frame.lineno) +
  49. ", in " + frame.function + ":")
  50. ksft_pr("Check| " + frame.code_context[0].strip())
  51. ksft_pr(*args)
  52. def ksft_eq(a, b, comment=""):
  53. global KSFT_RESULT
  54. if a != b:
  55. _fail("Check failed", a, "!=", b, comment)
  56. def ksft_ne(a, b, comment=""):
  57. global KSFT_RESULT
  58. if a == b:
  59. _fail("Check failed", a, "==", b, comment)
  60. def ksft_true(a, comment=""):
  61. if not a:
  62. _fail("Check failed", a, "does not eval to True", comment)
  63. def ksft_not_none(a, comment=""):
  64. if a is None:
  65. _fail("Check failed", a, "is None", comment)
  66. def ksft_in(a, b, comment=""):
  67. if a not in b:
  68. _fail("Check failed", a, "not in", b, comment)
  69. def ksft_not_in(a, b, comment=""):
  70. if a in b:
  71. _fail("Check failed", a, "in", b, comment)
  72. def ksft_is(a, b, comment=""):
  73. if a is not b:
  74. _fail("Check failed", a, "is not", b, comment)
  75. def ksft_ge(a, b, comment=""):
  76. if a < b:
  77. _fail("Check failed", a, "<", b, comment)
  78. def ksft_gt(a, b, comment=""):
  79. if a <= b:
  80. _fail("Check failed", a, "<=", b, comment)
  81. def ksft_lt(a, b, comment=""):
  82. if a >= b:
  83. _fail("Check failed", a, ">=", b, comment)
  84. class ksft_raises:
  85. def __init__(self, expected_type):
  86. self.exception = None
  87. self.expected_type = expected_type
  88. def __enter__(self):
  89. return self
  90. def __exit__(self, exc_type, exc_val, exc_tb):
  91. if exc_type is None:
  92. _fail(f"Expected exception {str(self.expected_type.__name__)}, none raised")
  93. elif self.expected_type != exc_type:
  94. _fail(f"Expected exception {str(self.expected_type.__name__)}, raised {str(exc_type.__name__)}")
  95. self.exception = exc_val
  96. # Suppress the exception if its the expected one
  97. return self.expected_type == exc_type
  98. def ksft_busy_wait(cond, sleep=0.005, deadline=1, comment=""):
  99. end = time.monotonic() + deadline
  100. while True:
  101. if cond():
  102. return
  103. if time.monotonic() > end:
  104. _fail("Waiting for condition timed out", comment)
  105. return
  106. time.sleep(sleep)
  107. def ktap_result(ok, cnt=1, case_name="", comment=""):
  108. global KSFT_RESULT_ALL
  109. KSFT_RESULT_ALL = KSFT_RESULT_ALL and ok
  110. res = ""
  111. if not ok:
  112. res += "not "
  113. res += "ok "
  114. res += str(cnt) + " "
  115. res += KSFT_MAIN_NAME
  116. if case_name:
  117. res += "." + case_name
  118. if comment:
  119. res += " # " + comment
  120. print(res, flush=True)
  121. def _ksft_defer_arm(state):
  122. """ Allow or disallow the use of defer() """
  123. utils.GLOBAL_DEFER_ARMED = state
  124. def ksft_flush_defer():
  125. global KSFT_RESULT
  126. i = 0
  127. qlen_start = len(utils.GLOBAL_DEFER_QUEUE)
  128. while utils.GLOBAL_DEFER_QUEUE:
  129. i += 1
  130. entry = utils.GLOBAL_DEFER_QUEUE.pop()
  131. try:
  132. entry.exec_only()
  133. except Exception:
  134. ksft_pr(f"Exception while handling defer / cleanup (callback {i} of {qlen_start})!")
  135. ksft_pr(traceback.format_exc(), line_pfx="Defer Exception|")
  136. KSFT_RESULT = False
  137. KsftCaseFunction = namedtuple("KsftCaseFunction",
  138. ['name', 'original_func', 'variants'])
  139. def ksft_disruptive(func):
  140. """
  141. Decorator that marks the test as disruptive (e.g. the test
  142. that can down the interface). Disruptive tests can be skipped
  143. by passing DISRUPTIVE=False environment variable.
  144. """
  145. @functools.wraps(func)
  146. def wrapper(*args, **kwargs):
  147. if not KSFT_DISRUPTIVE:
  148. raise KsftSkipEx("marked as disruptive")
  149. return func(*args, **kwargs)
  150. return wrapper
  151. class KsftNamedVariant:
  152. """ Named string name + argument list tuple for @ksft_variants """
  153. def __init__(self, name, *params):
  154. self.params = params
  155. self.name = name or "_".join([str(x) for x in self.params])
  156. def ksft_variants(params):
  157. """
  158. Decorator defining the sets of inputs for a test.
  159. The parameters will be included in the name of the resulting sub-case.
  160. Parameters can be either single object, tuple or a KsftNamedVariant.
  161. The argument can be a list or a generator.
  162. Example:
  163. @ksft_variants([
  164. (1, "a"),
  165. (2, "b"),
  166. KsftNamedVariant("three", 3, "c"),
  167. ])
  168. def my_case(cfg, a, b):
  169. pass # ...
  170. ksft_run(cases=[my_case], args=(cfg, ))
  171. Will generate cases:
  172. my_case.1_a
  173. my_case.2_b
  174. my_case.three
  175. """
  176. return lambda func: KsftCaseFunction(func.__name__, func, params)
  177. def ksft_setup(env):
  178. """
  179. Setup test framework global state from the environment.
  180. """
  181. def get_bool(env, name):
  182. value = env.get(name, "").lower()
  183. if value in ["yes", "true"]:
  184. return True
  185. if value in ["no", "false"]:
  186. return False
  187. try:
  188. return bool(int(value))
  189. except Exception:
  190. raise Exception(f"failed to parse {name}")
  191. if "DISRUPTIVE" in env:
  192. global KSFT_DISRUPTIVE
  193. KSFT_DISRUPTIVE = get_bool(env, "DISRUPTIVE")
  194. return env
  195. def _ksft_intr(signum, frame):
  196. # ksft runner.sh sends 2 SIGTERMs in a row on a timeout
  197. # if we don't ignore the second one it will stop us from handling cleanup
  198. global term_cnt
  199. term_cnt += 1
  200. if term_cnt == 1:
  201. raise KsftTerminate()
  202. else:
  203. ksft_pr(f"Ignoring SIGTERM (cnt: {term_cnt}), already exiting...")
  204. def _ksft_generate_test_cases(cases, globs, case_pfx, args):
  205. """Generate a flat list of (func, args, name) tuples"""
  206. cases = cases or []
  207. test_cases = []
  208. # If using the globs method find all relevant functions
  209. if globs and case_pfx:
  210. for key, value in globs.items():
  211. if not callable(value):
  212. continue
  213. for prefix in case_pfx:
  214. if key.startswith(prefix):
  215. cases.append(value)
  216. break
  217. for func in cases:
  218. if isinstance(func, KsftCaseFunction):
  219. # Parametrized test - create case for each param
  220. for param in func.variants:
  221. if not isinstance(param, KsftNamedVariant):
  222. if not isinstance(param, tuple):
  223. param = (param, )
  224. param = KsftNamedVariant(None, *param)
  225. test_cases.append((func.original_func,
  226. (*args, *param.params),
  227. func.name + "." + param.name))
  228. else:
  229. test_cases.append((func, args, func.__name__))
  230. return test_cases
  231. def ksft_run(cases=None, globs=None, case_pfx=None, args=()):
  232. test_cases = _ksft_generate_test_cases(cases, globs, case_pfx, args)
  233. global term_cnt
  234. term_cnt = 0
  235. prev_sigterm = signal.signal(signal.SIGTERM, _ksft_intr)
  236. totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
  237. print("TAP version 13", flush=True)
  238. print("1.." + str(len(test_cases)), flush=True)
  239. global KSFT_RESULT
  240. cnt = 0
  241. stop = False
  242. for func, args, name in test_cases:
  243. KSFT_RESULT = True
  244. cnt += 1
  245. comment = ""
  246. cnt_key = ""
  247. _ksft_defer_arm(True)
  248. try:
  249. func(*args)
  250. except KsftSkipEx as e:
  251. comment = "SKIP " + str(e)
  252. cnt_key = 'skip'
  253. except KsftXfailEx as e:
  254. comment = "XFAIL " + str(e)
  255. cnt_key = 'xfail'
  256. except BaseException as e:
  257. stop |= isinstance(e, KeyboardInterrupt)
  258. ksft_pr(traceback.format_exc(), line_pfx="Exception|")
  259. if stop:
  260. ksft_pr(f"Stopping tests due to {type(e).__name__}.")
  261. KSFT_RESULT = False
  262. cnt_key = 'fail'
  263. _ksft_defer_arm(False)
  264. try:
  265. ksft_flush_defer()
  266. except BaseException as e:
  267. ksft_pr(traceback.format_exc(), line_pfx="Exception|")
  268. if isinstance(e, KeyboardInterrupt):
  269. ksft_pr()
  270. ksft_pr("WARN: defer() interrupted, cleanup may be incomplete.")
  271. ksft_pr(" Attempting to finish cleanup before exiting.")
  272. ksft_pr(" Interrupt again to exit immediately.")
  273. ksft_pr()
  274. stop = True
  275. # Flush was interrupted, try to finish the job best we can
  276. ksft_flush_defer()
  277. if not cnt_key:
  278. cnt_key = 'pass' if KSFT_RESULT else 'fail'
  279. ktap_result(KSFT_RESULT, cnt, name, comment=comment)
  280. totals[cnt_key] += 1
  281. if stop:
  282. break
  283. signal.signal(signal.SIGTERM, prev_sigterm)
  284. print(
  285. f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0"
  286. )
  287. def ksft_exit():
  288. global KSFT_RESULT_ALL
  289. sys.exit(0 if KSFT_RESULT_ALL else 1)