Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: net: py: add test variants

There's a lot of cases where we try to re-run the same code with
different parameters. We currently need to either use a generator
method or create a "main" case implementation which then gets called
by trivial case functions:

def _test(x, y, z):
...

def case_int():
_test(1, 2, 3)

def case_str():
_test('a', 'b', 'c')

Add support for variants, similar to kselftests_harness.h and
a lot of other frameworks. Variants can be added as decorator
to test functions:

@ksft_variants([(1, 2, 3), ('a', 'b', 'c')])
def case(x, y, z):
...

ksft_run() will auto-generate case names:
case.1_2_3
case.a_b_c

Because the names may not always be pretty (and to avoid forcing
classes to implement case-friendly __str__()) add a wrapper class
KsftNamedVariant which lets the user specify the name for the variant.

Note that ksft_run's args are still supported. ksft_run splices args
and variant params together.

Reviewed-by: Willem de Bruijn <willemb@google.com>
Reviewed-by: Petr Machata <petrm@nvidia.com>
Link: https://patch.msgid.link/20251120021024.2944527-4-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+61 -7
+2 -2
tools/testing/selftests/drivers/net/hw/lib/py/__init__.py
··· 25 25 fd_read_timeout, ip, rand_port, wait_port_listen, wait_file 26 26 from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx 27 27 from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ 28 - ksft_setup 28 + ksft_setup, ksft_variants, KsftNamedVariant 29 29 from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ 30 30 ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none 31 31 from drivers.net.lib.py import GenerateTraffic, Remote ··· 40 40 "wait_port_listen", "wait_file", 41 41 "KsftSkipEx", "KsftFailEx", "KsftXfailEx", 42 42 "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", 43 - "ksft_setup", 43 + "ksft_setup", "ksft_variants", "KsftNamedVariant", 44 44 "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", 45 45 "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", 46 46 "ksft_not_none", "ksft_not_none",
+2 -2
tools/testing/selftests/drivers/net/lib/py/__init__.py
··· 25 25 fd_read_timeout, ip, rand_port, wait_port_listen, wait_file 26 26 from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx 27 27 from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ 28 - ksft_setup 28 + ksft_setup, ksft_variants, KsftNamedVariant 29 29 from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ 30 30 ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none 31 31 ··· 38 38 "wait_port_listen", "wait_file", 39 39 "KsftSkipEx", "KsftFailEx", "KsftXfailEx", 40 40 "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", 41 - "ksft_setup", 41 + "ksft_setup", "ksft_variants", "KsftNamedVariant", 42 42 "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", 43 43 "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", 44 44 "ksft_not_none", "ksft_not_none"]
+3 -2
tools/testing/selftests/net/lib/py/__init__.py
··· 8 8 from .ksft import KsftFailEx, KsftSkipEx, KsftXfailEx, ksft_pr, ksft_eq, \ 9 9 ksft_ne, ksft_true, ksft_not_none, ksft_in, ksft_not_in, ksft_is, \ 10 10 ksft_ge, ksft_gt, ksft_lt, ksft_raises, ksft_busy_wait, \ 11 - ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit 11 + ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit, \ 12 + ksft_variants, KsftNamedVariant 12 13 from .netns import NetNS, NetNSEnter 13 14 from .nsim import NetdevSim, NetdevSimDev 14 15 from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ ··· 22 21 "ksft_ne", "ksft_true", "ksft_not_none", "ksft_in", "ksft_not_in", 23 22 "ksft_is", "ksft_ge", "ksft_gt", "ksft_lt", "ksft_raises", 24 23 "ksft_busy_wait", "ktap_result", "ksft_disruptive", "ksft_setup", 25 - "ksft_run", "ksft_exit", 24 + "ksft_run", "ksft_exit", "ksft_variants", "KsftNamedVariant", 26 25 "NetNS", "NetNSEnter", 27 26 "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer", 28 27 "bpftool", "ip", "ethtool", "bpftrace", "rand_port",
+54 -1
tools/testing/selftests/net/lib/py/ksft.py
··· 6 6 import sys 7 7 import time 8 8 import traceback 9 + from collections import namedtuple 9 10 from .consts import KSFT_MAIN_NAME 10 11 from .utils import global_defer_queue 11 12 ··· 171 170 KSFT_RESULT = False 172 171 173 172 173 + KsftCaseFunction = namedtuple("KsftCaseFunction", 174 + ['name', 'original_func', 'variants']) 175 + 176 + 174 177 def ksft_disruptive(func): 175 178 """ 176 179 Decorator that marks the test as disruptive (e.g. the test ··· 188 183 raise KsftSkipEx("marked as disruptive") 189 184 return func(*args, **kwargs) 190 185 return wrapper 186 + 187 + 188 + class KsftNamedVariant: 189 + """ Named string name + argument list tuple for @ksft_variants """ 190 + 191 + def __init__(self, name, *params): 192 + self.params = params 193 + self.name = name or "_".join([str(x) for x in self.params]) 194 + 195 + 196 + def ksft_variants(params): 197 + """ 198 + Decorator defining the sets of inputs for a test. 199 + The parameters will be included in the name of the resulting sub-case. 200 + Parameters can be either single object, tuple or a KsftNamedVariant. 201 + The argument can be a list or a generator. 202 + 203 + Example: 204 + 205 + @ksft_variants([ 206 + (1, "a"), 207 + (2, "b"), 208 + KsftNamedVariant("three", 3, "c"), 209 + ]) 210 + def my_case(cfg, a, b): 211 + pass # ... 212 + 213 + ksft_run(cases=[my_case], args=(cfg, )) 214 + 215 + Will generate cases: 216 + my_case.1_a 217 + my_case.2_b 218 + my_case.three 219 + """ 220 + 221 + return lambda func: KsftCaseFunction(func.__name__, func, params) 191 222 192 223 193 224 def ksft_setup(env): ··· 277 236 break 278 237 279 238 for func in cases: 280 - test_cases.append((func, args, func.__name__)) 239 + if isinstance(func, KsftCaseFunction): 240 + # Parametrized test - create case for each param 241 + for param in func.variants: 242 + if not isinstance(param, KsftNamedVariant): 243 + if not isinstance(param, tuple): 244 + param = (param, ) 245 + param = KsftNamedVariant(None, *param) 246 + 247 + test_cases.append((func.original_func, 248 + (*args, *param.params), 249 + func.name + "." + param.name)) 250 + else: 251 + test_cases.append((func, args, func.__name__)) 281 252 282 253 return test_cases 283 254