2009-07-10 01:25:04 +00:00
|
|
|
require 'socket'
|
2009-07-09 18:17:58 +00:00
|
|
|
require 'erb'
|
2009-08-26 22:55:08 +00:00
|
|
|
require 'json'
|
2009-07-09 18:17:58 +00:00
|
|
|
|
|
|
|
module Jasmine
|
2009-07-13 22:08:11 +00:00
|
|
|
# this seemingly-over-complex method is necessary to get an open port on at least some of our Macs
|
2009-07-13 22:07:45 +00:00
|
|
|
def self.open_socket_on_unused_port
|
|
|
|
infos = Socket::getaddrinfo("localhost", nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)
|
|
|
|
families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
|
|
|
|
|
|
|
|
return TCPServer.open('0.0.0.0', 0) if families.has_key?('AF_INET')
|
|
|
|
return TCPServer.open('::', 0) if families.has_key?('AF_INET6')
|
|
|
|
return TCPServer.open(0)
|
|
|
|
end
|
|
|
|
|
2009-07-10 01:25:04 +00:00
|
|
|
def self.find_unused_port
|
2009-07-13 22:07:45 +00:00
|
|
|
socket = open_socket_on_unused_port
|
2009-07-10 01:25:04 +00:00
|
|
|
port = socket.addr[1]
|
|
|
|
socket.close
|
|
|
|
port
|
|
|
|
end
|
|
|
|
|
2009-08-10 20:49:59 +00:00
|
|
|
def self.server_is_listening_on(hostname, port)
|
|
|
|
require 'socket'
|
|
|
|
begin
|
|
|
|
socket = TCPSocket.open(hostname, port)
|
|
|
|
rescue Errno::ECONNREFUSED
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
socket.close
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.wait_for_listener(port, name = "required process", seconds_to_wait = 10)
|
|
|
|
time_out_at = Time.now + seconds_to_wait
|
|
|
|
until server_is_listening_on "localhost", port
|
|
|
|
sleep 0.1
|
|
|
|
puts "Waiting for #{name} on #{port}..."
|
|
|
|
raise "#{name} didn't show up on port #{port} after #{seconds_to_wait} seconds." if Time.now > time_out_at
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.kill_process_group(process_group_id, signal="TERM")
|
|
|
|
Process.kill signal, -process_group_id # negative pid means kill process group. (see man 2 kill)
|
|
|
|
end
|
|
|
|
|
2009-07-09 18:17:58 +00:00
|
|
|
class RunAdapter
|
2009-08-10 20:44:02 +00:00
|
|
|
def initialize(spec_files_or_proc)
|
|
|
|
@spec_files_or_proc = spec_files_or_proc
|
2009-07-09 18:17:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def call(env)
|
2009-08-10 20:44:02 +00:00
|
|
|
spec_files = @spec_files_or_proc
|
|
|
|
spec_files = spec_files.call if spec_files.respond_to?(:call)
|
|
|
|
|
2009-07-09 18:17:58 +00:00
|
|
|
body = ERB.new(File.read(File.join(File.dirname(__FILE__), "run.html"))).result(binding)
|
|
|
|
[
|
|
|
|
200,
|
|
|
|
{ 'Content-Type' => 'text/html' },
|
|
|
|
body
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-08-26 22:55:08 +00:00
|
|
|
class Redirect
|
|
|
|
def initialize(url)
|
|
|
|
@url = url
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(env)
|
|
|
|
[
|
|
|
|
302,
|
|
|
|
{ 'Location' => @url },
|
|
|
|
[]
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-07-09 18:17:58 +00:00
|
|
|
class SimpleServer
|
2009-08-10 20:44:02 +00:00
|
|
|
def self.start(port, spec_files_or_proc, mappings)
|
2009-07-10 21:35:24 +00:00
|
|
|
require 'thin'
|
2009-07-09 18:17:58 +00:00
|
|
|
|
|
|
|
config = {
|
2009-08-26 22:55:08 +00:00
|
|
|
'/' => Jasmine::Redirect.new('/run.html'),
|
2009-08-10 20:44:02 +00:00
|
|
|
'/run.html' => Jasmine::RunAdapter.new(spec_files_or_proc)
|
2009-07-09 18:17:58 +00:00
|
|
|
}
|
|
|
|
mappings.each do |from, to|
|
|
|
|
config[from] = Rack::File.new(to)
|
2009-08-27 05:41:19 +00:00
|
|
|
end
|
2009-07-09 18:17:58 +00:00
|
|
|
|
|
|
|
app = Rack::URLMap.new(config)
|
|
|
|
|
2009-07-10 01:25:04 +00:00
|
|
|
Thin::Server.start('0.0.0.0', port, app)
|
2009-07-09 18:17:58 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class SimpleClient
|
|
|
|
def initialize(selenium_host, selenium_port, selenium_browser_start_command, http_address)
|
|
|
|
require 'selenium'
|
|
|
|
@driver = Selenium::Client::Driver.new(
|
|
|
|
selenium_host,
|
|
|
|
selenium_port,
|
|
|
|
selenium_browser_start_command,
|
|
|
|
http_address
|
|
|
|
)
|
|
|
|
@http_address = http_address
|
|
|
|
end
|
|
|
|
|
|
|
|
def tests_have_finished?
|
|
|
|
@driver.get_eval("window.jasmine.getEnv().currentRunner.finished") == "true"
|
|
|
|
end
|
|
|
|
|
2009-07-10 17:14:27 +00:00
|
|
|
def connect
|
2009-07-09 18:17:58 +00:00
|
|
|
@driver.start
|
2009-07-10 01:25:04 +00:00
|
|
|
@driver.open("/run.html")
|
2009-07-10 17:14:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def disconnect
|
|
|
|
@driver.stop
|
|
|
|
end
|
2009-07-09 18:17:58 +00:00
|
|
|
|
2009-07-10 17:14:27 +00:00
|
|
|
def run
|
2009-07-09 18:17:58 +00:00
|
|
|
until tests_have_finished? do
|
|
|
|
sleep 0.1
|
|
|
|
end
|
|
|
|
|
|
|
|
puts @driver.get_eval("window.getResults()")
|
|
|
|
failed_count = @driver.get_eval("window.jasmine.getEnv().currentRunner.getResults().failedCount").to_i
|
|
|
|
failed_count == 0
|
|
|
|
end
|
2009-07-10 17:14:27 +00:00
|
|
|
|
|
|
|
def eval_js(script)
|
|
|
|
escaped_script = "'" + script.gsub(/(['\\])/) { '\\' + $1 } + "'"
|
|
|
|
|
2009-08-26 22:55:08 +00:00
|
|
|
result = @driver.get_eval("eval(#{escaped_script}, window)")
|
2009-07-10 21:35:24 +00:00
|
|
|
JSON.parse("[#{result}]")[0]
|
2009-07-10 17:14:27 +00:00
|
|
|
end
|
2009-07-09 18:17:58 +00:00
|
|
|
end
|
2009-07-10 01:14:10 +00:00
|
|
|
|
|
|
|
class Runner
|
|
|
|
def initialize(selenium_jar_path, spec_files, dir_mappings)
|
|
|
|
@selenium_jar_path = selenium_jar_path
|
|
|
|
@spec_files = spec_files
|
|
|
|
@dir_mappings = dir_mappings
|
2009-07-10 17:14:27 +00:00
|
|
|
|
|
|
|
@selenium_pid = nil
|
|
|
|
@jasmine_server_pid = nil
|
2009-07-10 01:14:10 +00:00
|
|
|
end
|
|
|
|
|
2009-07-10 17:14:27 +00:00
|
|
|
def start
|
|
|
|
start_servers
|
|
|
|
@client = Jasmine::SimpleClient.new("localhost", @selenium_server_port, "*firefox", "http://localhost:#{@jasmine_server_port}/")
|
|
|
|
@client.connect
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop
|
|
|
|
@client.disconnect
|
|
|
|
stop_servers
|
|
|
|
end
|
|
|
|
|
|
|
|
def start_servers
|
|
|
|
@jasmine_server_port = Jasmine::find_unused_port
|
|
|
|
@selenium_server_port = Jasmine::find_unused_port
|
|
|
|
|
|
|
|
@selenium_pid = fork do
|
2009-07-14 17:35:53 +00:00
|
|
|
Process.setpgrp
|
2009-07-16 00:03:40 +00:00
|
|
|
exec "java -jar #{@selenium_jar_path} -port #{@selenium_server_port} > /dev/null 2>&1"
|
2009-07-10 17:14:27 +00:00
|
|
|
end
|
|
|
|
puts "selenium started. pid is #{@selenium_pid}"
|
2009-07-10 01:25:04 +00:00
|
|
|
|
2009-07-10 17:14:27 +00:00
|
|
|
@jasmine_server_pid = fork do
|
2009-07-14 17:35:53 +00:00
|
|
|
Process.setpgrp
|
2009-07-10 17:14:27 +00:00
|
|
|
Jasmine::SimpleServer.start(@jasmine_server_port, @spec_files, @dir_mappings)
|
2009-07-14 17:35:53 +00:00
|
|
|
exit! 0
|
2009-07-10 17:14:27 +00:00
|
|
|
end
|
|
|
|
puts "jasmine server started. pid is #{@jasmine_server_pid}"
|
2009-07-10 01:14:10 +00:00
|
|
|
|
2009-08-10 20:49:59 +00:00
|
|
|
Jasmine::wait_for_listener(@selenium_server_port, "selenium server")
|
|
|
|
Jasmine::wait_for_listener(@jasmine_server_port, "jasmine server")
|
2009-07-14 17:35:53 +00:00
|
|
|
end
|
|
|
|
|
2009-07-10 17:14:27 +00:00
|
|
|
def stop_servers
|
|
|
|
puts "shutting down the servers..."
|
2009-08-10 20:49:59 +00:00
|
|
|
Jasmine::kill_process_group(@selenium_pid) if @selenium_pid
|
|
|
|
Jasmine::kill_process_group(@jasmine_server_pid) if @jasmine_server_pid
|
2009-07-10 17:14:27 +00:00
|
|
|
end
|
2009-07-10 01:14:10 +00:00
|
|
|
|
2009-07-10 17:14:27 +00:00
|
|
|
def run
|
|
|
|
begin
|
|
|
|
start
|
2009-07-10 01:14:10 +00:00
|
|
|
puts "servers are listening on their ports -- running the test script..."
|
2009-07-10 17:14:27 +00:00
|
|
|
tests_passed = @client.run
|
2009-07-10 01:14:10 +00:00
|
|
|
ensure
|
2009-07-10 17:14:27 +00:00
|
|
|
stop
|
2009-07-10 01:14:10 +00:00
|
|
|
end
|
|
|
|
return tests_passed
|
|
|
|
end
|
2009-07-10 17:14:27 +00:00
|
|
|
|
|
|
|
def eval_js(script)
|
|
|
|
@client.eval_js(script)
|
|
|
|
end
|
2009-07-10 01:14:10 +00:00
|
|
|
end
|
2009-07-09 18:17:58 +00:00
|
|
|
end
|