From a3d30eb32fa0872be900125c7c9150d301b8796b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 26 Sep 2025 11:16:25 +0200 Subject: [PATCH 1/4] test/infamy: minor, whitespace only Signed-off-by: Joachim Wiberg --- test/infamy/env.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/infamy/env.py b/test/infamy/env.py index a36d63d2..caac53a0 100644 --- a/test/infamy/env.py +++ b/test/infamy/env.py @@ -117,13 +117,13 @@ class Env(object): return self.ptop.get_password(node) def is_reachable(self, node, port): - ip = neigh.ll6ping(port) - if not ip: - return False + ip = neigh.ll6ping(port) + if not ip: + return False - return util.is_reachable(ip, self, self.get_password(node)) + return util.is_reachable(ip, self, self.get_password(node)) - def attach(self, node, port="mgmt", protocol=None, test_reset=True, username = None, password = None): + def attach(self, node, port="mgmt", protocol=None, test_reset=True, username=None, password=None): """Attach to node on port using protocol.""" name = node @@ -133,7 +133,6 @@ class Env(object): else: mapping = None - # Precedence: # 1. Caller specifies `protocol` # 2. User specifies `-t` when executing test @@ -159,8 +158,10 @@ class Env(object): if protocol == "netconf": dev = netconf.Device(name, - location=netconf.Location(cport, mgmtip, - username,password), + location=netconf.Location(cport, + mgmtip, + username, + password), mapping=mapping, yangdir=self.args.yangdir) if test_reset: From a9430070d252bc7167d4f84a91400833a94b98cc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 25 Sep 2025 13:30:15 +0200 Subject: [PATCH 2/4] test/infamy: add optional msg support to tap.py::Test.fail() A very common pattern in our tests is: if (condition): print(f"the condition failed with {output}") test.fail() This change allows us to write: if (condition): test.fail(f"the condition failed with {output}") Signed-off-by: Joachim Wiberg --- test/infamy/tap.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/infamy/tap.py b/test/infamy/tap.py index 5c805a02..7e46c973 100644 --- a/test/infamy/tap.py +++ b/test/infamy/tap.py @@ -95,7 +95,10 @@ class Test: def skip(self): raise TestSkip() - def fail(self): + def fail(self, message=None): + if message: + self.out.write(f"# {message}\n") + self.out.flush() raise TestFail() From 9d9e099cdb893791ec0266ed37d027e78407ef4b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 25 Sep 2025 13:35:38 +0200 Subject: [PATCH 3/4] test/infamy: slight improvement to tap.py::Test.__exit__() Before this change: ok 2 - Configure basic end-device firewall not ok 3 - Verify unused interface assigned to default zone # Exiting (2025-09-25 11:33:00) # Traceback (most recent call last): # File "/home/jocke/src/x-misc/test/./case/infix_firewall/basic/test.py", line 127, in # assert unused_if not in public_zone["interface"], \ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # AssertionError: Unused interface e4 should be in default zone 'public', got interfaces: ['e2', 'e3', 'e5', 'e7', 'e8'] After this change: ok 2 - Configure basic end-device firewall not ok 3 - Verify unused interface assigned to default zone # Exiting (2025-09-25 11:35:00) # File "/home/jocke/src/x-misc/test/./case/infix_firewall/basic/test.py", line 127, in # assert unused_if not in public_zone["interface"], \ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Unused interface e4 should be in default zone 'public', got interfaces: ['e2', 'e3', 'e5', 'e7', 'e8'] Slightly shorter and arguably easier to read for a non-pythonic human. Signed-off-by: Joachim Wiberg --- test/infamy/tap.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/infamy/tap.py b/test/infamy/tap.py index 7e46c973..b1938326 100644 --- a/test/infamy/tap.py +++ b/test/infamy/tap.py @@ -30,7 +30,7 @@ class Test: self.out.flush() return self - def __exit__(self, _, e, __): + def __exit__(self, t, e, tb): now = datetime.datetime.now().strftime("%F %T") self.out.write(f"# Exiting ({now})\n") self.out.flush() @@ -40,12 +40,16 @@ class Test: if not e: self._not_ok("Missing explicit test result\n") else: - if type(e) in (TestPass, TestSkip): + if t in (TestPass, TestSkip): self.out.write(f"{self.steps}..{self.steps}\n") self.out.flush() raise SystemExit(0) - - traceback.print_exception(e, file=self.commenter) + if t is AssertionError: + traceback.print_tb(tb, file=self.commenter) + self.out.write(f"{str(e)}\n") + self.out.flush() + else: + traceback.print_exception(e, file=self.commenter) if type(e) is subprocess.CalledProcessError: print("Failing subprocess stdout:\n", e.stdout) From a0a4051c8a51e37c7e7f7ce4f03354ccf88c2a74 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 25 Sep 2025 09:43:31 +0200 Subject: [PATCH 4/4] test/infamy: fix xpath_to_uri() to handle multiple predicates The xpath_to_uri() method only processed the first predicate in XPath expressions with multiple [key='value'] patterns. Each re.sub() call was performed on the original xpath instead of the result the previous substitutions, causing subsequent predicates to be ignored. Example XPath that would fail: /infix-firewall:firewall/zone[name='untrusted']/interface[.='e2'] Would incorrectly convert to: /infix-firewall:firewall/zone[name='untrusted']/interface=e2 Instead of the correct RESTCONF URL: /infix-firewall:firewall/zone=untrusted/interface=e2 Signed-off-by: Joachim Wiberg --- test/infamy/restconf.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/infamy/restconf.py b/test/infamy/restconf.py index 31078836..04c92201 100644 --- a/test/infamy/restconf.py +++ b/test/infamy/restconf.py @@ -26,16 +26,14 @@ class Location: def xpath_to_uri(xpath, extra=None): """Convert xpath to HTTP URI""" - # If the xpath has a pattern = r'\[(.*?)=["\'](.*?)["\']\]' matches = re.findall(pattern, xpath) + uri_path = xpath if matches: for key, value in matches: # replace [key=value] with =value - uri_path = re.sub(rf'\[{re.escape(key)}=["\']{re.escape(value)}["\']\]', f'={value}', xpath) - else: - uri_path = xpath + uri_path = re.sub(rf'\[{re.escape(key)}=["\']{re.escape(value)}["\']\]', f'={value}', uri_path) # Append extra if provided if extra is not None: