Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,16 @@ def unused_port(num = 1, protocol:, bind: "0.0.0.0")
# roughly 100 ports and multiple ranges may coexist, so use a sufficiently
# wide candidate range to reduce the probability that all candidates are
# excluded.
# Those reservations are taken from the dynamic port range (49152-65535 on
# Windows, observed on GitHub Actions runners), and a port can become
# reserved even between the checks below and the actual bind, which then
# fails with EACCES. So keep the candidate range outside the dynamic port
# range of every platform (Linux uses 32768-60999) to avoid competing with
# the OS for ports in the first place.
# About dynamic excluded port ranges, see:
# > netsh interface ipv4 show excludedportrange protocol=tcp
# > netsh interface ipv4 show excludedportrange protocol=ucp
PORT_RANGE_TCP_UDP = (55000..65000)
# > netsh interface ipv4 show excludedportrange protocol=udp
PORT_RANGE_TCP_UDP = (20000..30000)

def unused_port_tcp_udp(num = 1, retries: 1000)
raise "not support num > 1" if num > 1
Expand All @@ -106,9 +112,13 @@ def unused_port_tcp_udp(num = 1, retries: 1000)
raise "can't find unused port"
end

# Bind the loopback address that the tests using unused_port(protocol: :all)
# actually bind, so that a successful check means the same bind can succeed.
BIND_ADDRESS_TCP_UDP = "127.0.0.1"

def port_bindable_udp?(port)
u = UDPSocket.new(::Socket::AF_INET)
u.bind("0.0.0.0", port)
u.bind(BIND_ADDRESS_TCP_UDP, port)
true
rescue SystemCallError
false
Expand All @@ -117,7 +127,7 @@ def port_bindable_udp?(port)
end

def port_bindable_tcp?(port)
TCPServer.open("0.0.0.0", port).close
TCPServer.open(BIND_ADDRESS_TCP_UDP, port).close
true
rescue SystemCallError
false
Expand Down
8 changes: 4 additions & 4 deletions test/test_unused_port.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ class UnusedPortTest < Test::Unit::TestCase
port = unused_port(protocol: :all)
assert_kind_of(Integer, port)
assert_include(PORT_RANGE_TCP_UDP, port)
tcp = TCPServer.open("0.0.0.0", port)
tcp = TCPServer.open(BIND_ADDRESS_TCP_UDP, port)
tcp.close
udp = UDPSocket.new(::Socket::AF_INET)
udp.bind("0.0.0.0", port)
udp.bind(BIND_ADDRESS_TCP_UDP, port)
udp.close
end

Expand All @@ -20,7 +20,7 @@ class UnusedPortTest < Test::Unit::TestCase

sub_test_case "port_bindable_tcp?" do
test "returns false while the port is held and true after release" do
held = TCPServer.open("0.0.0.0", 0)
held = TCPServer.open(BIND_ADDRESS_TCP_UDP, 0)
port = held.addr[1]
assert_false(port_bindable_tcp?(port))
held.close
Expand All @@ -31,7 +31,7 @@ class UnusedPortTest < Test::Unit::TestCase
sub_test_case "port_bindable_udp?" do
test "returns false while the port is held and true after release" do
held = UDPSocket.new(::Socket::AF_INET)
held.bind("0.0.0.0", 0)
held.bind(BIND_ADDRESS_TCP_UDP, 0)
port = held.addr[1]
assert_false(port_bindable_udp?(port))
held.close
Expand Down
Loading