mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
009320d3bf
* Backfill tests on pending requests behavior * Implement parameterized wait time for wait_for_oending_requests At some point, this might be worth being a top-level configuration. * Re-implement counter mutex with a list of pending request envs Set up for exposing more information to developers about pending requests. * Expose pending requests in error message This provides a bit more information to other developers about the state of their tests. Whether we include further information would require more feedback from other developers and how the community use the error message for debugging. * Add safe fallback value to avoid passing `nil` * Use a memory barrier on read operations for array * Only store REQUEST_URI instead of env * Revert back to fixed wait_time (60) Keeping initial PR tidy and focused. * Remove redundant attr reader We already defined a getter for the value to ensure proper concurrent accessing. * Ruby linter: single-line body for modifier if usage * Ruby linter: single quotes
292 lines
9 KiB
Ruby
292 lines
9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe Capybara::Server do
|
|
it 'should spool up a rack server' do
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
server = described_class.new(app).boot
|
|
|
|
res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
|
|
|
|
expect(res.body).to include('Hello Server')
|
|
end
|
|
|
|
it 'should do nothing when no server given' do
|
|
expect do
|
|
described_class.new(nil).boot
|
|
end.not_to raise_error
|
|
end
|
|
|
|
it 'should bind to the specified host' do
|
|
# TODO: travis with jruby in container mode has an issue with this test
|
|
skip 'This platform has an issue with this test' if (ENV['TRAVIS'] && (RUBY_ENGINE == 'jruby')) || Gem.win_platform?
|
|
|
|
begin
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
|
|
Capybara.server_host = '127.0.0.1'
|
|
server = described_class.new(app).boot
|
|
res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
|
|
expect(res).to eq('Hello Server!')
|
|
|
|
Capybara.server_host = '0.0.0.0'
|
|
server = described_class.new(app).boot
|
|
res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
|
|
expect(res).to eq('Hello Server!')
|
|
ensure
|
|
Capybara.server_host = nil
|
|
end
|
|
end
|
|
|
|
it 'should use specified port' do
|
|
Capybara.server_port = 22789
|
|
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
server = described_class.new(app).boot
|
|
|
|
res = Net::HTTP.start(server.host, 22789) { |http| http.get('/') }
|
|
expect(res.body).to include('Hello Server')
|
|
|
|
Capybara.server_port = nil
|
|
end
|
|
|
|
it 'should use given port' do
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
server = described_class.new(app, port: 22790).boot
|
|
|
|
res = Net::HTTP.start(server.host, 22790) { |http| http.get('/') }
|
|
expect(res.body).to include('Hello Server')
|
|
|
|
Capybara.server_port = nil
|
|
end
|
|
|
|
it 'should find an available port' do
|
|
responses = ['Hello Server!', 'Hello Second Server!']
|
|
apps = responses.map do |response|
|
|
proc { |_env| [200, {}, [response]] }
|
|
end
|
|
servers = apps.map { |app| described_class.new(app).boot }
|
|
|
|
servers.each_with_index do |server, idx|
|
|
result = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
|
|
expect(result.body).to include(responses[idx])
|
|
end
|
|
end
|
|
|
|
it 'should handle that getting available ports fails randomly' do
|
|
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
|
|
|
|
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
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
server = described_class.new(app).boot
|
|
uri = ::Addressable::URI.parse(server.base_url)
|
|
expect(uri.to_hash).to include(scheme: 'http', host: server.host, port: server.port)
|
|
end
|
|
|
|
it 'should support SSL' do
|
|
begin
|
|
key = File.join(Dir.pwd, 'spec', 'fixtures', 'key.pem')
|
|
cert = File.join(Dir.pwd, 'spec', 'fixtures', 'certificate.pem')
|
|
Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" }
|
|
app = proc { |_env| [200, {}, ['Hello SSL Server!']] }
|
|
server = described_class.new(app).boot
|
|
|
|
expect do
|
|
Net::HTTP.start(server.host, server.port, max_retries: 0) { |http| http.get('/__identify__') }
|
|
end.to(raise_error do |e|
|
|
expect(e.is_a?(EOFError) || e.is_a?(Net::ReadTimeout)).to be true
|
|
end)
|
|
|
|
res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
|
|
https.get('/')
|
|
end
|
|
|
|
expect(res.body).to include('Hello SSL Server!')
|
|
uri = ::Addressable::URI.parse(server.base_url)
|
|
expect(uri.to_hash).to include(scheme: 'https', host: server.host, port: server.port)
|
|
ensure
|
|
Capybara.server = :default
|
|
end
|
|
end
|
|
|
|
context 'When Capybara.reuse_server is true' do
|
|
let!(:old_reuse_server) { Capybara.reuse_server }
|
|
|
|
before do
|
|
Capybara.reuse_server = true
|
|
end
|
|
|
|
after do
|
|
Capybara.reuse_server = old_reuse_server
|
|
end
|
|
|
|
it 'should use the existing server if it already running' do
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
|
|
servers = Array.new(2) { described_class.new(app).boot }
|
|
|
|
servers.each do |server|
|
|
res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
|
|
expect(res.body).to include('Hello Server')
|
|
end
|
|
|
|
expect(servers[0].port).to eq(servers[1].port)
|
|
end
|
|
|
|
it 'detects and waits for all reused server sessions pending requests' do
|
|
done = 0
|
|
|
|
app = proc do |env|
|
|
request = Rack::Request.new(env)
|
|
sleep request.params['wait_time'].to_f
|
|
done += 1
|
|
[200, {}, ['Hello Server!']]
|
|
end
|
|
|
|
server1 = described_class.new(app).boot
|
|
server2 = described_class.new(app).boot
|
|
|
|
expect do
|
|
start_request(server1, 1.0)
|
|
start_request(server2, 3.0)
|
|
server1.wait_for_pending_requests
|
|
end.to change { done }.from(0).to(2)
|
|
expect(server2.send(:pending_requests?)).to eq(false)
|
|
end
|
|
end
|
|
|
|
context 'When Capybara.reuse_server is false' do
|
|
before do
|
|
@old_reuse_server = Capybara.reuse_server
|
|
Capybara.reuse_server = false
|
|
end
|
|
|
|
after do
|
|
Capybara.reuse_server = @old_reuse_server # rubocop:disable RSpec/InstanceVariable
|
|
end
|
|
|
|
it 'should not reuse an already running server' do
|
|
app = proc { |_env| [200, {}, ['Hello Server!']] }
|
|
|
|
servers = Array.new(2) { described_class.new(app).boot }
|
|
|
|
servers.each do |server|
|
|
res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
|
|
expect(res.body).to include('Hello Server')
|
|
end
|
|
|
|
expect(servers[0].port).not_to eq(servers[1].port)
|
|
end
|
|
|
|
it 'detects and waits for only one sessions pending requests' do
|
|
done = 0
|
|
|
|
app = proc do |env|
|
|
request = Rack::Request.new(env)
|
|
sleep request.params['wait_time'].to_f
|
|
done += 1
|
|
[200, {}, ['Hello Server!']]
|
|
end
|
|
|
|
server1 = described_class.new(app).boot
|
|
server2 = described_class.new(app).boot
|
|
|
|
expect do
|
|
start_request(server1, 1.0)
|
|
start_request(server2, 3.0)
|
|
server1.wait_for_pending_requests
|
|
end.to change { done }.from(0).to(1)
|
|
expect(server2.send(:pending_requests?)).to eq(true)
|
|
expect do
|
|
server2.wait_for_pending_requests
|
|
end.to change { done }.from(1).to(2)
|
|
end
|
|
end
|
|
|
|
it 'should raise server errors when the server errors before the timeout' do
|
|
begin
|
|
Capybara.register_server :kaboom do
|
|
sleep 0.1
|
|
raise 'kaboom'
|
|
end
|
|
Capybara.server = :kaboom
|
|
|
|
expect do
|
|
described_class.new(proc { |e| }).boot
|
|
end.to raise_error(RuntimeError, 'kaboom')
|
|
ensure
|
|
Capybara.server = :default
|
|
end
|
|
end
|
|
|
|
it 'should raise an error when there are pending requests' do
|
|
app = proc do |env|
|
|
request = Rack::Request.new(env)
|
|
sleep request.params['wait_time'].to_f
|
|
[200, {}, ['Hello Server!']]
|
|
end
|
|
|
|
server = described_class.new(app).boot
|
|
|
|
expect do
|
|
start_request(server, 59.0)
|
|
server.wait_for_pending_requests
|
|
end.not_to raise_error
|
|
|
|
expect do
|
|
start_request(server, 61.0)
|
|
server.wait_for_pending_requests
|
|
end.to raise_error('Requests did not finish in 60 seconds: ["/?wait_time=61.0"]')
|
|
end
|
|
|
|
it 'is not #responsive? when Net::HTTP raises a SystemCallError' do
|
|
app = -> { [200, {}, ['Hello, world']] }
|
|
server = described_class.new(app)
|
|
allow(Net::HTTP).to receive(:start).and_raise(SystemCallError.allocate)
|
|
expect(server.responsive?).to eq false
|
|
end
|
|
|
|
[EOFError, Net::ReadTimeout].each do |err|
|
|
it "should attempt an HTTPS connection if HTTP connection returns #{err}" do
|
|
app = -> { [200, {}, ['Hello, world']] }
|
|
ordered_errors = [Errno::ECONNREFUSED, err]
|
|
allow(Net::HTTP).to receive(:start).with(anything, anything, hash_excluding(:use_ssl)) do
|
|
raise ordered_errors.shift
|
|
end
|
|
response = Net::HTTPSuccess.allocate
|
|
allow(response).to receive(:body).and_return app.object_id.to_s
|
|
allow(Net::HTTP).to receive(:start).with(anything, anything, hash_including(use_ssl: true)).and_return(response).once
|
|
described_class.new(app).boot
|
|
expect(Net::HTTP).to have_received(:start).exactly(3).times
|
|
end
|
|
end
|
|
|
|
def start_request(server, wait_time)
|
|
# Start request, but don't wait for it to finish
|
|
socket = TCPSocket.new(server.host, server.port)
|
|
socket.write "GET /?wait_time=#{wait_time} HTTP/1.0\r\n\r\n"
|
|
sleep 0.1
|
|
socket.close
|
|
sleep 0.1
|
|
end
|
|
end
|