diff --git a/lib/capybara/server.rb b/lib/capybara/server.rb index c37a4580..24f0314a 100644 --- a/lib/capybara/server.rb +++ b/lib/capybara/server.rb @@ -105,23 +105,20 @@ module Capybara end def find_available_port(host) - port = 0 - while port.zero? - begin - server = TCPServer.new(host, port) - port = server.addr[1] - ensure - server&.close - end - begin - server = TCPServer.new(host, port) - rescue Errno::EADDRINUSE - port = 0 - ensure - server&.close - end - end + server = TCPServer.new(host, 0) + port = server.addr[1] + server.close + + # Workaround issue where some platforms (mac, ???) when passed a host + # of '0.0.0.0' will return a port that is only available on one of the + # ip addresses that resolves to, but the next binding to that port requires + # that port to be available on all ips + server = TCPServer.new(host, port) port + rescue Errno::EADDRINUSE + retry + ensure + server&.close end end end diff --git a/spec/server_spec.rb b/spec/server_spec.rb index 2e68d9b0..03098f79 100644 --- a/spec/server_spec.rb +++ b/spec/server_spec.rb @@ -74,19 +74,26 @@ RSpec.describe Capybara::Server do end end - def use_port(host, port) - server = TCPServer.new(host, port) - ensure - server&.close - end - it 'should handle that getting available ports fails randomly' do - expect do - 100000.times do - port = described_class.new(Object.new).send(:find_available_port, '0.0.0.0') - use_port('0.0.0.0', port) + begin + # Use a port to force a EADDRINUSE error to be generated + server = TCPServer.new('0.0.0.0', 0) + server_port = server.addr[1] + d_server = instance_double('TCPServer', addr: [nil, server_port, nil, nil], close: nil) + call_count = 0 + allow(TCPServer).to receive(:new).and_wrap_original do |m, *args| + begin + call_count.zero? ? d_server : m.call(*args) + ensure + call_count += 1 + end end - end.not_to raise_error + + port = described_class.new(Object.new, host: '0.0.0.0').port + expect(port).not_to eq(server_port) + ensure + server&.close + end end it 'should return its #base_url' do