From: Petr Machata <petrm@nvidia.com>
To: Jakub Kicinski <kuba@kernel.org>
Cc: <davem@davemloft.net>, <netdev@vger.kernel.org>,
<edumazet@google.com>, <pabeni@redhat.com>, <shuah@kernel.org>,
<sdf@google.com>, <donald.hunter@gmail.com>,
<linux-kselftest@vger.kernel.org>, <petrm@nvidia.com>
Subject: Re: [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python
Date: Tue, 2 Apr 2024 17:53:41 +0200 [thread overview]
Message-ID: <87il0zith6.fsf@nvidia.com> (raw)
In-Reply-To: <20240402010520.1209517-4-kuba@kernel.org>
Jakub Kicinski <kuba@kernel.org> writes:
> 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.
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
> CC: shuah@kernel.org
> CC: linux-kselftest@vger.kernel.org
> ---
> tools/testing/selftests/Makefile | 7 ++
> tools/testing/selftests/net/lib/Makefile | 8 ++
> .../testing/selftests/net/lib/py/__init__.py | 6 ++
> tools/testing/selftests/net/lib/py/consts.py | 9 ++
> tools/testing/selftests/net/lib/py/ksft.py | 96 +++++++++++++++++++
> tools/testing/selftests/net/lib/py/utils.py | 47 +++++++++
> tools/testing/selftests/net/lib/py/ynl.py | 49 ++++++++++
> 7 files changed, 222 insertions(+)
> create mode 100644 tools/testing/selftests/net/lib/Makefile
> create mode 100644 tools/testing/selftests/net/lib/py/__init__.py
> create mode 100644 tools/testing/selftests/net/lib/py/consts.py
> create mode 100644 tools/testing/selftests/net/lib/py/ksft.py
> create mode 100644 tools/testing/selftests/net/lib/py/utils.py
> create mode 100644 tools/testing/selftests/net/lib/py/ynl.py
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index e1504833654d..0cffdfb4b116 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -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)),)
> + override TARGETS := $(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.
> diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile
> new file mode 100644
> index 000000000000..5730682aeffb
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +TEST_FILES := ../../../../../Documentation/netlink/s*
> +TEST_FILES += ../../../../net/*
> +
> +TEST_INCLUDES := $(wildcard py/*.py)
> +
> +include ../../lib.mk
> diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
> new file mode 100644
> index 000000000000..81a8d14b68f0
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/__init__.py
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +from .ksft import *
> +from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
> +from .consts import KSRC
> +from .utils import *
> diff --git a/tools/testing/selftests/net/lib/py/consts.py b/tools/testing/selftests/net/lib/py/consts.py
> new file mode 100644
> index 000000000000..f518ce79d82c
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/consts.py
> @@ -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
> diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py
> new file mode 100644
> index 000000000000..7c296fe5e438
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/ksft.py
> @@ -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
Hmm, instead of this global KSFT_RESULT business, have you considered
adding and raising an XsftFailEx, like for the other outcomes? We need
to use KSFT_RESULT-like approach in bash tests, because, well, bash.
Doing it all through exceptions likely requires consistent use of
context managers for resource clean-up. But if we do, we'll get
guaranteed cleanups as well. I see that you use __del__ and explicit
"finally: del cfg" later on, which is exactly the sort of lifetime
management boilerplate that context managers encapsulate.
This stuff is going to get cut'n'pasted around, and I worry we'll end up
with a mess of mutable globals and forgotten cleanups if the right
patterns are not introduced early on.
> + 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):
> + 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()
> + 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"
> + )
> diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py
> new file mode 100644
> index 000000000000..f0d425731fd4
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/utils.py
> @@ -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
> diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py
> new file mode 100644
> index 000000000000..298bbc6b93c5
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/ynl.py
> @@ -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 = Path(KSRC) / "tools"
> + SPEC_PATH = Path(KSRC) / "Documentation/netlink/specs"
I think KSRC is already Path? So it should be enough to just say KSRC /
"tools", like above with KSFT_DIR?
> + 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='')
next prev parent reply other threads:[~2024-04-02 17:08 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-02 1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
2024-04-02 1:05 ` [PATCH net-next 1/7] netlink: specs: define ethtool header flags Jakub Kicinski
2024-04-02 1:05 ` [PATCH net-next 2/7] tools: ynl: copy netlink error to NlError Jakub Kicinski
2024-04-02 1:05 ` [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python Jakub Kicinski
2024-04-02 15:53 ` Petr Machata [this message]
2024-04-02 17:26 ` Jakub Kicinski
2024-04-03 0:09 ` David Wei
2024-04-02 1:05 ` [PATCH net-next 4/7] selftests: nl_netdev: add a trivial Netlink netdev test Jakub Kicinski
2024-04-03 0:15 ` David Wei
2024-04-02 1:05 ` [PATCH net-next 5/7] netdevsim: report stats by default, like a real device Jakub Kicinski
2024-04-03 2:51 ` David Wei
2024-04-02 1:05 ` [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python Jakub Kicinski
2024-04-03 3:06 ` David Wei
2024-04-02 1:05 ` [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting Jakub Kicinski
2024-04-02 16:37 ` Petr Machata
2024-04-02 17:31 ` Jakub Kicinski
2024-04-02 17:44 ` Jakub Kicinski
2024-04-02 22:02 ` Petr Machata
2024-04-02 22:04 ` Petr Machata
2024-04-02 23:36 ` Jakub Kicinski
2024-04-03 8:58 ` Petr Machata
2024-04-03 13:55 ` Jakub Kicinski
2024-04-03 16:52 ` Petr Machata
2024-04-03 21:48 ` Jakub Kicinski
2024-04-03 3:15 ` David Wei
2024-04-03 3:09 ` David Wei
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87il0zith6.fsf@nvidia.com \
--to=petrm@nvidia.com \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sdf@google.com \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.