From ce63d37ebee8d062310a778c5efa6475814188e9 Mon Sep 17 00:00:00 2001 From: woltx <94266259+w0xlt@users.noreply.github.com> Date: Tue, 30 Dec 2025 23:30:17 -0800 Subject: [PATCH] test: use dynamic port allocation to avoid test conflicts Use port=0 for dynamic port allocation in test framework components to avoid "address already in use" errors from concurrent tests or ports stuck in TIME_WAIT state from previous test runs. Changes: - socks5.py: Update conf.addr after bind() to reflect actual port - p2p.py: Retrieve actual port after create_server() when port=0 - feature_proxy.py: Use port=0 for all SOCKS5 proxy servers - feature_anchors.py: Use port=0 for onion proxy server --- test/functional/feature_anchors.py | 6 ++++-- test/functional/feature_proxy.py | 15 +++++++-------- test/functional/test_framework/p2p.py | 3 ++- test/functional/test_framework/socks5.py | 3 +++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py index d3d24f46233..e82f28f4cc2 100755 --- a/test/functional/feature_anchors.py +++ b/test/functional/feature_anchors.py @@ -10,7 +10,7 @@ from test_framework.p2p import P2PInterface, P2P_SERVICES from test_framework.socks5 import Socks5Configuration, Socks5Server from test_framework.messages import CAddress, hash256 from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import check_node_connections, assert_equal, p2p_port +from test_framework.util import check_node_connections, assert_equal INBOUND_CONNECTIONS = 5 BLOCK_RELAY_CONNECTIONS = 2 @@ -92,7 +92,9 @@ class AnchorsTest(BitcoinTestFramework): onion_conf = Socks5Configuration() onion_conf.auth = True onion_conf.unauth = True - onion_conf.addr = ('127.0.0.1', p2p_port(self.num_nodes)) + # Use port=0 for dynamic allocation to avoid conflicts with concurrent + # tests or ports in TIME_WAIT state from previous test runs. + onion_conf.addr = ('127.0.0.1', 0) onion_conf.keep_alive = True onion_proxy = Socks5Server(onion_conf) onion_proxy.start() diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index e95d5ed4557..fa4e966ea6b 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -46,10 +46,7 @@ import tempfile from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, - p2p_port, -) +from test_framework.util import assert_equal from test_framework.netutil import test_ipv6_local, test_unix_socket # Networks returned by RPC getpeerinfo. @@ -74,22 +71,24 @@ class ProxyTest(BitcoinTestFramework): def setup_nodes(self): self.have_ipv6 = test_ipv6_local() self.have_unix_sockets = test_unix_socket() - # Create two proxies on different ports + # Create two proxies on different ports. + # Use port=0 to let the OS assign available ports, avoiding + # "address already in use" errors from concurrent tests. # ... one unauthenticated self.conf1 = Socks5Configuration() - self.conf1.addr = ('127.0.0.1', p2p_port(self.num_nodes)) + self.conf1.addr = ('127.0.0.1', 0) self.conf1.unauth = True self.conf1.auth = False # ... one supporting authenticated and unauthenticated (Tor) self.conf2 = Socks5Configuration() - self.conf2.addr = ('127.0.0.1', p2p_port(self.num_nodes + 1)) + self.conf2.addr = ('127.0.0.1', 0) self.conf2.unauth = True self.conf2.auth = True if self.have_ipv6: # ... one on IPv6 with similar configuration self.conf3 = Socks5Configuration() self.conf3.af = socket.AF_INET6 - self.conf3.addr = ('::1', p2p_port(self.num_nodes + 2)) + self.conf3.addr = ('::1', 0) self.conf3.unauth = True self.conf3.auth = True else: diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 346a7c6526a..986eaf1e88e 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -787,13 +787,14 @@ class NetworkThread(threading.Thread): cls.protos[(addr, port)] = None return response - if (addr, port) not in cls.listeners: + if port == 0 or (addr, port) not in cls.listeners: # When creating a listener on a given (addr, port) we only need to # do it once. If we want different behaviors for different # connections, we can accomplish this by providing different # `proto` functions listener = await cls.network_event_loop.create_server(peer_protocol, addr, port) + port = listener.sockets[0].getsockname()[1] logger.debug("Listening server on %s:%d should be started" % (addr, port)) cls.listeners[(addr, port)] = listener diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py index 3fd4d5c8e93..711734cf98e 100644 --- a/test/functional/test_framework/socks5.py +++ b/test/functional/test_framework/socks5.py @@ -203,6 +203,9 @@ class Socks5Server(): self.s = socket.socket(conf.af) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.s.bind(conf.addr) + # When port=0, the OS assigns an available port. Update conf.addr + # to reflect the actual bound address so callers can use it. + self.conf.addr = self.s.getsockname() self.s.listen(5) self.running = False self.thread = None