test framework: expand expected_stderr, expected_ret_code options
What changed, and why it matters
This commit changes Bitcoin Core's internal testing helper code. It makes two test-only quality-of-life improvements: an expected error message can now be a regular expression pattern instead of only a fixed string, and an expected program exit code can now be a list of acceptable values instead of a single value. These changes only affect how tests validate results; they do not change the behavior of the Bitcoin Core software that users run.
No security action needed. This is a benign test-framework enhancement. Reviewers may optionally verify that the regex path handles bytes vs strings correctly and that the Iterable check does not accidentally treat strings as iterables of characters (the supplied expected_ret_code is an int, so this is not a concern here).
Security signals we found
No strong security signals were identified.
Evidence from the diff
In test/functional/test_framework/test_node.py, the is_node_stopped/wait_until_stopped helpers are updated so expected_stderr accepts a re.Pattern and expected_ret_code accepts an iterable of exit codes. The code normalizes a scalar expected_ret_code into a tuple and uses ‘return_code in expected_ret_code’, and checks whether expected_stderr is a compiled regex, using search() for regexes and equality for strings. This is purely test-framework code and does not touch node runtime logic, networking, consensus, or wallet code.
Changed components
test/functional/test_framework/test_node.pyInspect captured patch +14 / −2
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index b56cf64c..89665ab2 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -22,6 +22,7 @@ import collections
import shlex
import shutil
import sys
+from collections.abc import Iterable
from pathlib import Path
from .authproxy import (
@@ -469,6 +470,12 @@ class TestNode():
"""Checks whether the node has stopped.
Returns True if the node has stopped. False otherwise.
+
+ If the process has exited, asserts that the exit code matches
+ `expected_ret_code` (which may be a single value or an iterable of values),
+ and that stderr matches `expected_stderr` exactly or, if a regex pattern is
+ provided, contains the pattern.
+
This method is responsible for freeing resources (self.process)."""
if not self.running:
return True
@@ -477,12 +484,17 @@ class TestNode():
return False
# process has stopped. Assert that it didn't return an error code.
- assert return_code == expected_ret_code, self._node_msg(
+ if not isinstance(expected_ret_code, Iterable):
+ expected_ret_code = (expected_ret_code,)
+ assert return_code in expected_ret_code, self._node_msg(
f"Node returned unexpected exit code ({return_code}) vs ({expected_ret_code}) when stopping")
# Check that stderr is as expected
self.stderr.seek(0)
stderr = self.stderr.read().decode('utf-8').strip()
- if stderr != expected_stderr:
+ if isinstance(expected_stderr, re.Pattern):
+ if not expected_stderr.search(stderr):
+ raise AssertionError(f"Unexpected stderr {stderr!r} does not contain {expected_stderr.pattern!r}")
+ elif stderr != expected_stderr:
raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr))
self.stdout.close()
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.