Extract a Connection class for booting the server

This commit is contained in:
Joe Ferris 2012-05-04 15:50:30 -04:00
parent 4ca10d29ed
commit 665e0fc729
7 changed files with 199 additions and 138 deletions

View File

@ -1,6 +1,7 @@
require "capybara"
require "capybara/driver/webkit/version"
require "capybara/driver/webkit/node"
require "capybara/driver/webkit/connection"
require "capybara/driver/webkit/browser"
require "capybara/driver/webkit/socket_debugger"
require "capybara/driver/webkit/cookie_jar"
@ -22,7 +23,7 @@ class Capybara::Driver::Webkit
@options = options
@rack_server = Capybara::Server.new(@app)
@rack_server.boot if Capybara.run_server
@browser = options[:browser] || Browser.new
@browser = options[:browser] || Browser.new(Connection.new(options))
@browser.ignore_ssl_errors if options[:ignore_ssl_errors]
end

View File

@ -1,19 +1,9 @@
require 'socket'
require 'thread'
require 'timeout'
require 'json'
class Capybara::Driver::Webkit
class Browser
attr :server_port
def initialize(options = {})
@socket_class = options[:socket_class] || TCPSocket
@stdout = options.has_key?(:stdout) ?
options[:stdout] :
$stdout
start_server
connect
def initialize(connection)
@connection = connection
end
def visit(url)
@ -88,11 +78,11 @@ class Capybara::Driver::Webkit
end
def command(name, *args)
@socket.puts name
@socket.puts args.size
@connection.puts name
@connection.puts args.size
args.each do |arg|
@socket.puts arg.to_s.bytesize
@socket.print arg.to_s
@connection.puts arg.to_s.bytesize
@connection.print arg.to_s
end
check
read_response
@ -138,90 +128,8 @@ class Capybara::Driver::Webkit
private
def start_server
pipe = fork_server
@server_port = discover_server_port(pipe)
@stdout_thread = Thread.new do
Thread.current.abort_on_exception = true
forward_stdout(pipe)
end
end
def fork_server
server_path = File.expand_path("../../../../../bin/webkit_server", __FILE__)
pipe, @pid = server_pipe_and_pid(server_path)
register_shutdown_hook
pipe
end
def kill_process(pid)
if RUBY_PLATFORM =~ /mingw32/
Process.kill(9, pid)
else
Process.kill("INT", pid)
end
end
def register_shutdown_hook
@owner_pid = Process.pid
at_exit do
if Process.pid == @owner_pid
kill_process(@pid)
end
end
end
def server_pipe_and_pid(server_path)
cmdline = [server_path]
pipe = IO.popen(cmdline.join(" "))
[pipe, pipe.pid]
end
def discover_server_port(read_pipe)
return unless IO.select([read_pipe], nil, nil, 10)
((read_pipe.first || '').match(/listening on port: (\d+)/) || [])[1].to_i
end
def forward_stdout(pipe)
while pipe_readable?(pipe)
line = pipe.readline
if @stdout
@stdout.write(line)
@stdout.flush
end
end
rescue EOFError
end
if !defined?(RUBY_ENGINE) || (RUBY_ENGINE == "ruby" && RUBY_VERSION <= "1.8")
# please note the use of IO::select() here, as it is used specifically to
# preserve correct signal handling behavior in ruby 1.8.
# https://github.com/thibaudgg/rb-fsevent/commit/d1a868bf8dc72dbca102bedbadff76c7e6c2dc21
# https://github.com/thibaudgg/rb-fsevent/blob/1ca42b987596f350ee7b19d8f8210b7b6ae8766b/ext/fsevent/fsevent_watch.c#L171
def pipe_readable?(pipe)
IO.select([pipe])
end
else
def pipe_readable?(pipe)
!pipe.eof?
end
end
def connect
Timeout.timeout(5) do
while @socket.nil?
attempt_connect
end
end
end
def attempt_connect
@socket = @socket_class.open("127.0.0.1", @server_port)
rescue Errno::ECONNREFUSED
end
def check
result = @socket.gets
result = @connection.gets
result.strip! if result
if result.nil?
@ -234,8 +142,8 @@ class Capybara::Driver::Webkit
end
def read_response
response_length = @socket.gets.to_i
response = @socket.read(response_length)
response_length = @connection.gets.to_i
response = @connection.read(response_length)
response.force_encoding("UTF-8") if response.respond_to?(:force_encoding)
response
end

View File

@ -0,0 +1,118 @@
require 'socket'
require 'timeout'
require 'thread'
class Capybara::Driver::Webkit
class Connection
attr_reader :port
def initialize(options = {})
@socket_class = options[:socket_class] || TCPSocket
@stdout = options.has_key?(:stdout) ?
options[:stdout] :
$stdout
start_server
connect
end
def puts(string)
@socket.puts string
end
def print(string)
@socket.print string
end
def gets
@socket.gets
end
def read(length)
@socket.read(length)
end
private
def start_server
pipe = fork_server
@port = discover_port(pipe)
@stdout_thread = Thread.new do
Thread.current.abort_on_exception = true
forward_stdout(pipe)
end
end
def fork_server
server_path = File.expand_path("../../../../../bin/webkit_server", __FILE__)
pipe, @pid = server_pipe_and_pid(server_path)
register_shutdown_hook
pipe
end
def kill_process(pid)
if RUBY_PLATFORM =~ /mingw32/
Process.kill(9, pid)
else
Process.kill("INT", pid)
end
end
def register_shutdown_hook
@owner_pid = Process.pid
at_exit do
if Process.pid == @owner_pid
kill_process(@pid)
end
end
end
def server_pipe_and_pid(server_path)
cmdline = [server_path]
pipe = IO.popen(cmdline.join(" "))
[pipe, pipe.pid]
end
def discover_port(read_pipe)
return unless IO.select([read_pipe], nil, nil, 10)
((read_pipe.first || '').match(/listening on port: (\d+)/) || [])[1].to_i
end
def forward_stdout(pipe)
while pipe_readable?(pipe)
line = pipe.readline
if @stdout
@stdout.write(line)
@stdout.flush
end
end
rescue EOFError
end
def connect
Timeout.timeout(5) do
while @socket.nil?
attempt_connect
end
end
end
def attempt_connect
@socket = @socket_class.open("127.0.0.1", @port)
rescue Errno::ECONNREFUSED
end
if !defined?(RUBY_ENGINE) || (RUBY_ENGINE == "ruby" && RUBY_VERSION <= "1.8")
# please note the use of IO::select() here, as it is used specifically to
# preserve correct signal handling behavior in ruby 1.8.
# https://github.com/thibaudgg/rb-fsevent/commit/d1a868bf8dc72dbca102bedbadff76c7e6c2dc21
# https://github.com/thibaudgg/rb-fsevent/blob/1ca42b987596f350ee7b19d8f8210b7b6ae8766b/ext/fsevent/fsevent_watch.c#L171
def pipe_readable?(pipe)
IO.select([pipe])
end
else
def pipe_readable?(pipe)
!pipe.eof?
end
end
end
end

View File

@ -2,40 +2,17 @@ require 'spec_helper'
require 'self_signed_ssl_cert'
require 'stringio'
require 'capybara/driver/webkit/browser'
require 'capybara/driver/webkit/connection'
require 'socket'
require 'base64'
describe Capybara::Driver::Webkit::Browser do
let(:browser) { Capybara::Driver::Webkit::Browser.new }
let(:browser_ignore_ssl_err) {
Capybara::Driver::Webkit::Browser.new.tap { |browser| browser.ignore_ssl_errors }
}
describe '#server_port' do
subject { browser.server_port }
it 'returns a valid port number' do
should be_a(Integer)
let(:browser) { Capybara::Driver::Webkit::Browser.new(Capybara::Driver::Webkit::Connection.new) }
let(:browser_ignore_ssl_err) do
Capybara::Driver::Webkit::Browser.new(Capybara::Driver::Webkit::Connection.new).tap do |browser|
browser.ignore_ssl_errors
end
it 'returns a port in the allowed range' do
should be_between 0x400, 0xffff
end
end
context 'random port' do
it 'chooses a new port number for a new browser instance' do
new_browser = Capybara::Driver::Webkit::Browser.new
new_browser.server_port.should_not == browser.server_port
end
end
it 'forwards stdout to the given IO object' do
io = StringIO.new
new_browser = Capybara::Driver::Webkit::Browser.new(:stdout => io)
new_browser.execute_script('console.log("hello world")')
sleep(0.5)
io.string.should include "hello world\n"
end
context 'handling of SSL validation errors' do

54
spec/connection_spec.rb Normal file
View File

@ -0,0 +1,54 @@
require 'spec_helper'
require 'capybara/driver/webkit/connection'
describe Capybara::Driver::Webkit::Connection do
it "boots a server to talk to" do
url = @rack_server.url("/")
connection.puts "Visit"
connection.puts 1
connection.puts url.to_s.bytesize
connection.print url
connection.gets.should == "ok\n"
connection.gets.should == "0\n"
connection.puts "Body"
connection.puts 0
connection.gets.should == "ok\n"
response_length = connection.gets.to_i
response = connection.read(response_length)
response.should include("Hey there")
end
it 'forwards stdout to the given IO object' do
io = StringIO.new
redirected_connection = Capybara::Driver::Webkit::Connection.new(:stdout => io)
script = 'console.log("hello world")'
redirected_connection.puts "Execute"
redirected_connection.puts 1
redirected_connection.puts script.to_s.bytesize
redirected_connection.print script
sleep(0.5)
io.string.should include "hello world\n"
end
it "returns the server port" do
connection.port.should be_between 0x400, 0xffff
end
it "chooses a new port number for a new connection" do
new_connection = Capybara::Driver::Webkit::Connection.new
new_connection.port.should_not == connection.port
end
let(:connection) { Capybara::Driver::Webkit::Connection.new }
before(:all) do
@app = lambda do |env|
body = "<html><body>Hey there</body></html>"
[200,
{ 'Content-Type' => 'text/html', 'Content-Length' => body.size.to_s },
[body]]
end
@rack_server = Capybara::Server.new(@app)
@rack_server.boot
end
end

View File

@ -988,15 +988,15 @@ describe Capybara::Driver::Webkit do
end
def make_the_server_come_back
subject.browser.instance_variable_get(:@socket).unstub!(:gets)
subject.browser.instance_variable_get(:@socket).unstub!(:puts)
subject.browser.instance_variable_get(:@socket).unstub!(:print)
subject.browser.instance_variable_get(:@connection).unstub!(:gets)
subject.browser.instance_variable_get(:@connection).unstub!(:puts)
subject.browser.instance_variable_get(:@connection).unstub!(:print)
end
def make_the_server_go_away
subject.browser.instance_variable_get(:@socket).stub!(:gets).and_return(nil)
subject.browser.instance_variable_get(:@socket).stub!(:puts)
subject.browser.instance_variable_get(:@socket).stub!(:print)
subject.browser.instance_variable_get(:@connection).stub!(:gets).and_return(nil)
subject.browser.instance_variable_get(:@connection).stub!(:puts)
subject.browser.instance_variable_get(:@connection).stub!(:print)
end
end
@ -1091,7 +1091,8 @@ describe Capybara::Driver::Webkit do
context "with socket debugger" do
let(:socket_debugger_class){ Capybara::Driver::Webkit::SocketDebugger }
let(:browser_with_debugger){
Capybara::Driver::Webkit::Browser.new(:socket_class => socket_debugger_class)
connection = Capybara::Driver::Webkit::Connection.new(:socket_class => socket_debugger_class)
Capybara::Driver::Webkit::Browser.new(connection)
}
let(:driver_with_debugger){ Capybara::Driver::Webkit.new(@app, :browser => browser_with_debugger) }

View File

@ -22,8 +22,10 @@ end
require File.join(spec_dir, "spec_helper")
require 'capybara/driver/webkit/connection'
require 'capybara/driver/webkit/browser'
$webkit_browser = Capybara::Driver::Webkit::Browser.new(:socket_class => TCPSocket, :stdout => nil)
connection = Capybara::Driver::Webkit::Connection.new(:socket_class => TCPSocket, :stdout => nil)
$webkit_browser = Capybara::Driver::Webkit::Browser.new(connection)
Capybara.register_driver :reusable_webkit do |app|
Capybara::Driver::Webkit.new(app, :browser => $webkit_browser)