All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests
@ 2024-04-02  1:05 Jakub Kicinski
  2024-04-02  1:05 ` [PATCH net-next 1/7] netlink: specs: define ethtool header flags Jakub Kicinski
                   ` (6 more replies)
  0 siblings, 7 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

Currently the options for writing networking tests are C, bash or
some mix of the two. YAML/Netlink gives us the ability to easily
interface with Netlink in higher level laguages. In particular,
there is a Python library already available in tree, under tools/net.
Add the scaffolding which allows writing tests using this library.

The "scaffolding" is needed because the library lives under
tools/net and uses YAML files from under Documentation/.
So we need a small amount of glue code to find those things
and add them to TEST_FILES.

This series adds both a basic SW sanity test and driver
test which can be run against netdevsim or a real device.
When I develop core code I usually test with netdevsim,
then a real device, and then a backport to Meta's kernel.
Because of the lack of integration, until now I had
to throw away the (YNL-based) test script and netdevsim code.

Running tests in tree directly:

 $ ./tools/testing/selftests/net/nl_netdev.py
 KTAP version 1
 1..2
 ok 1 nl_netdev.empty_check
 ok 2 nl_netdev.lo_check
 # Totals: pass:2 fail:0 xfail:0 xpass:0 skip:0 error:0

in tree via make:

 $ make -C tools/testing/selftests/ TARGETS=net \
	TEST_PROGS=nl_netdev.py TEST_GEN_PROGS="" run_tests
  [ ... ]

and installed externally, all seem to work:

 $ make -C tools/testing/selftests/ TARGETS=net \
	install INSTALL_PATH=/tmp/ksft-net
 $ /tmp/ksft-net/run_kselftest.sh -t net:nl_netdev.py
  [ ... ]

For driver tests I followed the lead of net/forwarding and
get the device name from env and/or a config file.

Jakub Kicinski (7):
  netlink: specs: define ethtool header flags
  tools: ynl: copy netlink error to NlError
  selftests: net: add scaffolding for Netlink tests in Python
  selftests: nl_netdev: add a trivial Netlink netdev test
  netdevsim: report stats by default, like a real device
  selftests: drivers: add scaffolding for Netlink tests in Python
  testing: net-drv: add a driver test for stats reporting

 Documentation/netlink/specs/ethtool.yaml      |   5 +
 drivers/net/netdevsim/ethtool.c               |  11 ++
 drivers/net/netdevsim/netdev.c                |  45 +++++++
 tools/net/ynl/lib/ynl.py                      |   3 +-
 tools/testing/selftests/Makefile              |   8 ++
 tools/testing/selftests/drivers/net/Makefile  |   7 ++
 .../testing/selftests/drivers/net/README.rst  |  30 +++++
 .../selftests/drivers/net/lib/py/__init__.py  |  17 +++
 .../selftests/drivers/net/lib/py/env.py       |  41 ++++++
 tools/testing/selftests/drivers/net/stats.py  |  85 +++++++++++++
 tools/testing/selftests/net/Makefile          |   1 +
 tools/testing/selftests/net/lib/Makefile      |   8 ++
 .../testing/selftests/net/lib/py/__init__.py  |   7 ++
 tools/testing/selftests/net/lib/py/consts.py  |   9 ++
 tools/testing/selftests/net/lib/py/ksft.py    |  96 ++++++++++++++
 tools/testing/selftests/net/lib/py/nsim.py    | 118 ++++++++++++++++++
 tools/testing/selftests/net/lib/py/utils.py   |  47 +++++++
 tools/testing/selftests/net/lib/py/ynl.py     |  49 ++++++++
 tools/testing/selftests/net/nl_netdev.py      |  24 ++++
 19 files changed, 610 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/drivers/net/Makefile
 create mode 100644 tools/testing/selftests/drivers/net/README.rst
 create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py
 create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py
 create mode 100755 tools/testing/selftests/drivers/net/stats.py
 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/nsim.py
 create mode 100644 tools/testing/selftests/net/lib/py/utils.py
 create mode 100644 tools/testing/selftests/net/lib/py/ynl.py
 create mode 100755 tools/testing/selftests/net/nl_netdev.py

-- 
2.44.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH net-next 1/7] netlink: specs: define ethtool header flags
  2024-04-02  1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
@ 2024-04-02  1:05 ` Jakub Kicinski
  2024-04-02  1:05 ` [PATCH net-next 2/7] tools: ynl: copy netlink error to NlError Jakub Kicinski
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

When interfacing with the ethtool commands it's handy to
be able to use the names of the flags. Example:

    ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
                                "flags": {'stats'}}})

Note that not all commands accept all the flags,
but the meaning of the bits does not change command
to command.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 Documentation/netlink/specs/ethtool.yaml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 197208f419dc..616bb3eb545f 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -16,6 +16,10 @@ doc: Partial family for Ethtool Netlink.
     name: stringset
     type: enum
     entries: []
+  -
+    name: header-flags
+    type: flags
+    entries: [ compact-bitsets, omit-reply, stats ]
 
 attribute-sets:
   -
@@ -30,6 +34,7 @@ doc: Partial family for Ethtool Netlink.
       -
         name: flags
         type: u32
+        enum: header-flags
 
   -
     name: bitset-bit
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH net-next 2/7] tools: ynl: copy netlink error to NlError
  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 ` Jakub Kicinski
  2024-04-02  1:05 ` [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python Jakub Kicinski
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski, jiri

Typing e.nl_msg.error when processing exception is a bit tedious
and counter-intuitive. Set a local .error member to the positive
value of the netlink level error.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
CC: donald.hunter@gmail.com
CC: jiri@resnulli.us
---
 tools/net/ynl/lib/ynl.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tools/net/ynl/lib/ynl.py b/tools/net/ynl/lib/ynl.py
index 82d3c98067aa..b30210f537f7 100644
--- a/tools/net/ynl/lib/ynl.py
+++ b/tools/net/ynl/lib/ynl.py
@@ -100,9 +100,10 @@ from .nlspec import SpecFamily
 class NlError(Exception):
   def __init__(self, nl_msg):
     self.nl_msg = nl_msg
+    self.error = -nl_msg.error
 
   def __str__(self):
-    return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}"
+    return f"Netlink error: {os.strerror(self.error)}\n{self.nl_msg}"
 
 
 class ConfigError(Exception):
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python
  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 ` Jakub Kicinski
  2024-04-02 15:53   ` Petr Machata
  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
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

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
+        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"
+
+        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='')
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH net-next 4/7] selftests: nl_netdev: add a trivial Netlink netdev test
  2024-04-02  1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
                   ` (2 preceding siblings ...)
  2024-04-02  1:05 ` [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python Jakub Kicinski
@ 2024-04-02  1:05 ` 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
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

Add a trivial test using YNL.

  $ ./tools/testing/selftests/net/nl_netdev.py
  KTAP version 1
  1..2
  ok 1 nl_netdev.empty_check
  ok 2 nl_netdev.lo_check

Instantiate the family once, it takes longer than the test itself.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
CC: shuah@kernel.org
CC: linux-kselftest@vger.kernel.org
---
 tools/testing/selftests/net/Makefile     |  1 +
 tools/testing/selftests/net/nl_netdev.py | 24 ++++++++++++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100755 tools/testing/selftests/net/nl_netdev.py

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index cb418a2346bc..5e34c93aa51b 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -34,6 +34,7 @@ TEST_PROGS += gre_gso.sh
 TEST_PROGS += cmsg_so_mark.sh
 TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
 TEST_PROGS += netns-name.sh
+TEST_PROGS += nl_netdev.py
 TEST_PROGS += srv6_end_dt46_l3vpn_test.sh
 TEST_PROGS += srv6_end_dt4_l3vpn_test.sh
 TEST_PROGS += srv6_end_dt6_l3vpn_test.sh
diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
new file mode 100755
index 000000000000..40a59857f984
--- /dev/null
+++ b/tools/testing/selftests/net/nl_netdev.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+from lib.py import ksft_run, ksft_pr, ksft_eq, ksft_ge, NetdevFamily
+
+
+nf = NetdevFamily()
+
+
+def empty_check() -> None:
+    global nf
+    devs = nf.dev_get({}, dump=True)
+    ksft_ge(len(devs), 1)
+
+
+def lo_check() -> None:
+    global nf
+    lo_info = nf.dev_get({"ifindex": 1})
+    ksft_eq(len(lo_info['xdp-features']), 0)
+    ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0)
+
+
+if __name__ == "__main__":
+    ksft_run([empty_check, lo_check])
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH net-next 5/7] netdevsim: report stats by default, like a real device
  2024-04-02  1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
                   ` (3 preceding siblings ...)
  2024-04-02  1:05 ` [PATCH net-next 4/7] selftests: nl_netdev: add a trivial Netlink netdev test Jakub Kicinski
@ 2024-04-02  1:05 ` 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-02  1:05 ` [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting Jakub Kicinski
  6 siblings, 1 reply; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

Real devices should implement qstats. Devices which support
pause or FEC configuration should also report the relevant stats.

nsim was missing FEC stats completely, some of the qstats
and pause stats required toggling a debugfs knob.

Note that the tests which used pause always initialize the setting
so they shouldn't be affected by the different starting value.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 drivers/net/netdevsim/ethtool.c | 11 ++++++++
 drivers/net/netdevsim/netdev.c  | 45 +++++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+)

diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index bd546d4d26c6..3f9c9327f149 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -140,6 +140,13 @@ nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
 	return 0;
 }
 
+static void
+nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats)
+{
+	fec_stats->corrected_blocks.total = 123;
+	fec_stats->uncorrectable_blocks.total = 4;
+}
+
 static int nsim_get_ts_info(struct net_device *dev,
 			    struct ethtool_ts_info *info)
 {
@@ -163,6 +170,7 @@ static const struct ethtool_ops nsim_ethtool_ops = {
 	.set_channels			= nsim_set_channels,
 	.get_fecparam			= nsim_get_fecparam,
 	.set_fecparam			= nsim_set_fecparam,
+	.get_fec_stats			= nsim_get_fec_stats,
 	.get_ts_info			= nsim_get_ts_info,
 };
 
@@ -182,6 +190,9 @@ void nsim_ethtool_init(struct netdevsim *ns)
 
 	nsim_ethtool_ring_init(ns);
 
+	ns->ethtool.pauseparam.report_stats_rx = true;
+	ns->ethtool.pauseparam.report_stats_tx = true;
+
 	ns->ethtool.fec.fec = ETHTOOL_FEC_NONE;
 	ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE;
 
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index 8330bc0bcb7e..096ac0abbc02 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -19,6 +19,7 @@
 #include <linux/module.h>
 #include <linux/netdevice.h>
 #include <linux/slab.h>
+#include <net/netdev_queues.h>
 #include <net/netlink.h>
 #include <net/pkt_cls.h>
 #include <net/rtnetlink.h>
@@ -330,6 +331,49 @@ static const struct net_device_ops nsim_vf_netdev_ops = {
 	.ndo_set_features	= nsim_set_features,
 };
 
+/* We don't have true par-queue stats, yet, so do some random fakery here. */
+static void nsim_get_queue_stats_rx(struct net_device *dev, int idx,
+				    struct netdev_queue_stats_rx *stats)
+{
+	struct rtnl_link_stats64 rtstats = {};
+
+	nsim_get_stats64(dev, &rtstats);
+
+	stats->packets = rtstats.rx_packets - !!rtstats.rx_packets;
+	stats->bytes = rtstats.rx_bytes;
+}
+
+static void nsim_get_queue_stats_tx(struct net_device *dev, int idx,
+				    struct netdev_queue_stats_tx *stats)
+{
+	struct rtnl_link_stats64 rtstats = {};
+
+	nsim_get_stats64(dev, &rtstats);
+
+	stats->packets = rtstats.tx_packets - !!rtstats.tx_packets;
+	stats->bytes = rtstats.tx_bytes;
+}
+
+static void nsim_get_base_stats(struct net_device *dev,
+				struct netdev_queue_stats_rx *rx,
+				struct netdev_queue_stats_tx *tx)
+{
+	struct rtnl_link_stats64 rtstats = {};
+
+	nsim_get_stats64(dev, &rtstats);
+
+	rx->packets = !!rtstats.rx_packets;
+	rx->bytes = 0;
+	tx->packets = !!rtstats.tx_packets;
+	tx->bytes = 0;
+}
+
+static const struct netdev_stat_ops nsim_stat_ops = {
+	.get_queue_stats_tx	= nsim_get_queue_stats_tx,
+	.get_queue_stats_rx	= nsim_get_queue_stats_rx,
+	.get_base_stats		= nsim_get_base_stats,
+};
+
 static void nsim_setup(struct net_device *dev)
 {
 	ether_setup(dev);
@@ -360,6 +404,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
 
 	ns->phc = phc;
 	ns->netdev->netdev_ops = &nsim_netdev_ops;
+	ns->netdev->stat_ops = &nsim_stat_ops;
 
 	err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev);
 	if (err)
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python
  2024-04-02  1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
                   ` (4 preceding siblings ...)
  2024-04-02  1:05 ` [PATCH net-next 5/7] netdevsim: report stats by default, like a real device Jakub Kicinski
@ 2024-04-02  1:05 ` 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
  6 siblings, 1 reply; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

Add drivers/net as a target for mixed-use tests.
The setup is expected to work similarly to the forwarding tests.
Since we only need one interface (unlike forwarding tests)
read the target device name from NETIF. If not present we'll
try to run the test against netdevsim.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/testing/selftests/Makefile              |   3 +-
 tools/testing/selftests/drivers/net/Makefile  |   7 ++
 .../testing/selftests/drivers/net/README.rst  |  30 +++++
 .../selftests/drivers/net/lib/py/__init__.py  |  17 +++
 .../selftests/drivers/net/lib/py/env.py       |  41 ++++++
 .../testing/selftests/net/lib/py/__init__.py  |   1 +
 tools/testing/selftests/net/lib/py/nsim.py    | 118 ++++++++++++++++++
 7 files changed, 216 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/drivers/net/Makefile
 create mode 100644 tools/testing/selftests/drivers/net/README.rst
 create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py
 create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py
 create mode 100644 tools/testing/selftests/net/lib/py/nsim.py

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 0cffdfb4b116..d015ec14a85e 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -17,6 +17,7 @@ TARGETS += devices
 TARGETS += dmabuf-heaps
 TARGETS += drivers/dma-buf
 TARGETS += drivers/s390x/uvdevice
+TARGETS += drivers/net
 TARGETS += drivers/net/bonding
 TARGETS += drivers/net/team
 TARGETS += dt
@@ -117,7 +118,7 @@ TARGETS_HOTPLUG = cpu-hotplug
 TARGETS_HOTPLUG += memory-hotplug
 
 # Networking tests want the net/lib target, include it automatically
-ifneq ($(filter net ,$(TARGETS)),)
+ifneq ($(filter net drivers/net,$(TARGETS)),)
 ifeq ($(filter net/lib,$(TARGETS)),)
 	override TARGETS := $(TARGETS) net/lib
 endif
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
new file mode 100644
index 000000000000..379cdb1960a7
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_INCLUDES := $(wildcard lib/py/*.py)
+
+TEST_PROGS := stats.py
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
new file mode 100644
index 000000000000..5ef7c417d431
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/README.rst
@@ -0,0 +1,30 @@
+Running tests
+=============
+
+Tests are executed within kselftest framework like any other tests.
+By default tests execute against software drivers such as netdevsim.
+All tests must support running against a real device (SW-only tests
+should instead be placed in net/ or drivers/net/netdevsim, HW-only
+tests in drivers/net/hw).
+
+Set appropriate variables to point the tests at a real device.
+
+Variables
+=========
+
+Variables can be set in the environment or by creating a net.config
+file in the same directory as this README file. Example::
+
+  $ NETIF=eth0 ./some_test.sh
+
+or::
+
+  $ cat tools/testing/selftests/drivers/net/net.config
+  # Variable set in a file
+  NETIF=eth0
+
+NETIF
+~~~~~
+
+Name of the netdevice against which the test should be executed.
+When empty or not set software devices will be used.
diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
new file mode 100644
index 000000000000..4653dffcd962
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import sys
+from pathlib import Path
+
+KSFT_DIR = (Path(__file__).parent / "../../../..").resolve()
+
+try:
+    sys.path.append(KSFT_DIR.as_posix())
+    from net.lib.py import *
+except ModuleNotFoundError as e:
+    ksft_pr("Failed importing `net` library from kernel sources")
+    ksft_pr(str(e))
+    ktap_result(True, comment="SKIP")
+    sys.exit(4)
+
+from .env import *
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
new file mode 100644
index 000000000000..ee4a44555d83
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import os
+import shlex
+from pathlib import Path
+from lib.py import ip
+from lib.py import NetdevSimDev
+
+class NetDrvEnv:
+    def __init__(self, src_path):
+        self.env = os.environ.copy()
+        self._load_env_file(src_path)
+
+        if 'NETIF' in self.env:
+            self._ns = None
+            self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0]
+        else:
+            self._ns = NetdevSimDev()
+            self.dev = self._ns.nsims[0].dev
+        self.ifindex = self.dev['ifindex']
+
+    def __del__(self):
+        if self._ns:
+            self._ns.remove()
+
+    def _load_env_file(self, src_path):
+        src_dir = Path(src_path).parent.resolve()
+        if not (src_dir / "net.config").exists():
+            return
+
+        lexer = shlex.shlex(open((src_dir / "net.config").as_posix(), 'r').read())
+        k = None
+        for token in lexer:
+            if k is None:
+                k = token
+                self.env[k] = ""
+            elif token == "=":
+                pass
+            else:
+                self.env[k] = token
+                k = None
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 81a8d14b68f0..99cfc8dc4dca 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -3,4 +3,5 @@
 from .ksft import *
 from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
 from .consts import KSRC
+from .nsim import *
 from .utils import *
diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py
new file mode 100644
index 000000000000..13eb42c82829
--- /dev/null
+++ b/tools/testing/selftests/net/lib/py/nsim.py
@@ -0,0 +1,118 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import json
+import os
+import random
+import re
+import time
+from .utils import cmd, ip
+
+
+class NetdevSim:
+    """
+    Class for netdevsim netdevice and its attributes.
+    """
+
+    def __init__(self, nsimdev, port_index, ifname, ns=None):
+        # In case udev renamed the netdev to according to new schema,
+        # check if the name matches the port_index.
+        nsimnamere = re.compile("eni\d+np(\d+)")
+        match = nsimnamere.match(ifname)
+        if match and int(match.groups()[0]) != port_index + 1:
+            raise Exception("netdevice name mismatches the expected one")
+
+        self.ifname = ifname
+        self.nsimdev = nsimdev
+        self.port_index = port_index
+        self.ns = ns
+        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
+        ret = ip("-j link show dev %s" % ifname, ns=ns)
+        self.dev = json.loads(ret.stdout)[0]
+
+    def dfs_write(self, path, val):
+        self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val)
+
+
+class NetdevSimDev:
+    """
+    Class for netdevsim bus device and its attributes.
+    """
+    @staticmethod
+    def ctrl_write(path, val):
+        fullpath = os.path.join("/sys/bus/netdevsim/", path)
+        with open(fullpath, "w") as f:
+            f.write(val)
+
+    def dfs_write(self, path, val):
+        fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path)
+        with open(fullpath, "w") as f:
+            f.write(val)
+
+    def __init__(self, port_count=1, ns=None):
+        # nsim will spawn in init_net, we'll set to actual ns once we switch it the.sre
+        self.ns = None
+
+        if not os.path.exists("/sys/bus/netdevsim"):
+            cmd("modprobe netdevsim")
+
+        addr = random.randrange(1 << 15)
+        while True:
+            try:
+                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
+            except OSError as e:
+                if e.errno == errno.ENOSPC:
+                    addr = random.randrange(1 << 15)
+                    continue
+                raise e
+            break
+        self.addr = addr
+
+        # As probe of netdevsim device might happen from a workqueue,
+        # so wait here until all netdevs appear.
+        self.wait_for_netdevs(port_count)
+
+        if ns:
+            cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}")
+            self.ns = ns
+
+        cmd("udevadm settle", ns=self.ns)
+        ifnames = self.get_ifnames()
+
+        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
+
+        self.nsims = []
+        for port_index in range(port_count):
+            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index],
+                                        ns=ns))
+
+    def get_ifnames(self):
+        ifnames = []
+        listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/",
+                      ns=self.ns).stdout.split()
+        for ifname in listdir:
+            ifnames.append(ifname)
+        ifnames.sort()
+        return ifnames
+
+    def wait_for_netdevs(self, port_count):
+        timeout = 5
+        timeout_start = time.time()
+
+        while True:
+            try:
+                ifnames = self.get_ifnames()
+            except FileNotFoundError as e:
+                ifnames = []
+            if len(ifnames) == port_count:
+                break
+            if time.time() < timeout_start + timeout:
+                continue
+            raise Exception("netdevices did not appear within timeout")
+
+    def remove(self):
+        self.ctrl_write("del_device", "%u" % (self.addr, ))
+
+    def remove_nsim(self, nsim):
+        self.nsims.remove(nsim)
+        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
+                        "%u" % (nsim.port_index, ))
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02  1:05 [PATCH net-next 0/7] selftests: net: groundwork for YNL-based tests Jakub Kicinski
                   ` (5 preceding siblings ...)
  2024-04-02  1:05 ` [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python Jakub Kicinski
@ 2024-04-02  1:05 ` Jakub Kicinski
  2024-04-02 16:37   ` Petr Machata
  2024-04-03  3:09   ` David Wei
  6 siblings, 2 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02  1:05 UTC (permalink / raw)
  To: davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm, Jakub Kicinski

Add a very simple test to make sure drivers report expected
stats. Drivers which implement FEC or pause configuration
should report relevant stats. Qstats must be reported,
at least packet and byte counts, and they must match
total device stats.

Tested with netdevsim, bnxt, in-tree and installed.

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
 tools/testing/selftests/drivers/net/stats.py | 85 ++++++++++++++++++++
 1 file changed, 85 insertions(+)
 create mode 100755 tools/testing/selftests/drivers/net/stats.py

diff --git a/tools/testing/selftests/drivers/net/stats.py b/tools/testing/selftests/drivers/net/stats.py
new file mode 100755
index 000000000000..751cca2869b8
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/stats.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+from lib.py import ksft_run, ksft_in, ksft_true, KsftSkipEx, KsftXfailEx
+from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
+from lib.py import NetDrvEnv
+
+cfg = None
+ethnl = EthtoolFamily()
+netfam = NetdevFamily()
+rtnl = RtnlFamily()
+
+
+def check_pause() -> None:
+    global cfg, ethnl
+
+    try:
+        ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftXfailEx("pause not supported by the device")
+        raise
+
+    data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
+                                       "flags": {'stats'}}})
+    ksft_true(data['stats'], "driver does not report stats")
+
+
+def check_fec() -> None:
+    global ethnl
+
+    try:
+        ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
+    except NlError as e:
+        if e.error == 95:
+            raise KsftXfailEx("FEC not supported by the device")
+        raise
+
+    data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
+                                     "flags": {'stats'}}})
+    ksft_true(data['stats'], "driver does not report stats")
+
+
+def pkt_byte_sum() -> None:
+    global cfg, netfam, rtnl
+
+    def get_qstat(test):
+        global netfam
+        stats = netfam.qstats_get({}, dump=True)
+        if stats:
+            for qs in stats:
+                if qs["ifindex"]== test.ifindex:
+                    return qs
+
+    qstat = get_qstat(cfg)
+    if qstat is None:
+        raise KsftSkipEx("qstats not supported by the device")
+
+    for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
+        ksft_in(key, qstat, "Drivers should always report basic keys")
+
+    # Compare stats, rtnl stats and qstats must match,
+    # but the interface may be up, so do a series of dumps
+    # each time the more "recent" stats must be higher or same.
+    def stat_cmp(rstat, qstat):
+        for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
+            if rstat[key] != qstat[key]:
+                return rstat[key] - qstat[key]
+        return 0
+
+    for _ in range(10):
+        rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats']
+        if stat_cmp(rtstat, qstat) < 0:
+            raise Exception("RTNL stats are lower, fetched later")
+        qstat = get_qstat(cfg)
+        if stat_cmp(rtstat, qstat) > 0:
+            raise Exception("Qstats are lower, fetched later")
+
+
+if __name__ == "__main__":
+    cfg = NetDrvEnv(__file__)
+    try:
+        ksft_run([check_pause, check_fec, pkt_byte_sum])
+    finally:
+        del cfg
-- 
2.44.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python
  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
  2024-04-02 17:26     ` Jakub Kicinski
  2024-04-03  0:09   ` David Wei
  1 sibling, 1 reply; 26+ messages in thread
From: Petr Machata @ 2024-04-02 15:53 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm


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='')


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  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-03  3:09   ` David Wei
  1 sibling, 1 reply; 26+ messages in thread
From: Petr Machata @ 2024-04-02 16:37 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm


Jakub Kicinski <kuba@kernel.org> writes:

> Add a very simple test to make sure drivers report expected
> stats. Drivers which implement FEC or pause configuration
> should report relevant stats. Qstats must be reported,
> at least packet and byte counts, and they must match
> total device stats.
>
> Tested with netdevsim, bnxt, in-tree and installed.
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/testing/selftests/drivers/net/stats.py | 85 ++++++++++++++++++++
>  1 file changed, 85 insertions(+)
>  create mode 100755 tools/testing/selftests/drivers/net/stats.py
>
> diff --git a/tools/testing/selftests/drivers/net/stats.py b/tools/testing/selftests/drivers/net/stats.py
> new file mode 100755
> index 000000000000..751cca2869b8
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/stats.py
> @@ -0,0 +1,85 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +from lib.py import ksft_run, ksft_in, ksft_true, KsftSkipEx, KsftXfailEx
> +from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
> +from lib.py import NetDrvEnv
> +
> +cfg = None
> +ethnl = EthtoolFamily()
> +netfam = NetdevFamily()
> +rtnl = RtnlFamily()
> +
> +
> +def check_pause() -> None:
> +    global cfg, ethnl
> +
> +    try:
> +        ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftXfailEx("pause not supported by the device")
> +        raise
> +
> +    data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
> +                                       "flags": {'stats'}}})
> +    ksft_true(data['stats'], "driver does not report stats")
> +
> +
> +def check_fec() -> None:
> +    global ethnl
> +
> +    try:
> +        ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftXfailEx("FEC not supported by the device")
> +        raise
> +
> +    data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
> +                                     "flags": {'stats'}}})
> +    ksft_true(data['stats'], "driver does not report stats")
> +
> +
> +def pkt_byte_sum() -> None:
> +    global cfg, netfam, rtnl
> +
> +    def get_qstat(test):
> +        global netfam
> +        stats = netfam.qstats_get({}, dump=True)
> +        if stats:
> +            for qs in stats:
> +                if qs["ifindex"]== test.ifindex:
> +                    return qs
> +
> +    qstat = get_qstat(cfg)
> +    if qstat is None:
> +        raise KsftSkipEx("qstats not supported by the device")
> +
> +    for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
> +        ksft_in(key, qstat, "Drivers should always report basic keys")
> +
> +    # Compare stats, rtnl stats and qstats must match,
> +    # but the interface may be up, so do a series of dumps
> +    # each time the more "recent" stats must be higher or same.
> +    def stat_cmp(rstat, qstat):
> +        for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
> +            if rstat[key] != qstat[key]:
> +                return rstat[key] - qstat[key]
> +        return 0
> +
> +    for _ in range(10):
> +        rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats']
> +        if stat_cmp(rtstat, qstat) < 0:
> +            raise Exception("RTNL stats are lower, fetched later")
> +        qstat = get_qstat(cfg)
> +        if stat_cmp(rtstat, qstat) > 0:
> +            raise Exception("Qstats are lower, fetched later")
> +
> +
> +if __name__ == "__main__":
> +    cfg = NetDrvEnv(__file__)
> +    try:
> +        ksft_run([check_pause, check_fec, pkt_byte_sum])
> +    finally:
> +        del cfg

Yeah, this would be usually done through context managers, as I mention
in the other e-mail. But then cfg would be lexically scoped, which IMHO
is a good thing, but then it needs to be passed around as an argument,
and that makes the ksft_run() invocation a bit messy:

    with NetDrvEnv(__file__) as cfg:
        ksft_run([lambda: check_pause(cfg),
                  lambda: check_fec(cfg),
                  lambda: pkt_byte_sum(cfg)])

Dunno, maybe it could forward *args **kwargs to the cases? But then it
loses some of the readability again.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python
  2024-04-02 15:53   ` Petr Machata
@ 2024-04-02 17:26     ` Jakub Kicinski
  0 siblings, 0 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02 17:26 UTC (permalink / raw)
  To: Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On Tue, 2 Apr 2024 17:53:41 +0200 Petr Machata wrote:
> > +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.

I wanted to support the semantics which the C kselftest harness has,
by which I mean EXPECT and ASSERT. The helpers don't raise, just record
the failure and keep going. ASSERT semantics are provided by the
exceptions.

I thought it may be easier to follow and write correct code if we raise
ASSERTS explicitly in the test, rather than in the helpers. I mean - if 
the programmer has to type in "raise" they are more likely to realize
they need to also add cleanup.

But TBH I'm happy to be persuaded otherwise, I couldn't find a strong
reason to do it one way or the other. I have tried to integrate with
unittest and that wasn't great (I have one huge test I need to port
back now). I don't know if pytest is better, but I decided that we
should probably roll our own. What "our own" exactly is I don't have
strong opinion.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02 16:37   ` Petr Machata
@ 2024-04-02 17:31     ` Jakub Kicinski
  2024-04-02 17:44       ` Jakub Kicinski
                         ` (2 more replies)
  0 siblings, 3 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02 17:31 UTC (permalink / raw)
  To: Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On Tue, 2 Apr 2024 18:37:44 +0200 Petr Machata wrote:
> Yeah, this would be usually done through context managers, as I mention
> in the other e-mail. But then cfg would be lexically scoped, which IMHO
> is a good thing, but then it needs to be passed around as an argument,
> and that makes the ksft_run() invocation a bit messy:
> 
>     with NetDrvEnv(__file__) as cfg:
>         ksft_run([lambda: check_pause(cfg),
>                   lambda: check_fec(cfg),
>                   lambda: pkt_byte_sum(cfg)])
> 
> Dunno, maybe it could forward *args **kwargs to the cases? But then it
> loses some of the readability again.

Yes, I was wondering about that. It must be doable, IIRC 
the multi-threading API "injects" args from a tuple.
I was thinking something along the lines of:

    with NetDrvEnv(__file__) as cfg:
        ksft_run([check_pause, check_fec, pkt_byte_sum],
                 args=(cfg, ))

I got lazy, let me take a closer look. Another benefit
will be that once we pass in "env" / cfg - we can "register" 
objects in there for auto-cleanup (in the future, current
tests don't need cleanup)

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  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-03  3:15       ` David Wei
  2 siblings, 1 reply; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02 17:44 UTC (permalink / raw)
  To: Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On Tue, 2 Apr 2024 10:31:11 -0700 Jakub Kicinski wrote:
> Yes, I was wondering about that. It must be doable, IIRC 
> the multi-threading API "injects" args from a tuple.
> I was thinking something along the lines of:
> 
>     with NetDrvEnv(__file__) as cfg:
>         ksft_run([check_pause, check_fec, pkt_byte_sum],
>                  args=(cfg, ))

seems to work, is this good?

diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py
index 7c296fe5e438..c7210525981c 100644
--- a/tools/testing/selftests/net/lib/py/ksft.py
+++ b/tools/testing/selftests/net/lib/py/ksft.py
@@ -60,7 +60,7 @@ KSFT_RESULT = None
     print(res)
 
 
-def ksft_run(cases):
+def ksft_run(cases, args=()):
     totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
 
     print("KTAP version 1")
@@ -72,7 +72,7 @@ KSFT_RESULT = None
         KSFT_RESULT = True
         cnt += 1
         try:
-            case()
+            case(*args)
         except KsftSkipEx as e:
             ktap_result(True, cnt, case, comment="SKIP " + str(e))
             totals['skip'] += 1

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02 17:44       ` Jakub Kicinski
@ 2024-04-02 22:02         ` Petr Machata
  0 siblings, 0 replies; 26+ messages in thread
From: Petr Machata @ 2024-04-02 22:02 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Petr Machata, davem, netdev, edumazet, pabeni, shuah, sdf,
	donald.hunter, linux-kselftest


Jakub Kicinski <kuba@kernel.org> writes:

> On Tue, 2 Apr 2024 10:31:11 -0700 Jakub Kicinski wrote:
>> Yes, I was wondering about that. It must be doable, IIRC 
>> the multi-threading API "injects" args from a tuple.
>> I was thinking something along the lines of:
>> 
>>     with NetDrvEnv(__file__) as cfg:
>>         ksft_run([check_pause, check_fec, pkt_byte_sum],
>>                  args=(cfg, ))
>
> seems to work, is this good?
>
> diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py
> index 7c296fe5e438..c7210525981c 100644
> --- a/tools/testing/selftests/net/lib/py/ksft.py
> +++ b/tools/testing/selftests/net/lib/py/ksft.py
> @@ -60,7 +60,7 @@ KSFT_RESULT = None
>      print(res)
>  
>  
> -def ksft_run(cases):
> +def ksft_run(cases, args=()):
>      totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
>  
>      print("KTAP version 1")
> @@ -72,7 +72,7 @@ KSFT_RESULT = None
>          KSFT_RESULT = True
>          cnt += 1
>          try:
> -            case()
> +            case(*args)
>          except KsftSkipEx as e:
>              ktap_result(True, cnt, case, comment="SKIP " + str(e))
>              totals['skip'] += 1

Yep, looks good.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02 17:31     ` Jakub Kicinski
  2024-04-02 17:44       ` Jakub Kicinski
@ 2024-04-02 22:04       ` Petr Machata
  2024-04-02 23:36         ` Jakub Kicinski
  2024-04-03  3:15       ` David Wei
  2 siblings, 1 reply; 26+ messages in thread
From: Petr Machata @ 2024-04-02 22:04 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Petr Machata, davem, netdev, edumazet, pabeni, shuah, sdf,
	donald.hunter, linux-kselftest


Jakub Kicinski <kuba@kernel.org> writes:

> On Tue, 2 Apr 2024 18:37:44 +0200 Petr Machata wrote:
>> Yeah, this would be usually done through context managers, as I mention
>> in the other e-mail. But then cfg would be lexically scoped, which IMHO
>> is a good thing, but then it needs to be passed around as an argument,
>> and that makes the ksft_run() invocation a bit messy:
>> 
>>     with NetDrvEnv(__file__) as cfg:
>>         ksft_run([lambda: check_pause(cfg),
>>                   lambda: check_fec(cfg),
>>                   lambda: pkt_byte_sum(cfg)])
>> 
>> Dunno, maybe it could forward *args **kwargs to the cases? But then it
>> loses some of the readability again.
>
> Yes, I was wondering about that. It must be doable, IIRC 
> the multi-threading API "injects" args from a tuple.
> I was thinking something along the lines of:
>
>     with NetDrvEnv(__file__) as cfg:
>         ksft_run([check_pause, check_fec, pkt_byte_sum],
>                  args=(cfg, ))
>
> I got lazy, let me take a closer look. Another benefit
> will be that once we pass in "env" / cfg - we can "register" 
> objects in there for auto-cleanup (in the future, current
> tests don't need cleanup)

Yeah, though some of those should probably just be their own context
managers IMHO, not necessarily hooked to cfg. I'm thinking something
fairly general, so that the support boilerplate doesn't end up costing
an arm and leg:

    with build("ip route add 192.0.2.1/28 nexthop via 192.0.2.17",
               "ip route del 192.0.2.1/28"),
         build("ip link set dev %s master %s" % (swp1, h1),
               "ip link set dev %s nomaster" % swp1):
        le_test()

Dunno. I guess it makes sense to have some of the common stuff
predefined, e.g. "with vrf() as h1". And then the stuff that's typically
in lib.sh's setup() and cleanup(), can be losslessly hooked up to cfg.

This is what I ended up gravitating towards after writing a handful of
LNST tests anyway. The scoping makes it clear where the object exists,
lifetime is taken care of, it's all ponies rainbows basically. At least
as long as your object lifetimes can be cleanly nested, which admittedly
is not always.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02 22:04       ` Petr Machata
@ 2024-04-02 23:36         ` Jakub Kicinski
  2024-04-03  8:58           ` Petr Machata
  0 siblings, 1 reply; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-02 23:36 UTC (permalink / raw)
  To: Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On Wed, 3 Apr 2024 00:04:14 +0200 Petr Machata wrote:
> > Yes, I was wondering about that. It must be doable, IIRC 
> > the multi-threading API "injects" args from a tuple.
> > I was thinking something along the lines of:
> >
> >     with NetDrvEnv(__file__) as cfg:
> >         ksft_run([check_pause, check_fec, pkt_byte_sum],
> >                  args=(cfg, ))
> >
> > I got lazy, let me take a closer look. Another benefit
> > will be that once we pass in "env" / cfg - we can "register" 
> > objects in there for auto-cleanup (in the future, current
> > tests don't need cleanup)  
> 
> Yeah, though some of those should probably just be their own context
> managers IMHO, not necessarily hooked to cfg. I'm thinking something
> fairly general, so that the support boilerplate doesn't end up costing
> an arm and leg:
> 
>     with build("ip route add 192.0.2.1/28 nexthop via 192.0.2.17",
>                "ip route del 192.0.2.1/28"),
>          build("ip link set dev %s master %s" % (swp1, h1),
>                "ip link set dev %s nomaster" % swp1):
>         le_test()
>
> Dunno. I guess it makes sense to have some of the common stuff
> predefined, e.g. "with vrf() as h1". And then the stuff that's typically
> in lib.sh's setup() and cleanup(), can be losslessly hooked up to cfg.

I was thinking of something along the lines of:

def test_abc(cfg):
    cfg.build("ip route add 192.0.2.1/28 nexthop via 192.0.2.17",
              "ip route del 192.0.2.1/28")
    cfg.build("ip link set dev %s master %s" % (swp1, h1),
              "ip link set dev %s nomaster" % swp1)

optionally we could then also:

     thing = cfg.build("ip link set dev %s master %s" % (swp1, h1),
                       "ip link set dev %s nomaster" % swp1)

     # ... some code which may raise ...

     # unlink to do something else with the device
     del thing
     # ... more code ... 

cfg may not be best here, could be cleaner to create a "test" object,
always pass it in as the first param, and destroy it after each test.

> This is what I ended up gravitating towards after writing a handful of
> LNST tests anyway. The scoping makes it clear where the object exists,
> lifetime is taken care of, it's all ponies rainbows basically. At least
> as long as your object lifetimes can be cleanly nested, which admittedly
> is not always.

Should be fairly easy to support all cases - "with", "recording on
cfg/test" and del.  Unfortunately in the two tests I came up with
quickly for this series cleanup is only needed for the env itself.
It's a bit awkward to add the lifetime helpers without any users.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 3/7] selftests: net: add scaffolding for Netlink tests in Python
  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
@ 2024-04-03  0:09   ` David Wei
  1 sibling, 0 replies; 26+ messages in thread
From: David Wei @ 2024-04-03  0:09 UTC (permalink / raw)
  To: Jakub Kicinski, davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm

On 2024-04-01 18:05, Jakub Kicinski wrote:
> 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)),)

style nit: consistency in spacing

> +	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 *

style nit: sort alphabetically

> 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
> +        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"
> +
> +        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='')

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 4/7] selftests: nl_netdev: add a trivial Netlink netdev test
  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
  0 siblings, 0 replies; 26+ messages in thread
From: David Wei @ 2024-04-03  0:15 UTC (permalink / raw)
  To: Jakub Kicinski, davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm

On 2024-04-01 18:05, Jakub Kicinski wrote:
> Add a trivial test using YNL.
> 
>   $ ./tools/testing/selftests/net/nl_netdev.py
>   KTAP version 1
>   1..2
>   ok 1 nl_netdev.empty_check
>   ok 2 nl_netdev.lo_check
> 
> Instantiate the family once, it takes longer than the test itself.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
> CC: shuah@kernel.org
> CC: linux-kselftest@vger.kernel.org
> ---
>  tools/testing/selftests/net/Makefile     |  1 +
>  tools/testing/selftests/net/nl_netdev.py | 24 ++++++++++++++++++++++++
>  2 files changed, 25 insertions(+)
>  create mode 100755 tools/testing/selftests/net/nl_netdev.py
> 
> diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
> index cb418a2346bc..5e34c93aa51b 100644
> --- a/tools/testing/selftests/net/Makefile
> +++ b/tools/testing/selftests/net/Makefile
> @@ -34,6 +34,7 @@ TEST_PROGS += gre_gso.sh
>  TEST_PROGS += cmsg_so_mark.sh
>  TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
>  TEST_PROGS += netns-name.sh
> +TEST_PROGS += nl_netdev.py
>  TEST_PROGS += srv6_end_dt46_l3vpn_test.sh
>  TEST_PROGS += srv6_end_dt4_l3vpn_test.sh
>  TEST_PROGS += srv6_end_dt6_l3vpn_test.sh
> diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py
> new file mode 100755
> index 000000000000..40a59857f984
> --- /dev/null
> +++ b/tools/testing/selftests/net/nl_netdev.py
> @@ -0,0 +1,24 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +from lib.py import ksft_run, ksft_pr, ksft_eq, ksft_ge, NetdevFamily
> +
> +
> +nf = NetdevFamily()
> +
> +
> +def empty_check() -> None:
> +    global nf

I know you're rolling your own instead of using unittest or pytest. How
about adding a Test class of some sort and make each test case a method?
Then you wouldn't need to do this for each test case.

Also it would allow you to share some base functionality across
different test suites.

> +    devs = nf.dev_get({}, dump=True)
> +    ksft_ge(len(devs), 1)
> +
> +
> +def lo_check() -> None:
> +    global nf
> +    lo_info = nf.dev_get({"ifindex": 1})
> +    ksft_eq(len(lo_info['xdp-features']), 0)
> +    ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0)
> +
> +
> +if __name__ == "__main__":
> +    ksft_run([empty_check, lo_check])

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 5/7] netdevsim: report stats by default, like a real device
  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
  0 siblings, 0 replies; 26+ messages in thread
From: David Wei @ 2024-04-03  2:51 UTC (permalink / raw)
  To: Jakub Kicinski, davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm

On 2024-04-01 18:05, Jakub Kicinski wrote:
> Real devices should implement qstats. Devices which support
> pause or FEC configuration should also report the relevant stats.
> 
> nsim was missing FEC stats completely, some of the qstats
> and pause stats required toggling a debugfs knob.
> 
> Note that the tests which used pause always initialize the setting
> so they shouldn't be affected by the different starting value.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  drivers/net/netdevsim/ethtool.c | 11 ++++++++
>  drivers/net/netdevsim/netdev.c  | 45 +++++++++++++++++++++++++++++++++
>  2 files changed, 56 insertions(+)
> 
> diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
> index bd546d4d26c6..3f9c9327f149 100644
> --- a/drivers/net/netdevsim/ethtool.c
> +++ b/drivers/net/netdevsim/ethtool.c
> @@ -140,6 +140,13 @@ nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
>  	return 0;
>  }
>  
> +static void
> +nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats)
> +{
> +	fec_stats->corrected_blocks.total = 123;
> +	fec_stats->uncorrectable_blocks.total = 4;
> +}
> +
>  static int nsim_get_ts_info(struct net_device *dev,
>  			    struct ethtool_ts_info *info)
>  {
> @@ -163,6 +170,7 @@ static const struct ethtool_ops nsim_ethtool_ops = {
>  	.set_channels			= nsim_set_channels,
>  	.get_fecparam			= nsim_get_fecparam,
>  	.set_fecparam			= nsim_set_fecparam,
> +	.get_fec_stats			= nsim_get_fec_stats,
>  	.get_ts_info			= nsim_get_ts_info,
>  };
>  
> @@ -182,6 +190,9 @@ void nsim_ethtool_init(struct netdevsim *ns)
>  
>  	nsim_ethtool_ring_init(ns);
>  
> +	ns->ethtool.pauseparam.report_stats_rx = true;
> +	ns->ethtool.pauseparam.report_stats_tx = true;
> +
>  	ns->ethtool.fec.fec = ETHTOOL_FEC_NONE;
>  	ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE;
>  
> diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
> index 8330bc0bcb7e..096ac0abbc02 100644
> --- a/drivers/net/netdevsim/netdev.c
> +++ b/drivers/net/netdevsim/netdev.c
> @@ -19,6 +19,7 @@
>  #include <linux/module.h>
>  #include <linux/netdevice.h>
>  #include <linux/slab.h>
> +#include <net/netdev_queues.h>
>  #include <net/netlink.h>
>  #include <net/pkt_cls.h>
>  #include <net/rtnetlink.h>
> @@ -330,6 +331,49 @@ static const struct net_device_ops nsim_vf_netdev_ops = {
>  	.ndo_set_features	= nsim_set_features,
>  };
>  
> +/* We don't have true par-queue stats, yet, so do some random fakery here. */

nit: per-queue

> +static void nsim_get_queue_stats_rx(struct net_device *dev, int idx,
> +				    struct netdev_queue_stats_rx *stats)
> +{
> +	struct rtnl_link_stats64 rtstats = {};
> +
> +	nsim_get_stats64(dev, &rtstats);
> +
> +	stats->packets = rtstats.rx_packets - !!rtstats.rx_packets;

Why subtract !!rtstats.rx_packets? This evaluates to 0 if rx_packets is
0 and 1 if rx_packets is non-zero.

> +	stats->bytes = rtstats.rx_bytes;
> +}
> +
> +static void nsim_get_queue_stats_tx(struct net_device *dev, int idx,
> +				    struct netdev_queue_stats_tx *stats)
> +{
> +	struct rtnl_link_stats64 rtstats = {};
> +
> +	nsim_get_stats64(dev, &rtstats);
> +
> +	stats->packets = rtstats.tx_packets - !!rtstats.tx_packets;
> +	stats->bytes = rtstats.tx_bytes;
> +}
> +
> +static void nsim_get_base_stats(struct net_device *dev,
> +				struct netdev_queue_stats_rx *rx,
> +				struct netdev_queue_stats_tx *tx)
> +{
> +	struct rtnl_link_stats64 rtstats = {};
> +
> +	nsim_get_stats64(dev, &rtstats);
> +
> +	rx->packets = !!rtstats.rx_packets;
> +	rx->bytes = 0;
> +	tx->packets = !!rtstats.tx_packets;
> +	tx->bytes = 0;
> +}
> +
> +static const struct netdev_stat_ops nsim_stat_ops = {
> +	.get_queue_stats_tx	= nsim_get_queue_stats_tx,
> +	.get_queue_stats_rx	= nsim_get_queue_stats_rx,
> +	.get_base_stats		= nsim_get_base_stats,
> +};
> +
>  static void nsim_setup(struct net_device *dev)
>  {
>  	ether_setup(dev);
> @@ -360,6 +404,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
>  
>  	ns->phc = phc;
>  	ns->netdev->netdev_ops = &nsim_netdev_ops;
> +	ns->netdev->stat_ops = &nsim_stat_ops;
>  
>  	err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev);
>  	if (err)

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 6/7] selftests: drivers: add scaffolding for Netlink tests in Python
  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
  0 siblings, 0 replies; 26+ messages in thread
From: David Wei @ 2024-04-03  3:06 UTC (permalink / raw)
  To: Jakub Kicinski, davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm

On 2024-04-01 18:05, Jakub Kicinski wrote:
> Add drivers/net as a target for mixed-use tests.
> The setup is expected to work similarly to the forwarding tests.
> Since we only need one interface (unlike forwarding tests)
> read the target device name from NETIF. If not present we'll
> try to run the test against netdevsim.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/testing/selftests/Makefile              |   3 +-
>  tools/testing/selftests/drivers/net/Makefile  |   7 ++
>  .../testing/selftests/drivers/net/README.rst  |  30 +++++
>  .../selftests/drivers/net/lib/py/__init__.py  |  17 +++
>  .../selftests/drivers/net/lib/py/env.py       |  41 ++++++
>  .../testing/selftests/net/lib/py/__init__.py  |   1 +
>  tools/testing/selftests/net/lib/py/nsim.py    | 118 ++++++++++++++++++
>  7 files changed, 216 insertions(+), 1 deletion(-)
>  create mode 100644 tools/testing/selftests/drivers/net/Makefile
>  create mode 100644 tools/testing/selftests/drivers/net/README.rst
>  create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py
>  create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py
>  create mode 100644 tools/testing/selftests/net/lib/py/nsim.py
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 0cffdfb4b116..d015ec14a85e 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -17,6 +17,7 @@ TARGETS += devices
>  TARGETS += dmabuf-heaps
>  TARGETS += drivers/dma-buf
>  TARGETS += drivers/s390x/uvdevice
> +TARGETS += drivers/net
>  TARGETS += drivers/net/bonding
>  TARGETS += drivers/net/team
>  TARGETS += dt
> @@ -117,7 +118,7 @@ TARGETS_HOTPLUG = cpu-hotplug
>  TARGETS_HOTPLUG += memory-hotplug
>  
>  # Networking tests want the net/lib target, include it automatically
> -ifneq ($(filter net ,$(TARGETS)),)
> +ifneq ($(filter net drivers/net,$(TARGETS)),)
>  ifeq ($(filter net/lib,$(TARGETS)),)
>  	override TARGETS := $(TARGETS) net/lib
>  endif
> diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
> new file mode 100644
> index 000000000000..379cdb1960a7
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +TEST_INCLUDES := $(wildcard lib/py/*.py)
> +
> +TEST_PROGS := stats.py
> +
> +include ../../lib.mk
> diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst
> new file mode 100644
> index 000000000000..5ef7c417d431
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/README.rst
> @@ -0,0 +1,30 @@
> +Running tests
> +=============
> +
> +Tests are executed within kselftest framework like any other tests.
> +By default tests execute against software drivers such as netdevsim.
> +All tests must support running against a real device (SW-only tests
> +should instead be placed in net/ or drivers/net/netdevsim, HW-only
> +tests in drivers/net/hw).
> +
> +Set appropriate variables to point the tests at a real device.
> +
> +Variables
> +=========
> +
> +Variables can be set in the environment or by creating a net.config
> +file in the same directory as this README file. Example::
> +
> +  $ NETIF=eth0 ./some_test.sh
> +
> +or::
> +
> +  $ cat tools/testing/selftests/drivers/net/net.config
> +  # Variable set in a file
> +  NETIF=eth0
> +
> +NETIF
> +~~~~~
> +
> +Name of the netdevice against which the test should be executed.
> +When empty or not set software devices will be used.
> diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py
> new file mode 100644
> index 000000000000..4653dffcd962
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py
> @@ -0,0 +1,17 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import sys
> +from pathlib import Path
> +
> +KSFT_DIR = (Path(__file__).parent / "../../../..").resolve()
> +
> +try:
> +    sys.path.append(KSFT_DIR.as_posix())
> +    from net.lib.py import *
> +except ModuleNotFoundError as e:
> +    ksft_pr("Failed importing `net` library from kernel sources")
> +    ksft_pr(str(e))
> +    ktap_result(True, comment="SKIP")
> +    sys.exit(4)
> +
> +from .env import *
> diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
> new file mode 100644
> index 000000000000..ee4a44555d83
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/lib/py/env.py
> @@ -0,0 +1,41 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import os
> +import shlex
> +from pathlib import Path
> +from lib.py import ip
> +from lib.py import NetdevSimDev

nit: these could be on the same line.

> +
> +class NetDrvEnv:
> +    def __init__(self, src_path):
> +        self.env = os.environ.copy()
> +        self._load_env_file(src_path)
> +
> +        if 'NETIF' in self.env:
> +            self._ns = None

My brain interprets 'ns' as 'namespace'. How about something like
nsimdev/nsdev/nsim?

> +            self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0]
> +        else:
> +            self._ns = NetdevSimDev()
> +            self.dev = self._ns.nsims[0].dev
> +        self.ifindex = self.dev['ifindex']
> +
> +    def __del__(self):
> +        if self._ns:
> +            self._ns.remove()
> +
> +    def _load_env_file(self, src_path):
> +        src_dir = Path(src_path).parent.resolve()
> +        if not (src_dir / "net.config").exists():
> +            return
> +
> +        lexer = shlex.shlex(open((src_dir / "net.config").as_posix(), 'r').read())
> +        k = None
> +        for token in lexer:
> +            if k is None:
> +                k = token
> +                self.env[k] = ""
> +            elif token == "=":
> +                pass
> +            else:
> +                self.env[k] = token
> +                k = None
> diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
> index 81a8d14b68f0..99cfc8dc4dca 100644
> --- a/tools/testing/selftests/net/lib/py/__init__.py
> +++ b/tools/testing/selftests/net/lib/py/__init__.py
> @@ -3,4 +3,5 @@
>  from .ksft import *
>  from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
>  from .consts import KSRC
> +from .nsim import *
>  from .utils import *
> diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py
> new file mode 100644
> index 000000000000..13eb42c82829
> --- /dev/null
> +++ b/tools/testing/selftests/net/lib/py/nsim.py
> @@ -0,0 +1,118 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import json
> +import os
> +import random
> +import re
> +import time
> +from .utils import cmd, ip
> +
> +
> +class NetdevSim:
> +    """
> +    Class for netdevsim netdevice and its attributes.
> +    """
> +
> +    def __init__(self, nsimdev, port_index, ifname, ns=None):
> +        # In case udev renamed the netdev to according to new schema,
> +        # check if the name matches the port_index.
> +        nsimnamere = re.compile("eni\d+np(\d+)")
> +        match = nsimnamere.match(ifname)
> +        if match and int(match.groups()[0]) != port_index + 1:
> +            raise Exception("netdevice name mismatches the expected one")
> +
> +        self.ifname = ifname
> +        self.nsimdev = nsimdev
> +        self.port_index = port_index
> +        self.ns = ns
> +        self.dfs_dir = "%s/ports/%u/" % (nsimdev.dfs_dir, port_index)
> +        ret = ip("-j link show dev %s" % ifname, ns=ns)
> +        self.dev = json.loads(ret.stdout)[0]
> +
> +    def dfs_write(self, path, val):
> +        self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val)
> +
> +
> +class NetdevSimDev:
> +    """
> +    Class for netdevsim bus device and its attributes.
> +    """
> +    @staticmethod
> +    def ctrl_write(path, val):
> +        fullpath = os.path.join("/sys/bus/netdevsim/", path)
> +        with open(fullpath, "w") as f:
> +            f.write(val)
> +
> +    def dfs_write(self, path, val):
> +        fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path)
> +        with open(fullpath, "w") as f:
> +            f.write(val)
> +
> +    def __init__(self, port_count=1, ns=None):
> +        # nsim will spawn in init_net, we'll set to actual ns once we switch it the.sre
> +        self.ns = None
> +
> +        if not os.path.exists("/sys/bus/netdevsim"):
> +            cmd("modprobe netdevsim")
> +
> +        addr = random.randrange(1 << 15)
> +        while True:
> +            try:
> +                self.ctrl_write("new_device", "%u %u" % (addr, port_count))
> +            except OSError as e:
> +                if e.errno == errno.ENOSPC:
> +                    addr = random.randrange(1 << 15)
> +                    continue
> +                raise e
> +            break
> +        self.addr = addr
> +
> +        # As probe of netdevsim device might happen from a workqueue,
> +        # so wait here until all netdevs appear.
> +        self.wait_for_netdevs(port_count)
> +
> +        if ns:
> +            cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}")
> +            self.ns = ns
> +
> +        cmd("udevadm settle", ns=self.ns)
> +        ifnames = self.get_ifnames()
> +
> +        self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr
> +
> +        self.nsims = []
> +        for port_index in range(port_count):
> +            self.nsims.append(NetdevSim(self, port_index, ifnames[port_index],
> +                                        ns=ns))
> +
> +    def get_ifnames(self):
> +        ifnames = []
> +        listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/",
> +                      ns=self.ns).stdout.split()
> +        for ifname in listdir:
> +            ifnames.append(ifname)
> +        ifnames.sort()
> +        return ifnames
> +
> +    def wait_for_netdevs(self, port_count):
> +        timeout = 5
> +        timeout_start = time.time()
> +
> +        while True:
> +            try:
> +                ifnames = self.get_ifnames()
> +            except FileNotFoundError as e:
> +                ifnames = []
> +            if len(ifnames) == port_count:
> +                break
> +            if time.time() < timeout_start + timeout:
> +                continue
> +            raise Exception("netdevices did not appear within timeout")
> +
> +    def remove(self):
> +        self.ctrl_write("del_device", "%u" % (self.addr, ))

I really want this to be in the dtor, but couldn't get it to work
because Python doesn't let you open files in __del__(). :(

> +
> +    def remove_nsim(self, nsim):
> +        self.nsims.remove(nsim)
> +        self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ),
> +                        "%u" % (nsim.port_index, ))

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  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-03  3:09   ` David Wei
  1 sibling, 0 replies; 26+ messages in thread
From: David Wei @ 2024-04-03  3:09 UTC (permalink / raw)
  To: Jakub Kicinski, davem
  Cc: netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest, petrm

On 2024-04-01 18:05, Jakub Kicinski wrote:
> Add a very simple test to make sure drivers report expected
> stats. Drivers which implement FEC or pause configuration
> should report relevant stats. Qstats must be reported,
> at least packet and byte counts, and they must match
> total device stats.
> 
> Tested with netdevsim, bnxt, in-tree and installed.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/testing/selftests/drivers/net/stats.py | 85 ++++++++++++++++++++
>  1 file changed, 85 insertions(+)
>  create mode 100755 tools/testing/selftests/drivers/net/stats.py
> 
> diff --git a/tools/testing/selftests/drivers/net/stats.py b/tools/testing/selftests/drivers/net/stats.py
> new file mode 100755
> index 000000000000..751cca2869b8
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/stats.py
> @@ -0,0 +1,85 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +from lib.py import ksft_run, ksft_in, ksft_true, KsftSkipEx, KsftXfailEx
> +from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
> +from lib.py import NetDrvEnv
> +
> +cfg = None
> +ethnl = EthtoolFamily()
> +netfam = NetdevFamily()
> +rtnl = RtnlFamily()
> +
> +
> +def check_pause() -> None:
> +    global cfg, ethnl
> +
> +    try:
> +        ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftXfailEx("pause not supported by the device")
> +        raise
> +
> +    data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
> +                                       "flags": {'stats'}}})
> +    ksft_true(data['stats'], "driver does not report stats")
> +
> +
> +def check_fec() -> None:
> +    global ethnl
> +
> +    try:
> +        ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
> +    except NlError as e:
> +        if e.error == 95:
> +            raise KsftXfailEx("FEC not supported by the device")
> +        raise
> +
> +    data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
> +                                     "flags": {'stats'}}})
> +    ksft_true(data['stats'], "driver does not report stats")
> +
> +
> +def pkt_byte_sum() -> None:
> +    global cfg, netfam, rtnl
> +
> +    def get_qstat(test):
> +        global netfam
> +        stats = netfam.qstats_get({}, dump=True)
> +        if stats:
> +            for qs in stats:
> +                if qs["ifindex"]== test.ifindex:
> +                    return qs
> +
> +    qstat = get_qstat(cfg)
> +    if qstat is None:
> +        raise KsftSkipEx("qstats not supported by the device")
> +
> +    for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
> +        ksft_in(key, qstat, "Drivers should always report basic keys")
> +
> +    # Compare stats, rtnl stats and qstats must match,
> +    # but the interface may be up, so do a series of dumps
> +    # each time the more "recent" stats must be higher or same.
> +    def stat_cmp(rstat, qstat):
> +        for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
> +            if rstat[key] != qstat[key]:
> +                return rstat[key] - qstat[key]
> +        return 0
> +
> +    for _ in range(10):
> +        rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats']
> +        if stat_cmp(rtstat, qstat) < 0:
> +            raise Exception("RTNL stats are lower, fetched later")
> +        qstat = get_qstat(cfg)
> +        if stat_cmp(rtstat, qstat) > 0:
> +            raise Exception("Qstats are lower, fetched later")
> +
> +
> +if __name__ == "__main__":
> +    cfg = NetDrvEnv(__file__)
> +    try:
> +        ksft_run([check_pause, check_fec, pkt_byte_sum])

Would there ever be a case where you don't want to run every test case
in a suite?

> +    finally:
> +        del cfg

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02 17:31     ` Jakub Kicinski
  2024-04-02 17:44       ` Jakub Kicinski
  2024-04-02 22:04       ` Petr Machata
@ 2024-04-03  3:15       ` David Wei
  2 siblings, 0 replies; 26+ messages in thread
From: David Wei @ 2024-04-03  3:15 UTC (permalink / raw)
  To: Jakub Kicinski, Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On 2024-04-02 10:31, Jakub Kicinski wrote:
> On Tue, 2 Apr 2024 18:37:44 +0200 Petr Machata wrote:
>> Yeah, this would be usually done through context managers, as I mention
>> in the other e-mail. But then cfg would be lexically scoped, which IMHO
>> is a good thing, but then it needs to be passed around as an argument,
>> and that makes the ksft_run() invocation a bit messy:
>>
>>     with NetDrvEnv(__file__) as cfg:
>>         ksft_run([lambda: check_pause(cfg),
>>                   lambda: check_fec(cfg),
>>                   lambda: pkt_byte_sum(cfg)])
>>
>> Dunno, maybe it could forward *args **kwargs to the cases? But then it
>> loses some of the readability again.
> 
> Yes, I was wondering about that. It must be doable, IIRC 
> the multi-threading API "injects" args from a tuple.
> I was thinking something along the lines of:
> 
>     with NetDrvEnv(__file__) as cfg:
>         ksft_run([check_pause, check_fec, pkt_byte_sum],
>                  args=(cfg, ))
> 
> I got lazy, let me take a closer look. Another benefit
> will be that once we pass in "env" / cfg - we can "register" 
> objects in there for auto-cleanup (in the future, current
> tests don't need cleanup)

How about a TestSuite class as a context manager and individual tests
being methods? Then running the test suite runs all test cases and you
won't need to add each test case manually to ksft_run().

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-02 23:36         ` Jakub Kicinski
@ 2024-04-03  8:58           ` Petr Machata
  2024-04-03 13:55             ` Jakub Kicinski
  0 siblings, 1 reply; 26+ messages in thread
From: Petr Machata @ 2024-04-03  8:58 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Petr Machata, davem, netdev, edumazet, pabeni, shuah, sdf,
	donald.hunter, linux-kselftest


Jakub Kicinski <kuba@kernel.org> writes:

> On Wed, 3 Apr 2024 00:04:14 +0200 Petr Machata wrote:
>> > Yes, I was wondering about that. It must be doable, IIRC 
>> > the multi-threading API "injects" args from a tuple.
>> > I was thinking something along the lines of:
>> >
>> >     with NetDrvEnv(__file__) as cfg:
>> >         ksft_run([check_pause, check_fec, pkt_byte_sum],
>> >                  args=(cfg, ))
>> >
>> > I got lazy, let me take a closer look. Another benefit
>> > will be that once we pass in "env" / cfg - we can "register" 
>> > objects in there for auto-cleanup (in the future, current
>> > tests don't need cleanup)  
>> 
>> Yeah, though some of those should probably just be their own context
>> managers IMHO, not necessarily hooked to cfg. I'm thinking something
>> fairly general, so that the support boilerplate doesn't end up costing
>> an arm and leg:
>> 
>>     with build("ip route add 192.0.2.1/28 nexthop via 192.0.2.17",
>>                "ip route del 192.0.2.1/28"),
>>          build("ip link set dev %s master %s" % (swp1, h1),
>>                "ip link set dev %s nomaster" % swp1):
>>         le_test()
>>
>> Dunno. I guess it makes sense to have some of the common stuff
>> predefined, e.g. "with vrf() as h1". And then the stuff that's typically
>> in lib.sh's setup() and cleanup(), can be losslessly hooked up to cfg.
>
> I was thinking of something along the lines of:
>
> def test_abc(cfg):
>     cfg.build("ip route add 192.0.2.1/28 nexthop via 192.0.2.17",
>               "ip route del 192.0.2.1/28")
>     cfg.build("ip link set dev %s master %s" % (swp1, h1),
>               "ip link set dev %s nomaster" % swp1)
>
> optionally we could then also:
>
>      thing = cfg.build("ip link set dev %s master %s" % (swp1, h1),
>                        "ip link set dev %s nomaster" % swp1)
>
>      # ... some code which may raise ...
>
>      # unlink to do something else with the device
>      del thing
>      # ... more code ... 
>
> cfg may not be best here, could be cleaner to create a "test" object,
> always pass it in as the first param, and destroy it after each test.

I assume above you mean that cfg inherits the thing, but cfg lifetime
currently looks like it spreads across several test cases. ksft_run()
would need to know about it and call something to issue the postponed
cleanups between cases.

Also, it's not clear what "del thing" should do in that context, because
if cfg also keeps a reference, __del__ won't get called. There could be
a direct method, like thing.exit() or whatever, but then you need
bookkeeping so as not to clean up the second time through cfg. It's the
less straightforward way of going about it IMHO.

I know that I must sound like a broken record at this point, but look:

    with build("ip link set dev %s master %s" % (swp1, h1),
               "ip link set dev %s nomaster" % swp1) as thing:
        ... some code which may rise ...
    ... more code, interface detached, `thing' gone ...

It's just as concise, makes it very clear where the device is part of
the bridge and where not anymore, and does away with the intricacies of
lifetime management.

If lifetimes don't nest, I think it's just going to be ugly either way.
But I don't think this comes up often.

I don't really see stuff that you could just throw at cfg to keep track
of, apart from the suite configuration (i.e. topology set up). But I
suppose if it comes up, we can do something like:

    thing = cfg.retain(build(..., ...))

Or maybe have a dedicated retainer object, or whatever, it doesn't
necessarily need to be cfg itself.

>> This is what I ended up gravitating towards after writing a handful of
>> LNST tests anyway. The scoping makes it clear where the object exists,
>> lifetime is taken care of, it's all ponies rainbows basically. At least
>> as long as your object lifetimes can be cleanly nested, which admittedly
>> is not always.
>
> Should be fairly easy to support all cases - "with", "recording on
> cfg/test" and del.  Unfortunately in the two tests I came up with

Yup.

> quickly for this series cleanup is only needed for the env itself.
> It's a bit awkward to add the lifetime helpers without any users.

Yeah. I'm basically delving in this now to kinda try and steer future
expectations.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-03  8:58           ` Petr Machata
@ 2024-04-03 13:55             ` Jakub Kicinski
  2024-04-03 16:52               ` Petr Machata
  0 siblings, 1 reply; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-03 13:55 UTC (permalink / raw)
  To: Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On Wed, 3 Apr 2024 10:58:19 +0200 Petr Machata wrote:
> Also, it's not clear what "del thing" should do in that context, because
> if cfg also keeps a reference, __del__ won't get called. There could be
> a direct method, like thing.exit() or whatever, but then you need
> bookkeeping so as not to clean up the second time through cfg. It's the
> less straightforward way of going about it IMHO.

I see, having read up on what del actually does - "del thing" would
indeed not work here.

> I know that I must sound like a broken record at this point, but look:
> 
>     with build("ip link set dev %s master %s" % (swp1, h1),
>                "ip link set dev %s nomaster" % swp1) as thing:
>         ... some code which may rise ...
>     ... more code, interface detached, `thing' gone ...
> 
> It's just as concise, makes it very clear where the device is part of
> the bridge and where not anymore, and does away with the intricacies of
> lifetime management.

My experience [1] is that with "with" we often end up writing tests
like this:

	def test():
		with a() as bunch,
		     of() as things:
			... entire body of the test indented ...

[1] https://github.com/kuba-moo/linux/blob/psp/tools/net/ynl/psp.py

Nothing wrong with that. I guess the question in my mind is whether
we're aiming for making the tests "pythonic" (in which case "with"
definitely wins), or more of a "bash with classes" style trying to
avoid any constructs people may have to google. I'm on the fence on
that one, as the del example proves my python expertise is not high.
OTOH people who prefer bash will continue to write bash tests,
so maybe we don't have to worry about non-experts too much. Dunno.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-03 13:55             ` Jakub Kicinski
@ 2024-04-03 16:52               ` Petr Machata
  2024-04-03 21:48                 ` Jakub Kicinski
  0 siblings, 1 reply; 26+ messages in thread
From: Petr Machata @ 2024-04-03 16:52 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Petr Machata, davem, netdev, edumazet, pabeni, shuah, sdf,
	donald.hunter, linux-kselftest


Jakub Kicinski <kuba@kernel.org> writes:

> On Wed, 3 Apr 2024 10:58:19 +0200 Petr Machata wrote:
>> Also, it's not clear what "del thing" should do in that context, because
>> if cfg also keeps a reference, __del__ won't get called. There could be
>> a direct method, like thing.exit() or whatever, but then you need
>> bookkeeping so as not to clean up the second time through cfg. It's the
>> less straightforward way of going about it IMHO.
>
> I see, having read up on what del actually does - "del thing" would
> indeed not work here.
>
>> I know that I must sound like a broken record at this point, but look:
>> 
>>     with build("ip link set dev %s master %s" % (swp1, h1),
>>                "ip link set dev %s nomaster" % swp1) as thing:
>>         ... some code which may rise ...
>>     ... more code, interface detached, `thing' gone ...
>> 
>> It's just as concise, makes it very clear where the device is part of
>> the bridge and where not anymore, and does away with the intricacies of
>> lifetime management.
>
> My experience [1] is that with "with" we often end up writing tests
> like this:
>
> 	def test():
> 		with a() as bunch,
> 		     of() as things:
> 			... entire body of the test indented ...
>
> [1] https://github.com/kuba-moo/linux/blob/psp/tools/net/ynl/psp.py

Yeah, that does end up happening. I think there are a couple places
where you could have folded several withs in one, but it is going to be
indented, yeah.

But you end up indenting for try: finally: to make the del work reliably
anyway, so it's kinda lose/lose in that regard.

> Nothing wrong with that. I guess the question in my mind is whether
> we're aiming for making the tests "pythonic" (in which case "with"
> definitely wins), or more of a "bash with classes" style trying to
> avoid any constructs people may have to google. I'm on the fence on
> that one, as the del example proves my python expertise is not high.
> OTOH people who prefer bash will continue to write bash tests,
> so maybe we don't have to worry about non-experts too much. Dunno.

What I'm saying is, bash is currently a bit of a mess when it comes to
cleanups. It's hard to get right, annoying to review, and sometimes
individual cases add state that they don't unwind in cleanup() but only
later in the function, so when you C-c half-way through such case, stuff
stays behind.

Python has tools to just magic all this away.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH net-next 7/7] testing: net-drv: add a driver test for stats reporting
  2024-04-03 16:52               ` Petr Machata
@ 2024-04-03 21:48                 ` Jakub Kicinski
  0 siblings, 0 replies; 26+ messages in thread
From: Jakub Kicinski @ 2024-04-03 21:48 UTC (permalink / raw)
  To: Petr Machata
  Cc: davem, netdev, edumazet, pabeni, shuah, sdf, donald.hunter,
	linux-kselftest

On Wed, 3 Apr 2024 18:52:50 +0200 Petr Machata wrote:
> > Nothing wrong with that. I guess the question in my mind is whether
> > we're aiming for making the tests "pythonic" (in which case "with"
> > definitely wins), or more of a "bash with classes" style trying to
> > avoid any constructs people may have to google. I'm on the fence on
> > that one, as the del example proves my python expertise is not high.
> > OTOH people who prefer bash will continue to write bash tests,
> > so maybe we don't have to worry about non-experts too much. Dunno.  
> 
> What I'm saying is, bash is currently a bit of a mess when it comes to
> cleanups. It's hard to get right, annoying to review, and sometimes
> individual cases add state that they don't unwind in cleanup() but only
> later in the function, so when you C-c half-way through such case, stuff
> stays behind.
> 
> Python has tools to just magic all this away.

Understood, just to be clear what I was saying is that +/- bugs 
in my example it is possible to "attach" the lifetime of things
to a test object or such. Maybe people would be less likely to remember
to do that than use "with"? Dunno. In any case, IIUC we don't have to
decide now, so I went ahead with the v2 last night.

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2024-04-03 21:48 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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
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

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.