2017-05-12 15:16:55 -04:00
|
|
|
require_relative "helper"
|
2011-12-07 16:43:16 -05:00
|
|
|
|
2016-11-22 10:05:49 -05:00
|
|
|
require "puma/cli"
|
|
|
|
require "puma/control_cli"
|
2011-12-07 17:48:41 -05:00
|
|
|
|
2012-11-29 15:05:29 -05:00
|
|
|
# These don't run on travis because they're too fragile
|
|
|
|
|
2016-11-22 10:05:49 -05:00
|
|
|
class TestIntegration < Minitest::Test
|
2011-12-07 16:43:16 -05:00
|
|
|
def setup
|
|
|
|
@state_path = "test/test_puma.state"
|
|
|
|
@bind_path = "test/test_server.sock"
|
|
|
|
@control_path = "test/test_control.sock"
|
2016-02-06 22:00:29 -05:00
|
|
|
@token = "xxyyzz"
|
2012-09-02 23:33:09 -04:00
|
|
|
@tcp_port = 9998
|
|
|
|
|
|
|
|
@server = nil
|
2013-07-05 19:54:15 -04:00
|
|
|
|
|
|
|
@wait, @ready = IO.pipe
|
|
|
|
|
|
|
|
@events = Puma::Events.strings
|
|
|
|
@events.on_booted { @ready << "!" }
|
2011-12-07 16:43:16 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def teardown
|
|
|
|
File.unlink @state_path rescue nil
|
2017-06-04 00:23:55 -04:00
|
|
|
File.unlink @bind_path rescue nil
|
2011-12-07 16:43:16 -05:00
|
|
|
File.unlink @control_path rescue nil
|
2012-09-02 23:33:09 -04:00
|
|
|
|
2013-07-05 19:54:15 -04:00
|
|
|
@wait.close
|
|
|
|
@ready.close
|
|
|
|
|
2012-09-02 23:33:09 -04:00
|
|
|
if @server
|
|
|
|
Process.kill "INT", @server.pid
|
2013-06-19 19:16:15 -04:00
|
|
|
begin
|
|
|
|
Process.wait @server.pid
|
|
|
|
rescue Errno::ECHILD
|
|
|
|
end
|
|
|
|
|
2012-09-02 23:33:09 -04:00
|
|
|
@server.close
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-02 16:24:01 -04:00
|
|
|
def server(argv)
|
2017-06-09 08:20:03 -04:00
|
|
|
# when we were started with bundler all load-paths and bin-paths are setup correctly
|
|
|
|
# this is what 9X% of users run, so it is what we should test
|
|
|
|
# the other case is solely for package builders or testing 1-off cases where the system puma is used
|
|
|
|
base = (defined?(Bundler) ? "bundle exec puma" : "#{Gem.ruby} -Ilib bin/puma")
|
|
|
|
cmd = "#{base} -b tcp://127.0.0.1:#{@tcp_port} #{argv}"
|
2017-06-02 16:24:01 -04:00
|
|
|
@server = IO.popen(cmd, "r")
|
2012-09-06 01:09:42 -04:00
|
|
|
|
2017-04-01 14:12:54 -04:00
|
|
|
wait_for_server_to_boot
|
2012-09-06 01:09:42 -04:00
|
|
|
|
2012-09-02 23:33:09 -04:00
|
|
|
@server
|
|
|
|
end
|
|
|
|
|
2017-08-16 11:23:51 -04:00
|
|
|
def start_forked_server(argv)
|
|
|
|
pid = fork do
|
|
|
|
exec "#{Gem.ruby} -I lib/ bin/puma -b tcp://127.0.0.1:#{@tcp_port} #{argv}"
|
|
|
|
end
|
|
|
|
|
|
|
|
sleep 5
|
|
|
|
pid
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop_forked_server(pid)
|
|
|
|
Process.kill(:TERM, pid)
|
|
|
|
sleep 1
|
|
|
|
Process.wait2(pid)
|
|
|
|
end
|
|
|
|
|
2017-06-02 16:24:01 -04:00
|
|
|
def restart_server_and_listen(argv)
|
2017-07-27 14:18:58 -04:00
|
|
|
skip_on_appveyor
|
2017-06-02 16:24:01 -04:00
|
|
|
server(argv)
|
|
|
|
s = connect
|
2017-06-04 00:23:55 -04:00
|
|
|
initial_reply = read_body(s)
|
2017-06-02 16:24:01 -04:00
|
|
|
restart_server(s)
|
2017-06-04 00:23:55 -04:00
|
|
|
[initial_reply, read_body(connect)]
|
2017-06-02 16:24:01 -04:00
|
|
|
end
|
|
|
|
|
2012-09-02 23:33:09 -04:00
|
|
|
def signal(which)
|
|
|
|
Process.kill which, @server.pid
|
2011-12-07 16:43:16 -05:00
|
|
|
end
|
|
|
|
|
2013-07-05 19:54:15 -04:00
|
|
|
def wait_booted
|
|
|
|
@wait.sysread 1
|
|
|
|
end
|
|
|
|
|
2017-04-01 14:12:54 -04:00
|
|
|
# reuses an existing connection to make sure that works
|
|
|
|
def restart_server(connection)
|
|
|
|
signal :USR2
|
|
|
|
|
|
|
|
connection.write "GET / HTTP/1.1\r\n\r\n" # trigger it to start by sending a new request
|
|
|
|
|
|
|
|
wait_for_server_to_boot
|
|
|
|
end
|
|
|
|
|
|
|
|
def connect
|
|
|
|
s = TCPSocket.new "localhost", @tcp_port
|
|
|
|
s << "GET / HTTP/1.1\r\n\r\n"
|
|
|
|
true until s.gets == "\r\n"
|
|
|
|
s
|
|
|
|
end
|
|
|
|
|
|
|
|
def wait_for_server_to_boot
|
2017-05-29 20:52:04 -04:00
|
|
|
true while @server.gets !~ /Ctrl-C/ # wait for server to say it booted
|
2017-04-01 14:12:54 -04:00
|
|
|
end
|
|
|
|
|
2017-06-04 00:23:55 -04:00
|
|
|
def read_body(connection)
|
|
|
|
Timeout.timeout(10) do
|
|
|
|
loop do
|
|
|
|
response = connection.readpartial(1024)
|
|
|
|
body = response.split("\r\n\r\n", 2).last
|
|
|
|
return body if body && !body.empty?
|
|
|
|
sleep 0.01
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-12-07 16:43:16 -05:00
|
|
|
def test_stop_via_pumactl
|
2017-06-04 00:22:37 -04:00
|
|
|
skip if Puma.jruby? || Puma.windows?
|
2011-12-07 17:58:30 -05:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
conf = Puma::Configuration.new do |c|
|
|
|
|
c.quiet
|
|
|
|
c.state_path @state_path
|
|
|
|
c.bind "unix://#{@bind_path}"
|
|
|
|
c.activate_control_app "unix://#{@control_path}", :auth_token => @token
|
2017-04-11 17:08:18 -04:00
|
|
|
c.rackup "test/rackup/hello.ru"
|
2016-02-06 22:00:29 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
l = Puma::Launcher.new conf, :events => @events
|
2011-12-07 16:43:16 -05:00
|
|
|
|
2011-12-07 17:48:41 -05:00
|
|
|
t = Thread.new do
|
2016-02-06 22:00:29 -05:00
|
|
|
Thread.current.abort_on_exception = true
|
|
|
|
l.run
|
2011-12-07 16:43:16 -05:00
|
|
|
end
|
|
|
|
|
2013-07-05 19:54:15 -04:00
|
|
|
wait_booted
|
2011-12-07 16:53:36 -05:00
|
|
|
|
|
|
|
s = UNIXSocket.new @bind_path
|
|
|
|
s << "GET / HTTP/1.0\r\n\r\n"
|
2017-06-04 00:23:55 -04:00
|
|
|
assert_equal "Hello World", read_body(s)
|
2011-12-07 16:53:36 -05:00
|
|
|
|
2013-07-05 19:54:15 -04:00
|
|
|
sout = StringIO.new
|
|
|
|
|
2011-12-07 17:48:41 -05:00
|
|
|
ccli = Puma::ControlCLI.new %W!-S #{@state_path} stop!, sout
|
2011-12-07 16:43:16 -05:00
|
|
|
|
2011-12-07 17:48:41 -05:00
|
|
|
ccli.run
|
2011-12-07 16:43:16 -05:00
|
|
|
|
2011-12-07 17:48:41 -05:00
|
|
|
assert_kind_of Thread, t.join(1), "server didn't stop"
|
2011-12-07 16:43:16 -05:00
|
|
|
end
|
2012-09-02 23:33:09 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
def test_phased_restart_via_pumactl
|
2017-06-04 00:22:37 -04:00
|
|
|
skip "Too finicky, fails 50% of the time on CI" if ENV["CI"]
|
|
|
|
skip if Puma.jruby? || Puma.windows?
|
2015-01-15 07:58:16 -05:00
|
|
|
|
2017-06-04 08:48:04 -04:00
|
|
|
launcher_thread = Thread.new do
|
|
|
|
conf = Puma::Configuration.new do |c|
|
|
|
|
c.quiet
|
|
|
|
c.state_path @state_path
|
|
|
|
c.bind "unix://#{@bind_path}"
|
|
|
|
c.activate_control_app "unix://#{@control_path}", :auth_token => @token
|
|
|
|
c.workers 2
|
|
|
|
c.worker_shutdown_timeout 1
|
|
|
|
c.rackup "test/rackup/hello-stuck.ru"
|
|
|
|
end
|
|
|
|
Puma::Launcher.new(conf, :events => @events).run
|
2015-01-15 07:58:16 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
wait_booted
|
|
|
|
|
|
|
|
# Make both workers stuck
|
|
|
|
s1 = UNIXSocket.new @bind_path
|
|
|
|
s1 << "GET / HTTP/1.0\r\n\r\n"
|
|
|
|
|
|
|
|
sout = StringIO.new
|
|
|
|
|
|
|
|
# Phased restart
|
2017-06-04 08:48:04 -04:00
|
|
|
ccli = Puma::ControlCLI.new ["-S", @state_path, "phased-restart"], sout
|
2015-01-15 07:58:16 -05:00
|
|
|
ccli.run
|
|
|
|
sleep 20
|
|
|
|
@events.stdout.rewind
|
|
|
|
log = @events.stdout.readlines.join("")
|
|
|
|
assert_match(/TERM sent/, log)
|
2016-12-12 14:56:15 -05:00
|
|
|
assert_match(/- Worker \d \(pid: \d+\) booted, phase: 1/, log)
|
2015-01-15 07:58:16 -05:00
|
|
|
|
|
|
|
# Stop
|
2017-06-04 08:48:04 -04:00
|
|
|
ccli = Puma::ControlCLI.new ["-S", @state_path, "stop"], sout
|
2015-01-15 07:58:16 -05:00
|
|
|
ccli.run
|
|
|
|
|
2017-06-04 08:48:04 -04:00
|
|
|
assert_kind_of Thread, launcher_thread.join(5), "server didn't stop"
|
2015-01-15 07:58:16 -05:00
|
|
|
end
|
|
|
|
|
2016-12-12 14:56:15 -05:00
|
|
|
def test_kill_unknown_via_pumactl
|
2017-06-04 00:22:37 -04:00
|
|
|
skip if Puma.jruby? || Puma.windows?
|
2016-12-12 14:56:15 -05:00
|
|
|
|
|
|
|
# we run ls to get a 'safe' pid to pass off as puma in cli stop
|
|
|
|
# do not want to accidently kill a valid other process
|
|
|
|
io = IO.popen("ls")
|
|
|
|
safe_pid = io.pid
|
|
|
|
Process.wait safe_pid
|
|
|
|
|
|
|
|
sout = StringIO.new
|
|
|
|
|
|
|
|
e = assert_raises SystemExit do
|
|
|
|
ccli = Puma::ControlCLI.new %W!-p #{safe_pid} stop!, sout
|
|
|
|
ccli.run
|
|
|
|
end
|
|
|
|
sout.rewind
|
|
|
|
assert_match(/No pid '\d+' found/, sout.readlines.join(""))
|
|
|
|
assert_equal(1, e.status)
|
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
def test_restart_closes_keepalive_sockets
|
2017-06-02 16:24:01 -04:00
|
|
|
_, new_reply = restart_server_and_listen("-q test/rackup/hello.ru")
|
|
|
|
assert_equal "Hello World", new_reply
|
2012-09-02 23:33:09 -04:00
|
|
|
end
|
2012-09-06 01:09:42 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
def test_restart_closes_keepalive_sockets_workers
|
2017-06-04 00:22:37 -04:00
|
|
|
skip_on_jruby
|
2017-06-02 16:24:01 -04:00
|
|
|
_, new_reply = restart_server_and_listen("-q -w 2 test/rackup/hello.ru")
|
|
|
|
assert_equal "Hello World", new_reply
|
2017-04-01 14:12:54 -04:00
|
|
|
end
|
2012-09-10 11:35:50 -04:00
|
|
|
|
2017-04-01 14:12:54 -04:00
|
|
|
# It does not share environments between multiple generations, which would break Dotenv
|
|
|
|
def test_restart_restores_environment
|
2017-06-04 00:22:37 -04:00
|
|
|
# jruby has a bug where setting `nil` into the ENV or `delete` do not change the
|
|
|
|
# next workers ENV
|
|
|
|
skip_on_jruby
|
2017-05-30 12:00:50 -04:00
|
|
|
|
2017-06-02 16:24:01 -04:00
|
|
|
initial_reply, new_reply = restart_server_and_listen("-q test/rackup/hello-env.ru")
|
2017-04-01 14:12:54 -04:00
|
|
|
|
|
|
|
assert_includes initial_reply, "Hello RAND"
|
|
|
|
assert_includes new_reply, "Hello RAND"
|
|
|
|
refute_equal initial_reply, new_reply
|
2012-09-10 11:35:50 -04:00
|
|
|
end
|
2017-08-16 11:23:51 -04:00
|
|
|
|
|
|
|
def test_term_signal_exit_code_in_single_mode
|
|
|
|
skip if Puma.jruby? || Puma.windows?
|
|
|
|
|
|
|
|
pid = start_forked_server("test/rackup/hello.ru")
|
|
|
|
_, status = stop_forked_server(pid)
|
|
|
|
|
|
|
|
assert_equal 15, status
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_term_signal_exit_code_in_clustered_mode
|
|
|
|
skip if Puma.jruby? || Puma.windows?
|
|
|
|
|
|
|
|
pid = start_forked_server("-w 2 test/rackup/hello.ru")
|
|
|
|
_, status = stop_forked_server(pid)
|
|
|
|
|
|
|
|
assert_equal 15, status
|
|
|
|
end
|
2016-03-06 00:44:53 -05:00
|
|
|
end
|