Merge #18617: test: add factor option to adjust test timeouts

2742c3428633b6ceaab6714635dc3adb74bf121b test: add factor option to adjust test timeouts (Harris)

Pull request description:

  This PR adds a new option **factor** that can be used to adjust timeouts in various functional tests.
  Several timeouts and functions from `authproxy`, `mininode`, `test_node` and `util` have been adapted to use this option. The factor-option definition is located in `test_framework.py`.

  Fixes https://github.com/bitcoin/bitcoin/issues/18266
  Also Fixes https://github.com/bitcoin/bitcoin/issues/18834

ACKs for top commit:
  MarcoFalke:
    Thanks! ACK 2742c3428633b6ceaab6714635dc3adb74bf121b

Tree-SHA512: 6d8421933ba2ac1b7db00b70cf2bc242d9842c48121c11aadc30b0985c4a174c86a127d6402d0cd73b993559d60d4f747872d21f9510cf4806e008349780d3ef
This commit is contained in:
MarcoFalke 2020-05-03 08:58:48 -04:00 committed by UdjinM6
parent 86a11a83a2
commit 6bb3e54578
No known key found for this signature in database
GPG Key ID: 83592BD1400D58D9
4 changed files with 28 additions and 21 deletions

View File

@ -22,7 +22,6 @@ from io import BytesIO
import logging import logging
import struct import struct
import sys import sys
import time
import threading import threading
from test_framework.messages import ( from test_framework.messages import (
@ -155,8 +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, uacomment=None): def peer_connect(self, dstaddr, dstport, *, net, factor, uacomment=None):
assert not self.is_connected assert not self.is_connected
self.factor = 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:
@ -440,11 +440,12 @@ class P2PInterface(P2PConnection):
# Connection helper methods # Connection helper methods
def wait_until(self, test_function, timeout):
wait_until(test_function, timeout=timeout, lock=mininode_lock, factor=self.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
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
# This is a hack. The related issues should be fixed by bitcoin 14119 and 14457.
time.sleep(1)
# Message receiving helper methods # Message receiving helper methods
@ -455,14 +456,14 @@ class P2PInterface(P2PConnection):
return False return False
return self.last_message['tx'].tx.rehash() == txid return self.last_message['tx'].tx.rehash() == txid
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
def wait_for_block(self, blockhash, timeout=60): def wait_for_block(self, blockhash, timeout=60):
def test_function(): def test_function():
assert self.is_connected assert self.is_connected
return self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash return self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
def wait_for_header(self, blockhash, timeout=60): def wait_for_header(self, blockhash, timeout=60):
def test_function(): def test_function():
@ -472,7 +473,7 @@ class P2PInterface(P2PConnection):
return False return False
return last_headers.headers[0].rehash() == blockhash return last_headers.headers[0].rehash() == blockhash
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
def wait_for_getdata(self, timeout=60): def wait_for_getdata(self, timeout=60):
"""Waits for a getdata message. """Waits for a getdata message.
@ -486,7 +487,7 @@ class P2PInterface(P2PConnection):
assert self.is_connected assert self.is_connected
return self.last_message.get("getdata") return self.last_message.get("getdata")
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
def wait_for_getheaders(self, timeout=60): def wait_for_getheaders(self, timeout=60):
"""Waits for a getheaders message. """Waits for a getheaders message.
@ -501,7 +502,7 @@ class P2PInterface(P2PConnection):
return self.last_message.get("getheaders2") if self.nServices & NODE_HEADERS_COMPRESSED \ return self.last_message.get("getheaders2") if self.nServices & NODE_HEADERS_COMPRESSED \
else self.last_message.get("getheaders") else self.last_message.get("getheaders")
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
# TODO: enable when p2p_filter.py is backported # TODO: enable when p2p_filter.py is backported
# def wait_for_inv(self, expected_inv, timeout=60): # def wait_for_inv(self, expected_inv, timeout=60):
@ -515,13 +516,13 @@ class P2PInterface(P2PConnection):
# self.last_message["inv"].inv[0].type == expected_inv[0].type and \ # self.last_message["inv"].inv[0].type == expected_inv[0].type and \
# self.last_message["inv"].inv[0].hash == expected_inv[0].hash # self.last_message["inv"].inv[0].hash == expected_inv[0].hash
# wait_until(test_function, timeout=timeout, lock=mininode_lock) # self.wait_until(test_function, timeout=timeout)
def wait_for_verack(self, timeout=60): def wait_for_verack(self, timeout=60):
def test_function(): def test_function():
return "verack" in self.last_message return "verack" in self.last_message
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
# Message sending helper functions # Message sending helper functions
@ -537,7 +538,7 @@ class P2PInterface(P2PConnection):
assert self.is_connected assert self.is_connected
return self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter return self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter
wait_until(test_function, timeout=timeout, lock=mininode_lock) self.wait_until(test_function, timeout=timeout)
self.ping_counter += 1 self.ping_counter += 1
@ -666,7 +667,7 @@ class P2PDataStore(P2PInterface):
self.send_message(msg_block(block=b)) self.send_message(msg_block(block=b))
else: else:
self.send_message(msg_headers([CBlockHeader(block) for block in blocks])) self.send_message(msg_headers([CBlockHeader(block) for block in blocks]))
wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) self.wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout)
if expect_disconnect: if expect_disconnect:
self.wait_for_disconnect() self.wait_for_disconnect()
@ -674,7 +675,7 @@ class P2PDataStore(P2PInterface):
self.sync_with_ping() self.sync_with_ping()
if success: if success:
wait_until(lambda: node.getbestblockhash() == blocks[-1].hash, timeout=timeout) self.wait_until(lambda: node.getbestblockhash() == blocks[-1].hash, timeout=timeout)
else: else:
assert node.getbestblockhash() != blocks[-1].hash assert node.getbestblockhash() != blocks[-1].hash

View File

@ -123,6 +123,7 @@ 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
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."""
@ -188,6 +189,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')
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
@ -440,6 +442,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,
bitcoind=binary[i], bitcoind=binary[i],
bitcoin_cli=self.options.bitcoincli, bitcoin_cli=self.options.bitcoincli,
mocktime=self.mocktime, mocktime=self.mocktime,
@ -613,6 +616,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,
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, 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, 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,6 +131,7 @@ class TestNode():
self.perf_subprocesses = {} self.perf_subprocesses = {}
self.p2ps = [] self.p2ps = []
self.factor = factor
AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key']) AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key'])
PRIV_KEYS = [ PRIV_KEYS = [
@ -337,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) wait_until(self.is_node_stopped, timeout=timeout, factor=self.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 time_end = time.time() + timeout * self.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:
@ -501,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)() p2p_conn.peer_connect(**kwargs, net=self.chain, factor=self.factor)()
self.p2ps.append(p2p_conn) self.p2ps.append(p2p_conn)
if wait_for_verack: if wait_for_verack:
p2p_conn.wait_for_verack() p2p_conn.wait_for_verack()

View File

@ -227,9 +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, lock=None, do_assert=True, allow_exception=False): def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), sleep=0.05, 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
attempt = 0 attempt = 0
timeout *= Options.timeout_scale timeout *= Options.timeout_scale
time_end = time.time() + timeout time_end = time.time() + timeout
@ -292,7 +293,7 @@ def get_rpc_proxy(url, node_number, *, timeout=None, coveragedir=None):
""" """
proxy_kwargs = {} proxy_kwargs = {}
if timeout is not None: if timeout is not None:
proxy_kwargs['timeout'] = timeout proxy_kwargs['timeout'] = int(timeout)
proxy = AuthServiceProxy(url, **proxy_kwargs) proxy = AuthServiceProxy(url, **proxy_kwargs)
proxy.url = url # store URL on proxy for info proxy.url = url # store URL on proxy for info