Skip to content

Commit 6d1ac9b

Browse files
MarcoFalkekwvg
MarcoFalke
authored andcommittedApr 5, 2022
Merge bitcoin#17633: tests: Add option --valgrind to run the functional tests under Valgrind
5db506b tests: Add option --valgrind to run nodes under valgrind in the functional tests (practicalswift) Pull request description: What is better than fixing bugs? Fixing entire bug classes of course! :) Add option `--valgrind` to run the functional tests under Valgrind. Regular functional testing under Valgrind would have caught many of the uninitialized reads we've seen historically. Let's kill this bug class once and for all: let's never use an uninitialized value ever again. Or at least not one that would be triggered by running the functional tests! :) My hope is that this addition will make it super-easy to run the functional tests under Valgrind and thus increase the probability of people making use of it :) Hopefully `test/functional/test_runner.py --valgrind` will become a natural part of the pre-release QA process. **Usage:** ``` $ test/functional/test_runner.py --help … --valgrind run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required ``` **Live demo:** First, let's re-introduce a memory bug by reverting the recent P2P uninitialized read bug fix from PR bitcoin#17624 ("net: Fix an uninitialized read in ProcessMessage(…, "tx", …) when receiving a transaction we already have"). ``` $ git diff diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 3401eb64c..940adea33 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -114,7 +114,7 @@ inline ValidationState::~ValidationState() {}; class TxValidationState : public ValidationState { private: - TxValidationResult m_result = TxValidationResult::TX_RESULT_UNSET; + TxValidationResult m_result; public: bool Invalid(TxValidationResult result, const std::string &reject_reason="", ``` Second, let's test as normal without Valgrind: ``` $ test/functional/p2p_segwit.py -l INFO 2019-11-28T09:30:42.810000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test__fc8q3qo … 2019-11-28T09:31:57.187000Z TestFramework (INFO): Subtest: test_non_standard_witness_blinding (Segwit active = True) … 2019-11-28T09:32:08.265000Z TestFramework (INFO): Tests successful ``` Third, let's test with `--valgrind` and see if the test fail (as we expect) when the unitialized value is used: ``` $ test/functional/p2p_segwit.py -l INFO --valgrind 2019-11-28T09:32:33.018000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_gtjecx2l … 2019-11-28T09:40:36.702000Z TestFramework (INFO): Subtest: test_non_standard_witness_blinding (Segwit active = True) 2019-11-28T09:40:37.813000Z TestFramework (ERROR): Assertion failed ConnectionRefusedError: [Errno 111] Connection refused ``` ACKs for top commit: MarcoFalke: ACK 5db506b jonatack: ACK 5db506b Tree-SHA512: 2eaecacf4da166febad88b2a8ee6d7ac2bcd38d4c1892ca39516b6343e8f8c8814edf5eaf14c90f11a069a0389d24f0713076112ac284de987e72fc5f6cc3795
1 parent 8bb684b commit 6d1ac9b

File tree

2 files changed

+13
-1
lines changed

2 files changed

+13
-1
lines changed
 

‎test/functional/test_framework/test_framework.py

+3
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ def main(self):
156156
help="Scale the test timeouts by multiplying them with the here provided value (default: %(default)s)")
157157
parser.add_argument("--perf", dest="perf", default=False, action="store_true",
158158
help="profile running nodes with perf for the duration of the test")
159+
parser.add_argument("--valgrind", dest="valgrind", default=False, action="store_true",
160+
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required")
159161
parser.add_argument("--randomseed", type=int,
160162
help="set a random seed for deterministically reproducing a previous test run")
161163
self.add_options(parser)
@@ -405,6 +407,7 @@ def add_nodes(self, num_nodes, extra_args=None, *, rpchost=None, binary=None):
405407
extra_args=extra_args[i],
406408
use_cli=self.options.usecli,
407409
start_perf=self.options.perf,
410+
use_valgrind=self.options.valgrind,
408411
))
409412

410413
def start_node(self, i, *args, **kwargs):

‎test/functional/test_framework/test_node.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class TestNode():
6060
To make things easier for the test writer, any unrecognised messages will
6161
be dispatched to the RPC connection."""
6262

63-
def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False):
63+
def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False):
6464
"""
6565
Kwargs:
6666
start_perf (bool): If True, begin profiling the node with `perf` as soon as
@@ -101,6 +101,15 @@ def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timew
101101
"-mocktime=" + str(mocktime),
102102
"-uacomment=testnode%d" % i
103103
]
104+
if use_valgrind:
105+
default_suppressions_file = os.path.join(
106+
os.path.dirname(os.path.realpath(__file__)),
107+
"..", "..", "..", "contrib", "valgrind.supp")
108+
suppressions_file = os.getenv("VALGRIND_SUPPRESSIONS_FILE",
109+
default_suppressions_file)
110+
self.args = ["valgrind", "--suppressions={}".format(suppressions_file),
111+
"--gen-suppressions=all", "--exit-on-first-error=yes",
112+
"--error-exitcode=1", "--quiet"] + self.args
104113

105114
self.cli = TestNodeCLI(bitcoin_cli, self.datadir)
106115
self.use_cli = use_cli

0 commit comments

Comments
 (0)