Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/cypress_on_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Configuration
attr_accessor :server_host
attr_accessor :server_port
attr_accessor :transactional_server
attr_accessor :server_readiness_path

# Attributes for backwards compatibility
def cypress_folder
Expand Down Expand Up @@ -62,6 +63,7 @@ def reset
self.server_host = ENV.fetch('CYPRESS_RAILS_HOST', 'localhost')
self.server_port = ENV.fetch('CYPRESS_RAILS_PORT', nil)
self.transactional_server = true
self.server_readiness_path = ENV.fetch('CYPRESS_RAILS_READINESS_PATH', '/')
end

def tagged_logged
Expand Down
63 changes: 51 additions & 12 deletions lib/cypress_on_rails/server.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'socket'
require 'timeout'
require 'fileutils'
require 'net/http'
require 'cypress_on_rails/configuration'

module CypressOnRails
Expand All @@ -9,13 +10,15 @@ class Server

def initialize(options = {})
config = CypressOnRails.configuration

@framework = options[:framework] || :cypress
@host = options[:host] || config.server_host
@port = options[:port] || config.server_port || find_available_port
@port = @port.to_i if @port
@install_folder = options[:install_folder] || config.install_folder || detect_install_folder
@transactional = options.fetch(:transactional, config.transactional_server)
@server_pid = nil
@server_pgid = nil
end

def open
Expand Down Expand Up @@ -105,30 +108,66 @@ def spawn_server

puts "Starting Rails server: #{server_args.join(' ')}"

spawn(*server_args, out: $stdout, err: $stderr)
@server_pid = spawn(*server_args, out: $stdout, err: $stderr, pgroup: true)
begin
@server_pgid = Process.getpgid(@server_pid)
rescue Errno::ESRCH => e
puts "Warning: Process #{@server_pid} terminated immediately after spawn: #{e.message}"
@server_pgid = nil
end
@server_pid
end

def wait_for_server(timeout = 30)
Timeout.timeout(timeout) do
loop do
begin
TCPSocket.new(host, port).close
break
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
sleep 0.1
end
break if server_responding?
sleep 0.1
end
end
rescue Timeout::Error
raise "Rails server failed to start on #{host}:#{port} after #{timeout} seconds"
end

def server_responding?
config = CypressOnRails.configuration
readiness_path = config.server_readiness_path || '/'
uri = URI("http://#{host}:#{port}#{readiness_path}")

response = Net::HTTP.start(uri.host, uri.port, open_timeout: 1, read_timeout: 1) do |http|
http.get(uri.path.empty? ? '/' : uri.path)
end

# Accept 200-399 (success and redirects), reject 404 and 5xx
(200..399).cover?(response.code.to_i)
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Errno::ETIMEDOUT, SocketError,
Net::OpenTimeout, Net::ReadTimeout, Net::HTTPBadResponse
false
end

def stop_server(pid)
if pid
puts "Stopping Rails server (PID: #{pid})"
Process.kill('TERM', pid)
Process.wait(pid)
return unless pid

puts "Stopping Rails server (PID: #{pid})"
send_term_signal
Process.wait(pid)
rescue Errno::ESRCH
# Process already terminated
end

def send_term_signal
if @server_pgid
Process.kill('TERM', -@server_pgid)
else
safe_kill_process('TERM', @server_pid)
end
rescue Errno::ESRCH, Errno::EPERM => e
puts "Warning: Failed to kill process group #{@server_pgid}: #{e.message}, trying single process"
safe_kill_process('TERM', @server_pid)
end

def safe_kill_process(signal, pid)
Process.kill(signal, pid) if pid
rescue Errno::ESRCH
# Process already terminated
end
Expand Down