From 665e0fc729e3028acb58324e0c6350c13e4854af Mon Sep 17 00:00:00 2001 From: Joe Ferris Date: Fri, 4 May 2012 15:50:30 -0400 Subject: [PATCH] Extract a Connection class for booting the server --- lib/capybara/driver/webkit.rb | 3 +- lib/capybara/driver/webkit/browser.rb | 110 ++------------------- lib/capybara/driver/webkit/connection.rb | 118 +++++++++++++++++++++++ spec/browser_spec.rb | 33 +------ spec/connection_spec.rb | 54 +++++++++++ spec/driver_spec.rb | 15 +-- spec/spec_helper.rb | 4 +- 7 files changed, 199 insertions(+), 138 deletions(-) create mode 100644 lib/capybara/driver/webkit/connection.rb create mode 100644 spec/connection_spec.rb diff --git a/lib/capybara/driver/webkit.rb b/lib/capybara/driver/webkit.rb index 042b43b..2ecc054 100644 --- a/lib/capybara/driver/webkit.rb +++ b/lib/capybara/driver/webkit.rb @@ -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 diff --git a/lib/capybara/driver/webkit/browser.rb b/lib/capybara/driver/webkit/browser.rb index 1b80e50..808ddeb 100644 --- a/lib/capybara/driver/webkit/browser.rb +++ b/lib/capybara/driver/webkit/browser.rb @@ -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 diff --git a/lib/capybara/driver/webkit/connection.rb b/lib/capybara/driver/webkit/connection.rb new file mode 100644 index 0000000..baeccb0 --- /dev/null +++ b/lib/capybara/driver/webkit/connection.rb @@ -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 diff --git a/spec/browser_spec.rb b/spec/browser_spec.rb index 28bea01..df9e6a7 100644 --- a/spec/browser_spec.rb +++ b/spec/browser_spec.rb @@ -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 diff --git a/spec/connection_spec.rb b/spec/connection_spec.rb new file mode 100644 index 0000000..5bc2162 --- /dev/null +++ b/spec/connection_spec.rb @@ -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 = "Hey there" + [200, + { 'Content-Type' => 'text/html', 'Content-Length' => body.size.to_s }, + [body]] + end + @rack_server = Capybara::Server.new(@app) + @rack_server.boot + end +end diff --git a/spec/driver_spec.rb b/spec/driver_spec.rb index 12b9184..296c99b 100644 --- a/spec/driver_spec.rb +++ b/spec/driver_spec.rb @@ -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) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 89a1361..7aa3f4f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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)