Merge #18986: tests: Add capability to disable RPC timeout in functional tests

38c3dd9c706e7e84b2a4dbaf1424a3f1c3b694fc docs: Add notes on how to diasble rpc timeout in functional tests while attatching gdb. (codeShark149)
784ae096259955ea7a294114f5c021c9489d2717 test: Add capability to disable RPC timeout in functional tests. (codeShark149)

Pull request description:

  Many times, especially while debugging RPC callbacks to core using gdb, the test timeout kicks in before the response can get back. This can be annoying and requires restarting the functional test as well as gdb attachment.

  This PR adds a `--notimeout` flag into `test_framework` and sets the `rpc_timeout` accordingly if the flag is set.

  The same effect can be achieved with newly added `--factor` flag but keeping a separate flag that explicitly disables the timeout can be easier for new testers to find it out and separates its purpose from the `--factor` flag.

  Requesting review ryanofsky jnewbery as per the IRC discussion.

  Update: After initial round of review, the approach is modified to accommodate the functionality in already existing `--factor` flag. `--factor` is changed to `--timeout-factor` to express its intent better.

ACKs for top commit:
  MarcoFalke:
    ACK 38c3dd9c706e7e84b2a4dbaf1424a3f1c3b694fc and thanks for fixing up all my typos 😅
  jnewbery:
    ACK 38c3dd9c706e7e84b2a4dbaf1424a3f1c3b694fc.

Tree-SHA512: 9458dd1010288c62f8bb83f7a4893284fbbf938882dd65fc9e08810a910db07ef676e3100266028e5d4c8ce407b2267b3860595015da070c84a9d4a9816797db
This commit is contained in:
MarcoFalke 2020-05-18 20:00:24 -04:00 committed by UdjinM6
parent 60cfe097be
commit c0e3829631
No known key found for this signature in database
GPG Key ID: 83592BD1400D58D9
5 changed files with 22 additions and 16 deletions

View File

@ -237,6 +237,10 @@ gdb /home/example/dashd <pid>
Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`. Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`.
See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt
Often while debugging rpc calls from functional tests, the test might reach timeout before
process can return a response. Use `--timeout-factor 0` to disable all rpc timeouts for that partcular
functional test. Ex: `test/functional/wallet_hd.py --timeout-factor 0`.
##### Profiling ##### Profiling
An easy way to profile node performance during functional tests is provided An easy way to profile node performance during functional tests is provided

View File

@ -154,9 +154,9 @@ class P2PConnection(asyncio.Protocol):
def is_connected(self): def is_connected(self):
return self._transport is not None return self._transport is not None
def peer_connect(self, dstaddr, dstport, *, net, factor, uacomment=None): def peer_connect(self, dstaddr, dstport, *, net, timeout_factor, uacomment=None):
assert not self.is_connected assert not self.is_connected
self.factor = factor self.timeout_factor = timeout_factor
self.dstaddr = dstaddr self.dstaddr = dstaddr
self.dstport = dstport self.dstport = dstport
# The initial message to send after the connection was made: # The initial message to send after the connection was made:
@ -441,7 +441,7 @@ class P2PInterface(P2PConnection):
# Connection helper methods # Connection helper methods
def wait_until(self, test_function, timeout): def wait_until(self, test_function, timeout):
wait_until(test_function, timeout=timeout, lock=mininode_lock, factor=self.factor) wait_until(test_function, timeout=timeout, lock=mininode_lock, timeout_factor=self.timeout_factor)
def wait_for_disconnect(self, timeout=60): def wait_for_disconnect(self, timeout=60):
test_function = lambda: not self.is_connected test_function = lambda: not self.is_connected

View File

@ -123,7 +123,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.extra_args_from_options = [] self.extra_args_from_options = []
self.set_test_params() self.set_test_params()
self.parse_args() self.parse_args()
self.rpc_timeout = int(self.rpc_timeout * self.options.factor) # optionally, increase timeout by a factor if self.options.timeout_factor == 0 :
self.options.timeout_factor = 99999
self.rpc_timeout = int(self.rpc_timeout * self.options.timeout_factor) # optionally, increase timeout by a factor
def main(self): def main(self):
"""Main function. This should not be overridden by the subclass test scripts.""" """Main function. This should not be overridden by the subclass test scripts."""
@ -189,7 +191,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required") help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required")
parser.add_argument("--randomseed", type=int, parser.add_argument("--randomseed", type=int,
help="set a random seed for deterministically reproducing a previous test run") help="set a random seed for deterministically reproducing a previous test run")
parser.add_argument('--factor', type=float, default=1.0, help='adjust test timeouts by a factor') parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts')
self.add_options(parser) self.add_options(parser)
# Running TestShell in a Jupyter notebook causes an additional -f argument # Running TestShell in a Jupyter notebook causes an additional -f argument
# To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument
@ -442,7 +444,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
chain=self.chain, chain=self.chain,
rpchost=rpchost, rpchost=rpchost,
timewait=self.rpc_timeout, timewait=self.rpc_timeout,
factor=self.options.factor, timeout_factor=self.options.timeout_factor,
bitcoind=binary[i], bitcoind=binary[i],
bitcoin_cli=self.options.bitcoincli, bitcoin_cli=self.options.bitcoincli,
mocktime=self.mocktime, mocktime=self.mocktime,
@ -616,7 +618,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
extra_args_from_options=self.extra_args_from_options, extra_args_from_options=self.extra_args_from_options,
rpchost=None, rpchost=None,
timewait=self.rpc_timeout, timewait=self.rpc_timeout,
factor=self.options.factor, timeout_factor=self.options.timeout_factor,
bitcoind=self.options.bitcoind, bitcoind=self.options.bitcoind,
bitcoin_cli=self.options.bitcoincli, bitcoin_cli=self.options.bitcoincli,
mocktime=self.mocktime, mocktime=self.mocktime,

View File

@ -62,7 +62,7 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection.""" be dispatched to the RPC connection."""
def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timewait, factor, bitcoind, bitcoin_cli, mocktime, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False): def __init__(self, i, datadir, extra_args_from_options, *, chain, rpchost, timewait, timeout_factor, bitcoind, bitcoin_cli, mocktime, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False):
""" """
Kwargs: Kwargs:
start_perf (bool): If True, begin profiling the node with `perf` as soon as start_perf (bool): If True, begin profiling the node with `perf` as soon as
@ -131,7 +131,7 @@ class TestNode():
self.perf_subprocesses = {} self.perf_subprocesses = {}
self.p2ps = [] self.p2ps = []
self.factor = factor self.timeout_factor = timeout_factor
AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key']) AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key'])
PRIV_KEYS = [ PRIV_KEYS = [
@ -253,7 +253,7 @@ class TestNode():
# The wait is done here to make tests as robust as possible # The wait is done here to make tests as robust as possible
# and prevent racy tests and intermittent failures as much # and prevent racy tests and intermittent failures as much
# as possible. Some tests might not need this, but the # as possible. Some tests might not need this, but the
# overhead is trivial, and the added gurantees are worth # overhead is trivial, and the added guarantees are worth
# the minimal performance cost. # the minimal performance cost.
self.log.debug("RPC successfully started") self.log.debug("RPC successfully started")
if self.use_cli: if self.use_cli:
@ -338,13 +338,13 @@ class TestNode():
return True return True
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
wait_until(self.is_node_stopped, timeout=timeout, factor=self.factor) wait_until(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor)
@contextlib.contextmanager @contextlib.contextmanager
def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2):
if unexpected_msgs is None: if unexpected_msgs is None:
unexpected_msgs = [] unexpected_msgs = []
time_end = time.time() + timeout * self.factor time_end = time.time() + timeout * self.timeout_factor
chain = get_chain_folder(self.datadir, self.chain) chain = get_chain_folder(self.datadir, self.chain)
debug_log = os.path.join(self.datadir, chain, 'debug.log') debug_log = os.path.join(self.datadir, chain, 'debug.log')
with open(debug_log, encoding='utf-8') as dl: with open(debug_log, encoding='utf-8') as dl:
@ -502,7 +502,7 @@ class TestNode():
if 'dstaddr' not in kwargs: if 'dstaddr' not in kwargs:
kwargs['dstaddr'] = '127.0.0.1' kwargs['dstaddr'] = '127.0.0.1'
p2p_conn.peer_connect(**kwargs, net=self.chain, factor=self.factor)() p2p_conn.peer_connect(**kwargs, net=self.chain, timeout_factor=self.timeout_factor)()
self.p2ps.append(p2p_conn) self.p2ps.append(p2p_conn)
if wait_for_verack: if wait_for_verack:
# Wait for the node to send us the version and verack # Wait for the node to send us the version and verack
@ -516,7 +516,7 @@ class TestNode():
# transaction that will be added to the mempool as soon as we return here. # transaction that will be added to the mempool as soon as we return here.
# #
# So syncing here is redundant when we only want to send a message, but the cost is low (a few milliseconds) # So syncing here is redundant when we only want to send a message, but the cost is low (a few milliseconds)
# in comparision to the upside of making tests less fragile and unexpected intermittent errors less likely. # in comparison to the upside of making tests less fragile and unexpected intermittent errors less likely.
p2p_conn.sync_with_ping() p2p_conn.sync_with_ping()
return p2p_conn return p2p_conn

View File

@ -227,10 +227,10 @@ def str_to_b64str(string):
def satoshi_round(amount): def satoshi_round(amount):
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), sleep=0.05, factor=1.0, lock=None, do_assert=True, allow_exception=False): def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), sleep=0.05, timeout_factor=1.0, lock=None, do_assert=True, allow_exception=False):
if attempts == float('inf') and timeout == float('inf'): if attempts == float('inf') and timeout == float('inf'):
timeout = 60 timeout = 60
timeout = timeout * factor timeout = timeout * timeout_factor
attempt = 0 attempt = 0
timeout *= Options.timeout_scale timeout *= Options.timeout_scale
time_end = time.time() + timeout time_end = time.time() + timeout