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

selftests: net: add scaffolding for Netlink tests in Python

Add glue code for accessing the YNL library which lives under
tools/net and YAML spec files from under Documentation/.
Automatically figure out if tests are run in tree or not.
Since we'll want to use this library both from net and
drivers/net test targets make the library a target as well,
and automatically include it when net or drivers/net are
included. Making net/lib a target ensures that we end up
with only one copy of it, and saves us some path guessing.

Add a tiny bit of formatting support to be able to output KTAP
from the start.

Reviewed-by: Petr Machata <petrm@nvidia.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Jakub Kicinski and committed by
David S. Miller
b86761ff d7d6e470

+223 -1
+8 -1
tools/testing/selftests/Makefile
··· 116 116 TARGETS_HOTPLUG = cpu-hotplug 117 117 TARGETS_HOTPLUG += memory-hotplug 118 118 119 + # Networking tests want the net/lib target, include it automatically 120 + ifneq ($(filter net,$(TARGETS)),) 121 + ifeq ($(filter net/lib,$(TARGETS)),) 122 + INSTALL_DEP_TARGETS := net/lib 123 + endif 124 + endif 125 + 119 126 # User can optionally provide a TARGETS skiplist. By default we skip 120 127 # BPF since it has cutting edge build time dependencies which require 121 128 # more effort to install. ··· 252 245 install -m 744 run_kselftest.sh $(INSTALL_PATH)/ 253 246 rm -f $(TEST_LIST) 254 247 @ret=1; \ 255 - for TARGET in $(TARGETS); do \ 248 + for TARGET in $(TARGETS) $(INSTALL_DEP_TARGETS); do \ 256 249 BUILD_TARGET=$$BUILD/$$TARGET; \ 257 250 $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install \ 258 251 INSTALL_PATH=$(INSTALL_PATH)/$$TARGET \
+8
tools/testing/selftests/net/lib/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + TEST_FILES := ../../../../../Documentation/netlink/specs 4 + TEST_FILES += ../../../../net/ynl 5 + 6 + TEST_INCLUDES := $(wildcard py/*.py) 7 + 8 + include ../../lib.mk
+6
tools/testing/selftests/net/lib/py/__init__.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + from .consts import KSRC 4 + from .ksft import * 5 + from .utils import * 6 + from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
+9
tools/testing/selftests/net/lib/py/consts.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + import sys 4 + from pathlib import Path 5 + 6 + KSFT_DIR = (Path(__file__).parent / "../../..").resolve() 7 + KSRC = (Path(__file__).parent / "../../../../../..").resolve() 8 + 9 + KSFT_MAIN_NAME = Path(sys.argv[0]).with_suffix("").name
+96
tools/testing/selftests/net/lib/py/ksft.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + import builtins 4 + from .consts import KSFT_MAIN_NAME 5 + 6 + KSFT_RESULT = None 7 + 8 + 9 + class KsftSkipEx(Exception): 10 + pass 11 + 12 + 13 + class KsftXfailEx(Exception): 14 + pass 15 + 16 + 17 + def ksft_pr(*objs, **kwargs): 18 + print("#", *objs, **kwargs) 19 + 20 + 21 + def ksft_eq(a, b, comment=""): 22 + global KSFT_RESULT 23 + if a != b: 24 + KSFT_RESULT = False 25 + ksft_pr("Check failed", a, "!=", b, comment) 26 + 27 + 28 + def ksft_true(a, comment=""): 29 + global KSFT_RESULT 30 + if not a: 31 + KSFT_RESULT = False 32 + ksft_pr("Check failed", a, "does not eval to True", comment) 33 + 34 + 35 + def ksft_in(a, b, comment=""): 36 + global KSFT_RESULT 37 + if a not in b: 38 + KSFT_RESULT = False 39 + ksft_pr("Check failed", a, "not in", b, comment) 40 + 41 + 42 + def ksft_ge(a, b, comment=""): 43 + global KSFT_RESULT 44 + if a < b: 45 + KSFT_RESULT = False 46 + ksft_pr("Check failed", a, "<", b, comment) 47 + 48 + 49 + def ktap_result(ok, cnt=1, case="", comment=""): 50 + res = "" 51 + if not ok: 52 + res += "not " 53 + res += "ok " 54 + res += str(cnt) + " " 55 + res += KSFT_MAIN_NAME 56 + if case: 57 + res += "." + str(case.__name__) 58 + if comment: 59 + res += " # " + comment 60 + print(res) 61 + 62 + 63 + def ksft_run(cases, args=()): 64 + totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0} 65 + 66 + print("KTAP version 1") 67 + print("1.." + str(len(cases))) 68 + 69 + global KSFT_RESULT 70 + cnt = 0 71 + for case in cases: 72 + KSFT_RESULT = True 73 + cnt += 1 74 + try: 75 + case(*args) 76 + except KsftSkipEx as e: 77 + ktap_result(True, cnt, case, comment="SKIP " + str(e)) 78 + totals['skip'] += 1 79 + continue 80 + except KsftXfailEx as e: 81 + ktap_result(True, cnt, case, comment="XFAIL " + str(e)) 82 + totals['xfail'] += 1 83 + continue 84 + except Exception as e: 85 + for line in str(e).split('\n'): 86 + ksft_pr("Exception|", line) 87 + ktap_result(False, cnt, case) 88 + totals['fail'] += 1 89 + continue 90 + 91 + ktap_result(KSFT_RESULT, cnt, case) 92 + totals['pass'] += 1 93 + 94 + print( 95 + f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0" 96 + )
+47
tools/testing/selftests/net/lib/py/utils.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + import json as _json 4 + import subprocess 5 + 6 + class cmd: 7 + def __init__(self, comm, shell=True, fail=True, ns=None, background=False): 8 + if ns: 9 + if isinstance(ns, NetNS): 10 + ns = ns.name 11 + comm = f'ip netns exec {ns} ' + comm 12 + 13 + self.stdout = None 14 + self.stderr = None 15 + self.ret = None 16 + 17 + self.comm = comm 18 + self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, 19 + stderr=subprocess.PIPE) 20 + if not background: 21 + self.process(terminate=False, fail=fail) 22 + 23 + def process(self, terminate=True, fail=None): 24 + if terminate: 25 + self.proc.terminate() 26 + stdout, stderr = self.proc.communicate() 27 + self.stdout = stdout.decode("utf-8") 28 + self.stderr = stderr.decode("utf-8") 29 + self.proc.stdout.close() 30 + self.proc.stderr.close() 31 + self.ret = self.proc.returncode 32 + 33 + if self.proc.returncode != 0 and fail: 34 + if len(stderr) > 0 and stderr[-1] == "\n": 35 + stderr = stderr[:-1] 36 + raise Exception("Command failed: %s\n%s" % (self.proc.args, stderr)) 37 + 38 + 39 + def ip(args, json=None, ns=None): 40 + cmd_str = "ip " 41 + if json: 42 + cmd_str += '-j ' 43 + cmd_str += args 44 + cmd_obj = cmd(cmd_str, ns=ns) 45 + if json: 46 + return _json.loads(cmd_obj.stdout) 47 + return cmd_obj
+49
tools/testing/selftests/net/lib/py/ynl.py
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + import sys 4 + from pathlib import Path 5 + from .consts import KSRC, KSFT_DIR 6 + from .ksft import ksft_pr, ktap_result 7 + 8 + # Resolve paths 9 + try: 10 + if (KSFT_DIR / "kselftest-list.txt").exists(): 11 + # Running in "installed" selftests 12 + tools_full_path = KSFT_DIR 13 + SPEC_PATH = KSFT_DIR / "net/lib/specs" 14 + 15 + sys.path.append(tools_full_path.as_posix()) 16 + from net.lib.ynl.lib import YnlFamily, NlError 17 + else: 18 + # Running in tree 19 + tools_full_path = KSRC / "tools" 20 + SPEC_PATH = KSRC / "Documentation/netlink/specs" 21 + 22 + sys.path.append(tools_full_path.as_posix()) 23 + from net.ynl.lib import YnlFamily, NlError 24 + except ModuleNotFoundError as e: 25 + ksft_pr("Failed importing `ynl` library from kernel sources") 26 + ksft_pr(str(e)) 27 + ktap_result(True, comment="SKIP") 28 + sys.exit(4) 29 + 30 + # 31 + # Wrapper classes, loading the right specs 32 + # Set schema='' to avoid jsonschema validation, it's slow 33 + # 34 + class EthtoolFamily(YnlFamily): 35 + def __init__(self): 36 + super().__init__((SPEC_PATH / Path('ethtool.yaml')).as_posix(), 37 + schema='') 38 + 39 + 40 + class RtnlFamily(YnlFamily): 41 + def __init__(self): 42 + super().__init__((SPEC_PATH / Path('rt_link.yaml')).as_posix(), 43 + schema='') 44 + 45 + 46 + class NetdevFamily(YnlFamily): 47 + def __init__(self): 48 + super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(), 49 + schema='')