mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
64c0153cd0
* Add basic JSON serializer For now, it only handles Arrays of Integers, but we'll extend it to support all of the common types * Serialize Strings * Escape quotes in Strings * Escape backslashes in Strings * Serialize Hashes with String keys * Extract method for serializing Strings * Add test coverage for non-Hash non-Array JSON serialization * Add test for unexpected key types * Serialize Hashes with Symbol keys * Raise on unexpected value types * Serialize boolean values * Serialize Floats * Add module comment to Puma::JSON * Update integration test to use fully-qualfied JSON module reference * Remove json gem dependency from /stats status server response Fixes a bug where requesting `/stats` from the status server would cause subsequent phased restarts to fail when upgrading/downgrading the json gem. * Run gc_stats tests on JRuby These were disabled at some point on JRuby, but they seem to run fine. Importantly, this test ensures that a call to `/gc-stats` returns well-formed JSON on JRuby, where the value of `GC.stat` contains nested structures. * Remove json gem dependency from /gc-stats status server response Fixes a bug where requesting `/gc-stats` from the status server would cause subsequent phased restarts to fail when upgrading/downgrading the json gem. * Remove json gem from /thread-backtraces status server response Fixes a bug where requesting `/thread-backtraces` from the status server would cause subsequent phased restarts to fail when upgrading/downgrading the json gem. * Remove json gem dependency from Puma.stats Fixes a bug where accessing `Puma.stats` would cause subsequent phased restarts to fail when upgrading/downgrading the json gem. * Fix test name in json test Co-authored-by: rmacklin <1863540+rmacklin@users.noreply.github.com> * Add History entry * Add test for exceptions on values of unexpected types * Update test name for additional clarity * Reorder cases to match order in ECMA-404 * Allow all serializable inputs in Puma::JSON::serialize The pervious implementation was based on and older JSON standard which defined JSON texts to be either objects or arrays. Modern JSON standands allow all JSON values to be valid JSON texts. * Update JSON tests to test value types directly * Reorder tests to roughly match source order * Add test for serializing integers as JSON * Serialize nil as null * Use block form of gsub instead of hash form * Escape control characters as required by ECMA-404 * Collapse handling of Symbol and String into one case * Extract constants used in string serialization * Remove superflous else case * Use stringio for incremental JSON construction * Extract test helper for testing JSON serialization * Assert that strings generated by Puma::JSON roundtrip when using ::JSON * Use a recent version of the json gem in tests `::JSON.parse` doesn't handle JSON texts other than objects and arrays in old versions * Handle default expected_roundtrip more explicitly for clarity Co-authored-by: rmacklin <1863540+rmacklin@users.noreply.github.com>
447 lines
12 KiB
Ruby
447 lines
12 KiB
Ruby
require_relative "helper"
|
|
require_relative "helpers/ssl" if ::Puma::HAS_SSL
|
|
require_relative "helpers/tmp_path"
|
|
|
|
require "puma/cli"
|
|
require "json"
|
|
|
|
class TestCLI < Minitest::Test
|
|
include SSLHelper if ::Puma::HAS_SSL
|
|
include TmpPath
|
|
|
|
def setup
|
|
@environment = 'production'
|
|
|
|
@tmp_path = tmp_path('puma-test')
|
|
@tmp_path2 = "#{@tmp_path}2"
|
|
|
|
File.unlink @tmp_path if File.exist? @tmp_path
|
|
File.unlink @tmp_path2 if File.exist? @tmp_path2
|
|
|
|
@wait, @ready = IO.pipe
|
|
|
|
@events = Puma::Events.strings
|
|
@events.on_booted { @ready << "!" }
|
|
end
|
|
|
|
def wait_booted
|
|
@wait.sysread 1
|
|
end
|
|
|
|
def teardown
|
|
File.unlink @tmp_path if File.exist? @tmp_path
|
|
File.unlink @tmp_path2 if File.exist? @tmp_path2
|
|
|
|
@wait.close
|
|
@ready.close
|
|
end
|
|
|
|
def test_control_for_tcp
|
|
cntl = UniquePort.call
|
|
url = "tcp://127.0.0.1:#{cntl}/"
|
|
|
|
cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:0",
|
|
"--control-url", url,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new do
|
|
cli.run
|
|
end
|
|
|
|
wait_booted
|
|
|
|
s = TCPSocket.new "127.0.0.1", cntl
|
|
s << "GET /stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
assert_equal Puma.stats_hash, JSON.parse(Puma.stats, symbolize_names: true)
|
|
|
|
dmt = Puma::Configuration.new.default_max_threads
|
|
assert_match(/{"started_at":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z","backlog":0,"running":0,"pool_capacity":#{dmt},"max_threads":#{dmt},"requests_count":0}/, body.split(/\r?\n/).last)
|
|
|
|
ensure
|
|
cli.launcher.stop
|
|
t.join
|
|
end
|
|
|
|
def test_control_for_ssl
|
|
skip 'No ssl support' unless ::Puma::HAS_SSL
|
|
|
|
require "net/http"
|
|
control_port = UniquePort.call
|
|
control_host = "127.0.0.1"
|
|
control_url = "ssl://#{control_host}:#{control_port}?#{ssl_query}"
|
|
token = "token"
|
|
|
|
cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:0",
|
|
"--control-url", control_url,
|
|
"--control-token", token,
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new do
|
|
cli.run
|
|
end
|
|
|
|
wait_booted
|
|
|
|
body = ""
|
|
http = Net::HTTP.new control_host, control_port
|
|
http.use_ssl = true
|
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
http.start do
|
|
req = Net::HTTP::Get.new "/stats?token=#{token}", {}
|
|
body = http.request(req).body
|
|
end
|
|
|
|
dmt = Puma::Configuration.new.default_max_threads
|
|
expected_stats = /{"started_at":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z","backlog":0,"running":0,"pool_capacity":#{dmt},"max_threads":#{dmt}/
|
|
assert_match(expected_stats, body.split(/\r?\n/).last)
|
|
|
|
ensure
|
|
cli.launcher.stop if cli
|
|
t.join if t
|
|
end
|
|
|
|
def test_control_clustered
|
|
skip NO_FORK_MSG unless HAS_FORK
|
|
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
|
url = "unix://#{@tmp_path}"
|
|
|
|
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
|
|
"-t", "2:2",
|
|
"-w", "2",
|
|
"--control-url", url,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
# without this, Minitest.after_run will trigger on this test ?
|
|
$debugging_hold = true
|
|
|
|
t = Thread.new { cli.run }
|
|
|
|
wait_booted
|
|
|
|
s = UNIXSocket.new @tmp_path
|
|
s << "GET /stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
require 'json'
|
|
status = JSON.parse(body.split("\n").last)
|
|
|
|
assert_equal 2, status["workers"]
|
|
|
|
s = UNIXSocket.new @tmp_path
|
|
s << "GET /stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
assert_match(/\{"started_at":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z","workers":2,"phase":0,"booted_workers":2,"old_workers":0,"worker_status":\[\{"started_at":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z","pid":\d+,"index":0,"phase":0,"booted":true,"last_checkin":"[^"]+","last_status":\{"backlog":0,"running":2,"pool_capacity":2,"max_threads":2,"requests_count":0\}\},\{"started_at":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z","pid":\d+,"index":1,"phase":0,"booted":true,"last_checkin":"[^"]+","last_status":\{"backlog":0,"running":2,"pool_capacity":2,"max_threads":2,"requests_count":0\}\}\]\}/, body.split("\r\n").last)
|
|
ensure
|
|
if UNIX_SKT_EXIST && HAS_FORK
|
|
cli.launcher.stop
|
|
t.join
|
|
|
|
done = nil
|
|
until done
|
|
@events.stdout.rewind
|
|
log = @events.stdout.readlines.join ''
|
|
done = log[/ - Goodbye!/]
|
|
end
|
|
|
|
$debugging_hold = false
|
|
end
|
|
end
|
|
|
|
def test_control
|
|
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
|
url = "unix://#{@tmp_path}"
|
|
|
|
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
|
|
"--control-url", url,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new { cli.run }
|
|
|
|
wait_booted
|
|
|
|
s = UNIXSocket.new @tmp_path
|
|
s << "GET /stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
dmt = Puma::Configuration.new.default_max_threads
|
|
assert_match(/{"started_at":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z","backlog":0,"running":0,"pool_capacity":#{dmt},"max_threads":#{dmt},"requests_count":0}/, body.split("\r\n").last)
|
|
ensure
|
|
if UNIX_SKT_EXIST
|
|
cli.launcher.stop
|
|
t.join
|
|
end
|
|
end
|
|
|
|
def test_control_stop
|
|
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
|
url = "unix://#{@tmp_path}"
|
|
|
|
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
|
|
"--control-url", url,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new { cli.run }
|
|
|
|
wait_booted
|
|
|
|
s = UNIXSocket.new @tmp_path
|
|
s << "GET /stop HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
assert_equal '{ "status": "ok" }', body.split("\r\n").last
|
|
ensure
|
|
t.join if UNIX_SKT_EXIST
|
|
end
|
|
|
|
def test_control_requests_count
|
|
tcp = UniquePort.call
|
|
cntl = UniquePort.call
|
|
url = "tcp://127.0.0.1:#{cntl}/"
|
|
|
|
cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:#{tcp}",
|
|
"--control-url", url,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new do
|
|
cli.run
|
|
end
|
|
|
|
wait_booted
|
|
|
|
s = TCPSocket.new "127.0.0.1", cntl
|
|
s << "GET /stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
assert_equal 0, JSON.parse(body.split(/\r?\n/).last)['requests_count']
|
|
|
|
# send real requests to server
|
|
3.times do
|
|
s = TCPSocket.new "127.0.0.1", tcp
|
|
s << "GET / HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
end
|
|
|
|
s = TCPSocket.new "127.0.0.1", cntl
|
|
s << "GET /stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
assert_equal 3, JSON.parse(body.split(/\r?\n/).last)['requests_count']
|
|
ensure
|
|
cli.launcher.stop
|
|
t.join
|
|
end
|
|
|
|
def test_control_thread_backtraces
|
|
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
|
url = "unix://#{@tmp_path}"
|
|
|
|
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
|
|
"--control-url", url,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new { cli.run }
|
|
|
|
wait_booted
|
|
|
|
s = UNIXSocket.new @tmp_path
|
|
s << "GET /thread-backtraces HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
assert_match %r{Thread: TID-}, body.split("\r\n").last
|
|
ensure
|
|
cli.launcher.stop if cli
|
|
t.join if UNIX_SKT_EXIST
|
|
end
|
|
|
|
def control_gc_stats(uri, cntl)
|
|
cli = Puma::CLI.new ["-b", uri,
|
|
"--control-url", cntl,
|
|
"--control-token", "",
|
|
"test/rackup/lobster.ru"], @events
|
|
|
|
t = Thread.new do
|
|
cli.run
|
|
end
|
|
|
|
wait_booted
|
|
|
|
s = yield
|
|
s << "GET /gc-stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
lines = body.split("\r\n")
|
|
json_line = lines.detect { |l| l[0] == "{" }
|
|
pairs = json_line.scan(/\"[^\"]+\": [^,]+/)
|
|
gc_stats = {}
|
|
pairs.each do |p|
|
|
p =~ /\"([^\"]+)\": ([^,]+)/ || raise("Can't parse #{p.inspect}!")
|
|
gc_stats[$1] = $2
|
|
end
|
|
gc_count_before = gc_stats["count"].to_i
|
|
|
|
s = yield
|
|
s << "GET /gc HTTP/1.0\r\n\r\n"
|
|
body = s.read # Ignored
|
|
s.close
|
|
|
|
s = yield
|
|
s << "GET /gc-stats HTTP/1.0\r\n\r\n"
|
|
body = s.read
|
|
s.close
|
|
|
|
lines = body.split("\r\n")
|
|
json_line = lines.detect { |l| l[0] == "{" }
|
|
gc_stats = JSON.parse(json_line)
|
|
gc_count_after = gc_stats["count"].to_i
|
|
|
|
# Hitting the /gc route should increment the count by 1
|
|
assert(gc_count_before < gc_count_after, "make sure a gc has happened")
|
|
|
|
ensure
|
|
cli.launcher.stop if cli
|
|
t.join
|
|
end
|
|
|
|
def test_control_gc_stats_tcp
|
|
uri = "tcp://127.0.0.1:#{UniquePort.call}/"
|
|
cntl_port = UniquePort.call
|
|
cntl = "tcp://127.0.0.1:#{cntl_port}/"
|
|
|
|
control_gc_stats(uri, cntl) { TCPSocket.new "127.0.0.1", cntl_port }
|
|
end
|
|
|
|
def test_control_gc_stats_unix
|
|
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
|
|
|
uri = "unix://#{@tmp_path2}"
|
|
cntl = "unix://#{@tmp_path}"
|
|
|
|
control_gc_stats(uri, cntl) { UNIXSocket.new @tmp_path }
|
|
end
|
|
|
|
def test_tmp_control
|
|
skip_on :jruby, suffix: " - Unknown issue"
|
|
|
|
cli = Puma::CLI.new ["--state", @tmp_path, "--control-url", "auto"]
|
|
cli.launcher.write_state
|
|
|
|
data = YAML.load File.read(@tmp_path)
|
|
|
|
assert_equal Process.pid, data["pid"]
|
|
|
|
url = data["control_url"]
|
|
|
|
m = %r!unix://(.*)!.match(url)
|
|
|
|
assert m, "'#{url}' is not a URL"
|
|
end
|
|
|
|
def test_state_file_callback_filtering
|
|
skip NO_FORK_MSG unless HAS_FORK
|
|
cli = Puma::CLI.new [ "--config", "test/config/state_file_testing_config.rb",
|
|
"--state", @tmp_path ]
|
|
cli.launcher.write_state
|
|
|
|
data = YAML.load_file(@tmp_path)
|
|
|
|
keys_not_stripped = data.keys & Puma::CLI::KEYS_NOT_TO_PERSIST_IN_STATE
|
|
assert_empty keys_not_stripped
|
|
end
|
|
|
|
def test_log_formatter_default_single
|
|
cli = Puma::CLI.new [ ]
|
|
assert_instance_of Puma::Events::DefaultFormatter, cli.launcher.events.formatter
|
|
end
|
|
|
|
def test_log_formatter_default_clustered
|
|
skip NO_FORK_MSG unless HAS_FORK
|
|
|
|
cli = Puma::CLI.new [ "-w 2" ]
|
|
assert_instance_of Puma::Events::PidFormatter, cli.launcher.events.formatter
|
|
end
|
|
|
|
def test_log_formatter_custom_single
|
|
cli = Puma::CLI.new [ "--config", "test/config/custom_log_formatter.rb" ]
|
|
assert_instance_of Proc, cli.launcher.events.formatter
|
|
assert_match(/^\[.*\] \[.*\] .*: test$/, cli.launcher.events.format('test'))
|
|
end
|
|
|
|
def test_log_formatter_custom_clustered
|
|
skip NO_FORK_MSG unless HAS_FORK
|
|
|
|
cli = Puma::CLI.new [ "--config", "test/config/custom_log_formatter.rb", "-w 2" ]
|
|
assert_instance_of Proc, cli.launcher.events.formatter
|
|
assert_match(/^\[.*\] \[.*\] .*: test$/, cli.launcher.events.format('test'))
|
|
end
|
|
|
|
def test_state
|
|
url = "tcp://127.0.0.1:#{UniquePort.call}"
|
|
cli = Puma::CLI.new ["--state", @tmp_path, "--control-url", url]
|
|
cli.launcher.write_state
|
|
|
|
data = YAML.load File.read(@tmp_path)
|
|
|
|
assert_equal Process.pid, data["pid"]
|
|
assert_equal url, data["control_url"]
|
|
end
|
|
|
|
def test_load_path
|
|
Puma::CLI.new ["--include", 'foo/bar']
|
|
|
|
assert_equal 'foo/bar', $LOAD_PATH[0]
|
|
$LOAD_PATH.shift
|
|
|
|
Puma::CLI.new ["--include", 'foo/bar:baz/qux']
|
|
|
|
assert_equal 'foo/bar', $LOAD_PATH[0]
|
|
$LOAD_PATH.shift
|
|
assert_equal 'baz/qux', $LOAD_PATH[0]
|
|
$LOAD_PATH.shift
|
|
end
|
|
|
|
def test_extra_runtime_dependencies
|
|
cli = Puma::CLI.new ['--extra-runtime-dependencies', 'a,b']
|
|
extra_dependencies = cli.instance_variable_get(:@conf)
|
|
.instance_variable_get(:@options)[:extra_runtime_dependencies]
|
|
|
|
assert_equal %w[a b], extra_dependencies
|
|
end
|
|
|
|
def test_environment_rack_env
|
|
ENV.delete 'RACK_ENV'
|
|
|
|
Puma::CLI.new ["--environment", @environment]
|
|
|
|
assert_equal @environment, ENV['RACK_ENV']
|
|
end
|
|
|
|
def test_environment_rails_env
|
|
ENV.delete 'RACK_ENV'
|
|
ENV['RAILS_ENV'] = @environment
|
|
|
|
Puma::CLI.new []
|
|
|
|
assert_equal @environment, ENV['RACK_ENV']
|
|
|
|
ENV.delete 'RAILS_ENV'
|
|
end
|
|
end
|