1
0
Fork 0
mirror of https://github.com/teamcapybara/capybara.git synced 2022-11-09 12:08:07 -05:00
teamcapybara--capybara/spec/server_spec.rb
Juan Carlos Medina 009320d3bf Display pending requests (#2250)
* 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
2019-10-07 17:26:48 -07:00

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