Commit 6ae67f11 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

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: default avatarWillem de Bruijn <willemb@google.com>
Reviewed-by: default avatarPetr Machata <petrm@nvidia.com>
Link: https://patch.msgid.link/20251120021024.2944527-4-kuba@kernel.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 80970e0f
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ try:
        fd_read_timeout, ip, rand_port, wait_port_listen, wait_file
    from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
    from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
        ksft_setup
        ksft_setup, ksft_variants, KsftNamedVariant
    from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
        ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
    from drivers.net.lib.py import GenerateTraffic, Remote
@@ -40,7 +40,7 @@ try:
               "wait_port_listen", "wait_file",
               "KsftSkipEx", "KsftFailEx", "KsftXfailEx",
               "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
               "ksft_setup",
               "ksft_setup", "ksft_variants", "KsftNamedVariant",
               "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
               "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
               "ksft_not_none", "ksft_not_none",
+2 −2
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ try:
        fd_read_timeout, ip, rand_port, wait_port_listen, wait_file
    from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
    from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
        ksft_setup
        ksft_setup, ksft_variants, KsftNamedVariant
    from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
        ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none

@@ -38,7 +38,7 @@ try:
               "wait_port_listen", "wait_file",
               "KsftSkipEx", "KsftFailEx", "KsftXfailEx",
               "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
               "ksft_setup",
               "ksft_setup", "ksft_variants", "KsftNamedVariant",
               "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
               "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
               "ksft_not_none", "ksft_not_none"]
+3 −2
Original line number Diff line number Diff line
@@ -8,7 +8,8 @@ from .consts import KSRC
from .ksft import KsftFailEx, KsftSkipEx, KsftXfailEx, ksft_pr, ksft_eq, \
    ksft_ne, ksft_true, ksft_not_none, ksft_in, ksft_not_in, ksft_is, \
    ksft_ge, ksft_gt, ksft_lt, ksft_raises, ksft_busy_wait, \
    ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit
    ktap_result, ksft_disruptive, ksft_setup, ksft_run, ksft_exit, \
    ksft_variants, KsftNamedVariant
from .netns import NetNS, NetNSEnter
from .nsim import NetdevSim, NetdevSimDev
from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
@@ -21,7 +22,7 @@ __all__ = ["KSRC",
           "ksft_ne", "ksft_true", "ksft_not_none", "ksft_in", "ksft_not_in",
           "ksft_is", "ksft_ge", "ksft_gt", "ksft_lt", "ksft_raises",
           "ksft_busy_wait", "ktap_result", "ksft_disruptive", "ksft_setup",
           "ksft_run", "ksft_exit",
           "ksft_run", "ksft_exit", "ksft_variants", "KsftNamedVariant",
           "NetNS", "NetNSEnter",
           "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
           "bpftool", "ip", "ethtool", "bpftrace", "rand_port",
+54 −1
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import signal
import sys
import time
import traceback
from collections import namedtuple
from .consts import KSFT_MAIN_NAME
from .utils import global_defer_queue

@@ -170,6 +171,10 @@ def ksft_flush_defer():
            KSFT_RESULT = False


KsftCaseFunction = namedtuple("KsftCaseFunction",
                              ['name', 'original_func', 'variants'])


def ksft_disruptive(func):
    """
    Decorator that marks the test as disruptive (e.g. the test
@@ -185,6 +190,42 @@ def ksft_disruptive(func):
    return wrapper


class KsftNamedVariant:
    """ Named string name + argument list tuple for @ksft_variants """

    def __init__(self, name, *params):
        self.params = params
        self.name = name or "_".join([str(x) for x in self.params])


def ksft_variants(params):
    """
    Decorator defining the sets of inputs for a test.
    The parameters will be included in the name of the resulting sub-case.
    Parameters can be either single object, tuple or a KsftNamedVariant.
    The argument can be a list or a generator.

    Example:

    @ksft_variants([
        (1, "a"),
        (2, "b"),
        KsftNamedVariant("three", 3, "c"),
    ])
    def my_case(cfg, a, b):
        pass # ...

    ksft_run(cases=[my_case], args=(cfg, ))

    Will generate cases:
        my_case.1_a
        my_case.2_b
        my_case.three
    """

    return lambda func: KsftCaseFunction(func.__name__, func, params)


def ksft_setup(env):
    """
    Setup test framework global state from the environment.
@@ -236,6 +277,18 @@ def _ksft_generate_test_cases(cases, globs, case_pfx, args):
                    break

    for func in cases:
        if isinstance(func, KsftCaseFunction):
            # Parametrized test - create case for each param
            for param in func.variants:
                if not isinstance(param, KsftNamedVariant):
                    if not isinstance(param, tuple):
                        param = (param, )
                    param = KsftNamedVariant(None, *param)

                test_cases.append((func.original_func,
                                   (*args, *param.params),
                                   func.name + "." + param.name))
        else:
            test_cases.append((func, args, func.__name__))

    return test_cases