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>
This commit is contained in:
parent
d7d6e47016
commit
b86761ff63
@ -116,6 +116,13 @@ TARGETS += zram
|
||||
TARGETS_HOTPLUG = cpu-hotplug
|
||||
TARGETS_HOTPLUG += memory-hotplug
|
||||
|
||||
# Networking tests want the net/lib target, include it automatically
|
||||
ifneq ($(filter net,$(TARGETS)),)
|
||||
ifeq ($(filter net/lib,$(TARGETS)),)
|
||||
INSTALL_DEP_TARGETS := net/lib
|
||||
endif
|
||||
endif
|
||||
|
||||
# User can optionally provide a TARGETS skiplist. By default we skip
|
||||
# BPF since it has cutting edge build time dependencies which require
|
||||
# more effort to install.
|
||||
@ -245,7 +252,7 @@ ifdef INSTALL_PATH
|
||||
install -m 744 run_kselftest.sh $(INSTALL_PATH)/
|
||||
rm -f $(TEST_LIST)
|
||||
@ret=1; \
|
||||
for TARGET in $(TARGETS); do \
|
||||
for TARGET in $(TARGETS) $(INSTALL_DEP_TARGETS); do \
|
||||
BUILD_TARGET=$$BUILD/$$TARGET; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install \
|
||||
INSTALL_PATH=$(INSTALL_PATH)/$$TARGET \
|
||||
|
8
tools/testing/selftests/net/lib/Makefile
Normal file
8
tools/testing/selftests/net/lib/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
TEST_FILES := ../../../../../Documentation/netlink/specs
|
||||
TEST_FILES += ../../../../net/ynl
|
||||
|
||||
TEST_INCLUDES := $(wildcard py/*.py)
|
||||
|
||||
include ../../lib.mk
|
6
tools/testing/selftests/net/lib/py/__init__.py
Normal file
6
tools/testing/selftests/net/lib/py/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
from .consts import KSRC
|
||||
from .ksft import *
|
||||
from .utils import *
|
||||
from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
|
9
tools/testing/selftests/net/lib/py/consts.py
Normal file
9
tools/testing/selftests/net/lib/py/consts.py
Normal file
@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
KSFT_DIR = (Path(__file__).parent / "../../..").resolve()
|
||||
KSRC = (Path(__file__).parent / "../../../../../..").resolve()
|
||||
|
||||
KSFT_MAIN_NAME = Path(sys.argv[0]).with_suffix("").name
|
96
tools/testing/selftests/net/lib/py/ksft.py
Normal file
96
tools/testing/selftests/net/lib/py/ksft.py
Normal file
@ -0,0 +1,96 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import builtins
|
||||
from .consts import KSFT_MAIN_NAME
|
||||
|
||||
KSFT_RESULT = None
|
||||
|
||||
|
||||
class KsftSkipEx(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class KsftXfailEx(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def ksft_pr(*objs, **kwargs):
|
||||
print("#", *objs, **kwargs)
|
||||
|
||||
|
||||
def ksft_eq(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a != b:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "!=", b, comment)
|
||||
|
||||
|
||||
def ksft_true(a, comment=""):
|
||||
global KSFT_RESULT
|
||||
if not a:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "does not eval to True", comment)
|
||||
|
||||
|
||||
def ksft_in(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a not in b:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "not in", b, comment)
|
||||
|
||||
|
||||
def ksft_ge(a, b, comment=""):
|
||||
global KSFT_RESULT
|
||||
if a < b:
|
||||
KSFT_RESULT = False
|
||||
ksft_pr("Check failed", a, "<", b, comment)
|
||||
|
||||
|
||||
def ktap_result(ok, cnt=1, case="", comment=""):
|
||||
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_run(cases, args=()):
|
||||
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
|
||||
try:
|
||||
case(*args)
|
||||
except KsftSkipEx as e:
|
||||
ktap_result(True, cnt, case, comment="SKIP " + str(e))
|
||||
totals['skip'] += 1
|
||||
continue
|
||||
except KsftXfailEx as e:
|
||||
ktap_result(True, cnt, case, comment="XFAIL " + str(e))
|
||||
totals['xfail'] += 1
|
||||
continue
|
||||
except Exception as e:
|
||||
for line in str(e).split('\n'):
|
||||
ksft_pr("Exception|", line)
|
||||
ktap_result(False, cnt, case)
|
||||
totals['fail'] += 1
|
||||
continue
|
||||
|
||||
ktap_result(KSFT_RESULT, cnt, case)
|
||||
totals['pass'] += 1
|
||||
|
||||
print(
|
||||
f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0"
|
||||
)
|
47
tools/testing/selftests/net/lib/py/utils.py
Normal file
47
tools/testing/selftests/net/lib/py/utils.py
Normal file
@ -0,0 +1,47 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import json as _json
|
||||
import subprocess
|
||||
|
||||
class cmd:
|
||||
def __init__(self, comm, shell=True, fail=True, ns=None, background=False):
|
||||
if ns:
|
||||
if isinstance(ns, NetNS):
|
||||
ns = ns.name
|
||||
comm = f'ip netns exec {ns} ' + comm
|
||||
|
||||
self.stdout = None
|
||||
self.stderr = None
|
||||
self.ret = None
|
||||
|
||||
self.comm = comm
|
||||
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
if not background:
|
||||
self.process(terminate=False, fail=fail)
|
||||
|
||||
def process(self, terminate=True, fail=None):
|
||||
if terminate:
|
||||
self.proc.terminate()
|
||||
stdout, stderr = self.proc.communicate()
|
||||
self.stdout = stdout.decode("utf-8")
|
||||
self.stderr = stderr.decode("utf-8")
|
||||
self.proc.stdout.close()
|
||||
self.proc.stderr.close()
|
||||
self.ret = self.proc.returncode
|
||||
|
||||
if self.proc.returncode != 0 and fail:
|
||||
if len(stderr) > 0 and stderr[-1] == "\n":
|
||||
stderr = stderr[:-1]
|
||||
raise Exception("Command failed: %s\n%s" % (self.proc.args, stderr))
|
||||
|
||||
|
||||
def ip(args, json=None, ns=None):
|
||||
cmd_str = "ip "
|
||||
if json:
|
||||
cmd_str += '-j '
|
||||
cmd_str += args
|
||||
cmd_obj = cmd(cmd_str, ns=ns)
|
||||
if json:
|
||||
return _json.loads(cmd_obj.stdout)
|
||||
return cmd_obj
|
49
tools/testing/selftests/net/lib/py/ynl.py
Normal file
49
tools/testing/selftests/net/lib/py/ynl.py
Normal file
@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from .consts import KSRC, KSFT_DIR
|
||||
from .ksft import ksft_pr, ktap_result
|
||||
|
||||
# Resolve paths
|
||||
try:
|
||||
if (KSFT_DIR / "kselftest-list.txt").exists():
|
||||
# Running in "installed" selftests
|
||||
tools_full_path = KSFT_DIR
|
||||
SPEC_PATH = KSFT_DIR / "net/lib/specs"
|
||||
|
||||
sys.path.append(tools_full_path.as_posix())
|
||||
from net.lib.ynl.lib import YnlFamily, NlError
|
||||
else:
|
||||
# Running in tree
|
||||
tools_full_path = KSRC / "tools"
|
||||
SPEC_PATH = KSRC / "Documentation/netlink/specs"
|
||||
|
||||
sys.path.append(tools_full_path.as_posix())
|
||||
from net.ynl.lib import YnlFamily, NlError
|
||||
except ModuleNotFoundError as e:
|
||||
ksft_pr("Failed importing `ynl` library from kernel sources")
|
||||
ksft_pr(str(e))
|
||||
ktap_result(True, comment="SKIP")
|
||||
sys.exit(4)
|
||||
|
||||
#
|
||||
# Wrapper classes, loading the right specs
|
||||
# Set schema='' to avoid jsonschema validation, it's slow
|
||||
#
|
||||
class EthtoolFamily(YnlFamily):
|
||||
def __init__(self):
|
||||
super().__init__((SPEC_PATH / Path('ethtool.yaml')).as_posix(),
|
||||
schema='')
|
||||
|
||||
|
||||
class RtnlFamily(YnlFamily):
|
||||
def __init__(self):
|
||||
super().__init__((SPEC_PATH / Path('rt_link.yaml')).as_posix(),
|
||||
schema='')
|
||||
|
||||
|
||||
class NetdevFamily(YnlFamily):
|
||||
def __init__(self):
|
||||
super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(),
|
||||
schema='')
|
Loading…
x
Reference in New Issue
Block a user