Skip to content

Commit 612ba35

Browse files
committed
Merge #12755: [tests] Better stderr testing
beee49b [tests] Allow stderr to be tested against specified string (John Newbery) e503671 [Tests] Use LIBC_FATAL_STDERR_=1 in tests (John Newbery) c22ce8a [Tests] Write stdout/stderr to datadir instead of temp file. (John Newbery) Pull request description: **Due to a merge conflict, this is now based on #10267. Please review that PR first!** Subset of #12379 now that parts of that PR have been merged. #12362 was only observed when running the functional tests locally because: - by defatul libc logs to `/dev/tty` instead of stderr - the functional tests only check for substring inclusion in stderr when we're expecting bitcoind to fail. This PR tightens our checking of stderr and will cause tests to fail if there is any unexpected message in stderr: - commit *Write stdout/stderr to datadir instead of temp file* writes stderr to a file in the datadir instead of a temporary file. This helps with debugging in the case of failure. - commit *Use LIBC_FATAL_STDERR=1 in tests* ensures that libc failures are logged to stderr instead of the terminal. commit *Assert that bitcoind stdout is empty on shutdown* asserts that stderr is empty on bitcoind shutdown. Tree-SHA512: 21111030e667b3b686f2a7625c2b625ebcfb6998e1cccb4f3932e8b5d21fb514b19a73ac971595d049343430e9a63155986a7f5648cad55b8f36f3c58b1c7048
2 parents 4a50ec0 + beee49b commit 612ba35

File tree

4 files changed

+43
-22
lines changed

4 files changed

+43
-22
lines changed

test/functional/feature_includeconf.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
file.
1616
"""
1717
import os
18-
import tempfile
1918

20-
from test_framework.test_framework import BitcoinTestFramework, assert_equal
19+
from test_framework.test_framework import BitcoinTestFramework
2120

2221
class IncludeConfTest(BitcoinTestFramework):
2322
def set_test_params(self):
@@ -44,20 +43,18 @@ def run_test(self):
4443

4544
self.log.info("-includeconf cannot be used as command-line arg. subversion should still end with 'main; relative)/'")
4645
self.stop_node(0)
47-
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
48-
self.start_node(0, extra_args=["-includeconf=relative2.conf"], stderr=log_stderr)
4946

50-
subversion = self.nodes[0].getnetworkinfo()["subversion"]
51-
assert subversion.endswith("main; relative)/")
52-
log_stderr.seek(0)
53-
stderr = log_stderr.read().decode('utf-8').strip()
54-
assert_equal(stderr, 'warning: -includeconf cannot be used from commandline; ignoring -includeconf=relative2.conf')
47+
self.start_node(0, extra_args=["-includeconf=relative2.conf"])
48+
49+
subversion = self.nodes[0].getnetworkinfo()["subversion"]
50+
assert subversion.endswith("main; relative)/")
51+
self.stop_node(0, expected_stderr="warning: -includeconf cannot be used from commandline; ignoring -includeconf=relative2.conf")
5552

5653
self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'")
5754
with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f:
5855
f.write("includeconf=relative2.conf\n")
5956

60-
self.restart_node(0)
57+
self.start_node(0)
6158

6259
subversion = self.nodes[0].getnetworkinfo()["subversion"]
6360
assert subversion.endswith("main; relative)/")

test/functional/test_framework/test_framework.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, bin
256256
assert_equal(len(extra_args), num_nodes)
257257
assert_equal(len(binary), num_nodes)
258258
for i in range(num_nodes):
259-
self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli))
259+
self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli))
260260

261261
def start_node(self, i, *args, **kwargs):
262262
"""Start a bitcoind"""
@@ -289,9 +289,9 @@ def start_nodes(self, extra_args=None, *args, **kwargs):
289289
for node in self.nodes:
290290
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
291291

292-
def stop_node(self, i):
292+
def stop_node(self, i, expected_stderr=''):
293293
"""Stop a bitcoind test node"""
294-
self.nodes[i].stop_node()
294+
self.nodes[i].stop_node(expected_stderr)
295295
self.nodes[i].wait_until_stopped()
296296

297297
def stop_nodes(self):
@@ -407,7 +407,7 @@ def _initialize_chain(self):
407407
args = [self.options.bitcoind, "-datadir=" + datadir]
408408
if i > 0:
409409
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
410-
self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), extra_conf=["bind=127.0.0.1"], extra_args=[], rpchost=None, timewait=None, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, stderr=None, mocktime=self.mocktime, coverage_dir=None))
410+
self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), extra_conf=["bind=127.0.0.1"], extra_args=[], rpchost=None, timewait=None, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=None))
411411
self.nodes[i].args = args
412412
self.start_node(i)
413413

test/functional/test_framework/test_node.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import http.client
1111
import json
1212
import logging
13+
import os
1314
import re
1415
import subprocess
1516
import tempfile
@@ -55,17 +56,18 @@ class TestNode():
5556
To make things easier for the test writer, any unrecognised messages will
5657
be dispatched to the RPC connection."""
5758

58-
def __init__(self, i, datadir, rpchost, timewait, bitcoind, bitcoin_cli, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
59+
def __init__(self, i, datadir, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
5960
self.index = i
6061
self.datadir = datadir
62+
self.stdout_dir = os.path.join(self.datadir, "stdout")
63+
self.stderr_dir = os.path.join(self.datadir, "stderr")
6164
self.rpchost = rpchost
6265
if timewait:
6366
self.rpc_timeout = timewait
6467
else:
6568
# Wait for up to 60 seconds for the RPC server to respond
6669
self.rpc_timeout = 60
6770
self.binary = bitcoind
68-
self.stderr = stderr
6971
self.coverage_dir = coverage_dir
7072
if extra_conf != None:
7173
append_config(datadir, extra_conf)
@@ -124,17 +126,29 @@ def __getattr__(self, name):
124126
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
125127
return getattr(self.rpc, name)
126128

127-
def start(self, extra_args=None, stderr=None, *args, **kwargs):
129+
def start(self, extra_args=None, stdout=None, stderr=None, *args, **kwargs):
128130
"""Start the node."""
129131
if extra_args is None:
130132
extra_args = self.extra_args
133+
134+
# Add a new stdout and stderr file each time bitcoind is started
131135
if stderr is None:
132-
stderr = self.stderr
136+
stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False)
137+
if stdout is None:
138+
stdout = tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False)
139+
self.stderr = stderr
140+
self.stdout = stdout
141+
133142
# Delete any existing cookie file -- if such a file exists (eg due to
134143
# unclean shutdown), it will get overwritten anyway by bitcoind, and
135144
# potentially interfere with our attempt to authenticate
136145
delete_cookie_file(self.datadir)
137-
self.process = subprocess.Popen(self.args + extra_args, stderr=stderr, *args, **kwargs)
146+
147+
# add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal
148+
subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1")
149+
150+
self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, *args, **kwargs)
151+
138152
self.running = True
139153
self.log.debug("bitcoind started, waiting for RPC to come up")
140154

@@ -174,7 +188,7 @@ def get_wallet_rpc(self, wallet_name):
174188
wallet_path = "wallet/%s" % wallet_name
175189
return self.rpc / wallet_path
176190

177-
def stop_node(self):
191+
def stop_node(self, expected_stderr=''):
178192
"""Stop the node."""
179193
if not self.running:
180194
return
@@ -183,6 +197,13 @@ def stop_node(self):
183197
self.stop()
184198
except http.client.CannotSendRequest:
185199
self.log.exception("Unable to stop node.")
200+
201+
# Check that stderr is as expected
202+
self.stderr.seek(0)
203+
stderr = self.stderr.read().decode('utf-8').strip()
204+
if stderr != expected_stderr:
205+
raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr))
206+
186207
del self.p2ps[:]
187208

188209
def is_node_stopped(self):
@@ -217,9 +238,10 @@ def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, mat
217238
218239
Will throw if bitcoind starts without an error.
219240
Will throw if an expected_msg is provided and it does not match bitcoind's stdout."""
220-
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
241+
with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \
242+
tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout:
221243
try:
222-
self.start(extra_args, stderr=log_stderr, *args, **kwargs)
244+
self.start(extra_args, stdout=log_stdout, stderr=log_stderr, *args, **kwargs)
223245
self.wait_for_rpc_connection()
224246
self.stop_node()
225247
self.wait_until_stopped()

test/functional/test_framework/util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ def initialize_datadir(dirname, n):
301301
f.write("keypool=1\n")
302302
f.write("discover=0\n")
303303
f.write("listenonion=0\n")
304+
os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True)
305+
os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
304306
return datadir
305307

306308
def get_datadir_path(dirname, n):

0 commit comments

Comments
 (0)