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
7 changed files with 223 additions and 1 deletions
|
@ -116,6 +116,13 @@ TARGETS += zram
|
||||||
TARGETS_HOTPLUG = cpu-hotplug
|
TARGETS_HOTPLUG = cpu-hotplug
|
||||||
TARGETS_HOTPLUG += memory-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
|
# User can optionally provide a TARGETS skiplist. By default we skip
|
||||||
# BPF since it has cutting edge build time dependencies which require
|
# BPF since it has cutting edge build time dependencies which require
|
||||||
# more effort to install.
|
# more effort to install.
|
||||||
|
@ -245,7 +252,7 @@ ifdef INSTALL_PATH
|
||||||
install -m 744 run_kselftest.sh $(INSTALL_PATH)/
|
install -m 744 run_kselftest.sh $(INSTALL_PATH)/
|
||||||
rm -f $(TEST_LIST)
|
rm -f $(TEST_LIST)
|
||||||
@ret=1; \
|
@ret=1; \
|
||||||
for TARGET in $(TARGETS); do \
|
for TARGET in $(TARGETS) $(INSTALL_DEP_TARGETS); do \
|
||||||
BUILD_TARGET=$$BUILD/$$TARGET; \
|
BUILD_TARGET=$$BUILD/$$TARGET; \
|
||||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install \
|
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install \
|
||||||
INSTALL_PATH=$(INSTALL_PATH)/$$TARGET \
|
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…
Add table
Reference in a new issue