[PATCH net-next 14/14] selftests: net: shaper: Cover recursive node cleanup
From: Mohsin Bashir
Date: Tue Aug 04 2026 - 23:13:13 EST
From: Mohsin Bashir <hmohsin@xxxxxxxx>
Exercise cleanup of nested nodes after deleting their last queue leaf. The
test builds a two-level node hierarchy and checks that removing the queue
also removes both now-empty node shapers.
Signed-off-by: Jakub Kicinski <kuba@xxxxxxxxxx>
Signed-off-by: Mohsin Bashir <hmohsin@xxxxxxxx>
---
tools/testing/selftests/drivers/net/shaper.py | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/shaper.py b/tools/testing/selftests/drivers/net/shaper.py
index 9264aeb74a7a..a53316726f69 100755
--- a/tools/testing/selftests/drivers/net/shaper.py
+++ b/tools/testing/selftests/drivers/net/shaper.py
@@ -374,6 +374,64 @@ def mixed_parent_group_requires_parent(cfg, nl_shaper) -> None:
shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
ksft_eq(len(shapers), 0)
+def recursive_empty_node_cleanup(cfg, nl_shaper) -> None:
+ r"""Deleting the last leaf recursively removes the emptied ancestors.
+
+ netdev netdev
+ | del Q0
+ N1 ------> (N1 and N2 removed too)
+ |
+ N2
+ |
+ Q0
+ """
+ _require_queues(cfg, 1)
+ _require_caps(cfg, nl_shaper, 'node',
+ ['support-bw-max', 'support-metric-bps', 'support-nesting'],
+ "device does not support nested node scope shapers")
+ _require_caps(cfg, nl_shaper, 'queue',
+ ['support-nesting', 'support-weight'],
+ "device does not support nested queue scope shapers with weight")
+
+ n1_handle = nl_shaper.group({
+ 'ifindex': cfg.ifindex,
+ 'leaves':[{'handle': {'scope': 'queue', 'id': 0},
+ 'weight': 1}],
+ 'handle': {'scope':'node'},
+ 'metric': 'bps',
+ 'bw-max': 10000})
+ n1_id = n1_handle['handle']['id']
+ defer(_delete_shaper, cfg, nl_shaper, {'scope': 'queue', 'id': 0})
+
+ n2_handle = nl_shaper.group({
+ 'ifindex': cfg.ifindex,
+ 'leaves':[{'handle': {'scope': 'queue', 'id': 0},
+ 'weight': 1}],
+ 'handle': {'scope':'node'},
+ 'parent': {'scope': 'node', 'id': n1_id},
+ 'metric': 'bps',
+ 'bw-max': 5000})
+ n2_id = n2_handle['handle']['id']
+
+ shaper_q0 = nl_shaper.get({'ifindex': cfg.ifindex,
+ 'handle': {'scope': 'queue', 'id': 0}})
+ ksft_eq(shaper_q0, {'ifindex': cfg.ifindex,
+ 'parent': {'scope': 'node', 'id': n2_id},
+ 'handle': {'scope': 'queue', 'id': 0},
+ 'weight': 1})
+
+ nl_shaper.delete({'ifindex': cfg.ifindex,
+ 'handle': {'scope': 'queue', 'id': 0}})
+
+ for handle in ({'scope': 'queue', 'id': 0},
+ {'scope': 'node', 'id': n2_id},
+ {'scope': 'node', 'id': n1_id}):
+ with ksft_raises(NlError):
+ nl_shaper.get({'ifindex': cfg.ifindex, 'handle': handle})
+
+ shapers = nl_shaper.get({'ifindex': cfg.ifindex}, dump=True)
+ ksft_eq(len(shapers), 0)
+
def _group_under_netdev(cfg, nl_shaper, bw_max=None):
r"""Group queues under a netdev-scope node; caller owns node teardown.
@@ -1293,6 +1351,7 @@ def main() -> None:
set_all_supported_attrs,
invalid_set_preserves_state,
mixed_parent_group_requires_parent,
+ recursive_empty_node_cleanup,
basic_groups,
basic_groups_with_rate,
qgroups,
--
2.53.0-Meta