2017-04-20 08:51:38 -04:00
|
|
|
require 'fileutils'
|
|
|
|
|
|
|
|
require 'excon'
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
describe 'Unicorn' do
|
|
|
|
before(:all) do
|
|
|
|
config_lines = File.read('config/unicorn.rb.example').split("\n")
|
|
|
|
|
|
|
|
# Remove these because they make setup harder.
|
|
|
|
config_lines = config_lines.reject do |line|
|
|
|
|
%w[
|
2017-04-28 07:51:19 -04:00
|
|
|
working_directory
|
|
|
|
worker_processes
|
|
|
|
listen
|
2017-04-20 08:51:38 -04:00
|
|
|
pid
|
|
|
|
stderr_path
|
|
|
|
stdout_path
|
|
|
|
].any? { |prefix| line.start_with?(prefix) }
|
|
|
|
end
|
|
|
|
|
|
|
|
config_lines << "working_directory '#{Rails.root}'"
|
|
|
|
|
|
|
|
# We want to have exactly 1 worker process because that makes it
|
|
|
|
# predictable which process will handle our requests.
|
|
|
|
config_lines << 'worker_processes 1'
|
|
|
|
|
|
|
|
@socket_path = File.join(Dir.pwd, 'tmp/tests/unicorn.socket')
|
|
|
|
config_lines << "listen '#{@socket_path}'"
|
|
|
|
|
|
|
|
ready_file = 'tmp/tests/unicorn-worker-ready'
|
|
|
|
FileUtils.rm_f(ready_file)
|
|
|
|
after_fork_index = config_lines.index { |l| l.start_with?('after_fork') }
|
|
|
|
config_lines.insert(after_fork_index + 1, "File.write('#{ready_file}', Process.pid)")
|
|
|
|
|
|
|
|
config_path = 'tmp/tests/unicorn.rb'
|
|
|
|
File.write(config_path, config_lines.join("\n") + "\n")
|
|
|
|
|
2017-11-22 12:46:40 -05:00
|
|
|
rackup_path = 'tmp/tests/config.ru'
|
|
|
|
File.write(rackup_path, <<~EOS)
|
|
|
|
app =
|
|
|
|
proc do |env|
|
|
|
|
if env['REQUEST_METHOD'] == 'GET'
|
|
|
|
[200, {}, [Process.pid]]
|
|
|
|
else
|
|
|
|
Process.kill(env['QUERY_STRING'], Process.pid)
|
|
|
|
[200, {}, ['Bye!']]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
run app
|
|
|
|
EOS
|
|
|
|
|
|
|
|
cmd = %W[unicorn -E test -c #{config_path} #{rackup_path}]
|
2017-04-20 08:51:38 -04:00
|
|
|
@unicorn_master_pid = spawn(*cmd)
|
2017-04-28 06:27:48 -04:00
|
|
|
wait_unicorn_boot!(@unicorn_master_pid, ready_file)
|
2017-04-20 08:51:38 -04:00
|
|
|
WebMock.allow_net_connect!
|
|
|
|
end
|
|
|
|
|
|
|
|
%w[SIGQUIT SIGTERM SIGKILL].each do |signal|
|
|
|
|
it "has a worker that self-terminates on signal #{signal}" do
|
2017-11-22 12:46:40 -05:00
|
|
|
response = Excon.get('unix://', socket: @socket_path)
|
2017-04-20 08:51:38 -04:00
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
worker_pid = response.body.to_i
|
2017-04-28 06:29:15 -04:00
|
|
|
expect(worker_pid).to be > 0
|
2017-04-20 08:51:38 -04:00
|
|
|
|
|
|
|
begin
|
2017-11-22 12:46:40 -05:00
|
|
|
Excon.post("unix://?#{signal}", socket: @socket_path)
|
2017-04-20 08:51:38 -04:00
|
|
|
rescue Excon::Error::Socket
|
|
|
|
# The connection may be closed abruptly
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(pid_gone?(worker_pid)).to eq(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
after(:all) do
|
|
|
|
WebMock.disable_net_connect!(allow_localhost: true)
|
|
|
|
Process.kill('TERM', @unicorn_master_pid)
|
|
|
|
end
|
|
|
|
|
2017-04-28 06:27:48 -04:00
|
|
|
def wait_unicorn_boot!(master_pid, ready_file)
|
2017-06-08 12:35:55 -04:00
|
|
|
# We have seen the boot timeout after 2 minutes in CI so let's set it to 5 minutes.
|
|
|
|
timeout = 5 * 60
|
2017-04-28 06:27:48 -04:00
|
|
|
timeout.times do
|
|
|
|
return if File.exist?(ready_file)
|
2017-11-14 04:02:39 -05:00
|
|
|
|
2017-04-28 06:27:48 -04:00
|
|
|
pid = Process.waitpid(master_pid, Process::WNOHANG)
|
|
|
|
raise "unicorn failed to boot: #{$?}" unless pid.nil?
|
|
|
|
|
|
|
|
sleep 1
|
|
|
|
end
|
|
|
|
|
|
|
|
raise "unicorn boot timed out after #{timeout} seconds"
|
|
|
|
end
|
|
|
|
|
2017-04-20 08:51:38 -04:00
|
|
|
def pid_gone?(pid)
|
2017-04-28 06:27:48 -04:00
|
|
|
# Worker termination should take less than a second. That makes 10
|
|
|
|
# seconds a generous timeout.
|
2017-04-20 08:51:38 -04:00
|
|
|
10.times do
|
|
|
|
begin
|
|
|
|
Process.kill(0, pid)
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
sleep 1
|
|
|
|
end
|
|
|
|
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|