puma/test/test_config.rb

623 lines
16 KiB
Ruby

# frozen_string_literal: true
require_relative "helper"
require_relative "helpers/config_file"
require "puma/configuration"
require 'puma/log_writer'
require 'rack'
class TestConfigFile < TestConfigFileBase
parallelize_me!
def test_default_max_threads
max_threads = 16
max_threads = 5 if RUBY_ENGINE.nil? || RUBY_ENGINE == 'ruby'
assert_equal max_threads, Puma::Configuration.new.options.default_options[:max_threads]
end
def test_app_from_rackup
skip_if :rack3
conf = Puma::Configuration.new do |c|
c.rackup "test/rackup/hello-bind.ru"
end
conf.load
# suppress deprecation warning of Rack (>= 2.2.0)
# > Parsing options from the first comment line is deprecated!\n
assert_output(nil, nil) do
conf.app
end
assert_equal ["tcp://127.0.0.1:9292"], conf.options[:binds]
end
def test_app_from_app_DSL
conf = Puma::Configuration.new do |c|
c.load "test/config/app.rb"
end
conf.load
app = conf.app
assert_equal [200, {}, ["embedded app"]], app.call({})
end
def test_ssl_configuration_from_DSL
skip_unless :ssl
conf = Puma::Configuration.new do |config|
config.load "test/config/ssl_config.rb"
end
conf.load
bind_configuration = conf.options.file_options[:binds].first
app = conf.app
assert bind_configuration =~ %r{ca=.*ca.crt}
assert bind_configuration&.include?('verify_mode=peer')
assert_equal [200, {}, ["embedded app"]], app.call({})
end
def test_ssl_self_signed_configuration_from_DSL
skip_if :jruby
skip_unless :ssl
conf = Puma::Configuration.new do |config|
config.load "test/config/ssl_self_signed_config.rb"
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?&verify_mode=none"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind
skip_if :jruby
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "/path/to/cert",
key: "/path/to/key",
verify_mode: "the_verify_mode",
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?cert=%2Fpath%2Fto%2Fcert&key=%2Fpath%2Fto%2Fkey&verify_mode=the_verify_mode"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_with_escaped_filenames
skip_if :jruby
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "/path/to/cert+1",
ca: "/path/to/ca+1",
key: "/path/to/key+1",
verify_mode: :peer
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?cert=%2Fpath%2Fto%2Fcert%2B1&key=%2Fpath%2Fto%2Fkey%2B1&verify_mode=peer&ca=%2Fpath%2Fto%2Fca%2B1"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_with_cert_and_key_pem
skip_if :jruby
skip_unless :ssl
cert_path = File.expand_path "../examples/puma/client-certs", __dir__
cert_pem = File.read("#{cert_path}/server.crt")
key_pem = File.read("#{cert_path}/server.key")
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert_pem: cert_pem,
key_pem: key_pem,
verify_mode: "the_verify_mode",
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?cert=store%3A0&key=store%3A1&verify_mode=the_verify_mode"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_with_backlog
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
backlog: "2048",
}
end
conf.load
ssl_binding = conf.options[:binds].first
assert ssl_binding.include?('&backlog=2048')
end
def test_ssl_bind_jruby
skip_unless :jruby
skip_unless :ssl
ciphers = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
keystore: "/path/to/keystore",
keystore_pass: "password",
cipher_suites: ciphers,
protocols: 'TLSv1.2',
verify_mode: "the_verify_mode"
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \
"&keystore-pass=password&cipher_suites=#{ciphers}&protocols=TLSv1.2" \
"&verify_mode=the_verify_mode"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_jruby_with_ssl_cipher_list
skip_unless :jruby
skip_unless :ssl
cipher_list = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
keystore: "/path/to/keystore",
keystore_pass: "password",
ssl_cipher_list: cipher_list,
verify_mode: "the_verify_mode"
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \
"&keystore-pass=password&ssl_cipher_list=#{cipher_list}" \
"&verify_mode=the_verify_mode"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_jruby_with_truststore
skip_unless :jruby
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
keystore: "/path/to/keystore",
keystore_type: "pkcs12",
keystore_pass: "password",
truststore: "default",
truststore_type: "jks",
verify_mode: "none"
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?keystore=/path/to/keystore" \
"&keystore-pass=password&keystore-type=pkcs12" \
"&truststore=default&truststore-type=jks" \
"&verify_mode=none"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_no_tlsv1_1
skip_if :jruby
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "/path/to/cert",
key: "/path/to/key",
verify_mode: "the_verify_mode",
no_tlsv1_1: true
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?cert=%2Fpath%2Fto%2Fcert&key=%2Fpath%2Fto%2Fkey&verify_mode=the_verify_mode&no_tlsv1_1=true"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_with_cipher_filter
skip_if :jruby
skip_unless :ssl
cipher_filter = "!aNULL:AES+SHA"
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "cert",
key: "key",
ssl_cipher_filter: cipher_filter,
}
end
conf.load
ssl_binding = conf.options[:binds].first
assert ssl_binding.include?("&ssl_cipher_filter=#{cipher_filter}")
end
def test_ssl_bind_with_verification_flags
skip_if :jruby
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "cert",
key: "key",
verification_flags: ["TRUSTED_FIRST", "NO_CHECK_TIME"]
}
end
conf.load
ssl_binding = conf.options[:binds].first
assert ssl_binding.include?("&verification_flags=TRUSTED_FIRST,NO_CHECK_TIME")
end
def test_ssl_bind_with_ca
skip_unless :ssl
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "/path/to/cert",
ca: "/path/to/ca",
key: "/path/to/key",
verify_mode: :peer,
}
end
conf.load
ssl_binding = conf.options[:binds].first
assert_includes ssl_binding, Puma::Util.escape("/path/to/ca")
assert_includes ssl_binding, "verify_mode=peer"
end
def test_lowlevel_error_handler_DSL
conf = Puma::Configuration.new do |c|
c.load "test/config/app.rb"
end
conf.load
app = conf.options[:lowlevel_error_handler]
assert_equal [200, {}, ["error page"]], app.call({})
end
def test_allow_users_to_override_default_options
conf = Puma::Configuration.new(restart_cmd: 'bin/rails server')
assert_equal 'bin/rails server', conf.options[:restart_cmd]
end
def test_overwrite_options
conf = Puma::Configuration.new do |c|
c.workers 3
end
conf.load
assert_equal conf.options[:workers], 3
conf.options[:workers] += 1
assert_equal conf.options[:workers], 4
end
def test_explicit_config_files
conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c|
end
conf.load
assert_match(/:3000$/, conf.options[:binds].first)
end
def test_parameters_overwrite_files
conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c|
c.port 3030
end
conf.load
assert_match(/:3030$/, conf.options[:binds].first)
assert_equal 3, conf.options[:min_threads]
assert_equal 5, conf.options[:max_threads]
end
def test_config_files_default
conf = Puma::Configuration.new do
end
assert_equal [nil], conf.config_files
end
def test_config_files_with_dash
conf = Puma::Configuration.new(config_files: ['-']) do
end
assert_equal [], conf.config_files
end
def test_config_files_with_existing_path
conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do
end
assert_equal ['test/config/settings.rb'], conf.config_files
end
def test_config_files_with_non_existing_path
conf = Puma::Configuration.new(config_files: ['test/config/typo/settings.rb']) do
end
assert_equal ['test/config/typo/settings.rb'], conf.config_files
end
def test_config_files_with_integer_convert
conf = Puma::Configuration.new(config_files: ['test/config/with_integer_convert.rb']) do
end
conf.load
assert_equal 6, conf.options[:persistent_timeout]
assert_equal 3, conf.options[:first_data_timeout]
assert_equal 2, conf.options[:workers]
assert_equal 4, conf.options[:min_threads]
assert_equal 8, conf.options[:max_threads]
assert_equal 90, conf.options[:worker_timeout]
assert_equal 120, conf.options[:worker_boot_timeout]
assert_equal 150, conf.options[:worker_shutdown_timeout]
end
def test_config_files_with_float_convert
conf = Puma::Configuration.new(config_files: ['test/config/with_float_convert.rb']) do
end
conf.load
assert_equal Float::INFINITY, conf.options[:max_fast_inline]
end
def test_config_files_with_symbol_convert
conf = Puma::Configuration.new(config_files: ['test/config/with_symbol_convert.rb']) do
end
conf.load
assert_equal :ruby, conf.options[:io_selector_backend]
end
def test_config_raise_exception_on_sigterm
conf = Puma::Configuration.new do |c|
c.raise_exception_on_sigterm false
end
conf.load
assert_equal conf.options[:raise_exception_on_sigterm], false
conf.options[:raise_exception_on_sigterm] = true
assert_equal conf.options[:raise_exception_on_sigterm], true
end
def test_run_hooks_on_restart_hook
assert_run_hooks :on_restart
end
def test_run_hooks_before_worker_fork
assert_run_hooks :before_worker_fork, configured_with: :on_worker_fork
end
def test_run_hooks_after_worker_fork
assert_run_hooks :after_worker_fork
end
def test_run_hooks_before_worker_boot
assert_run_hooks :before_worker_boot, configured_with: :on_worker_boot
end
def test_run_hooks_before_worker_shutdown
assert_run_hooks :before_worker_shutdown, configured_with: :on_worker_shutdown
end
def test_run_hooks_before_fork
assert_run_hooks :before_fork
end
def test_run_hooks_and_exception
conf = Puma::Configuration.new do |c|
c.on_restart do |a|
raise RuntimeError, 'Error from hook'
end
end
conf.load
log_writer = Puma::LogWriter.strings
conf.run_hooks(:on_restart, 'ARG', log_writer)
expected = /WARNING hook on_restart failed with exception \(RuntimeError\) Error from hook/
assert_match expected, log_writer.stdout.string
end
def test_config_does_not_load_workers_by_default
assert_equal 0, Puma::Configuration.new.options.default_options[:workers]
end
def test_final_options_returns_merged_options
conf = Puma::Configuration.new({ min_threads: 1, max_threads: 2 }, { min_threads: 2 })
assert_equal 1, conf.final_options[:min_threads]
assert_equal 2, conf.final_options[:max_threads]
end
def test_silence_single_worker_warning_default
conf = Puma::Configuration.new
conf.load
assert_equal false, conf.options[:silence_single_worker_warning]
end
def test_silence_single_worker_warning_overwrite
conf = Puma::Configuration.new do |c|
c.silence_single_worker_warning
end
conf.load
assert_equal true, conf.options[:silence_single_worker_warning]
end
private
def assert_run_hooks(hook_name, options = {})
configured_with = options[:configured_with] || hook_name
# test single, not an array
messages = []
conf = Puma::Configuration.new
conf.options[hook_name] = -> (a) {
messages << "#{hook_name} is called with #{a}"
}
conf.run_hooks(hook_name, 'ARG', Puma::LogWriter.strings)
assert_equal messages, ["#{hook_name} is called with ARG"]
# test multiple
messages = []
conf = Puma::Configuration.new do |c|
c.send(configured_with) do |a|
messages << "#{hook_name} is called with #{a} one time"
end
c.send(configured_with) do |a|
messages << "#{hook_name} is called with #{a} a second time"
end
end
conf.load
conf.run_hooks(hook_name, 'ARG', Puma::LogWriter.strings)
assert_equal messages, ["#{hook_name} is called with ARG one time", "#{hook_name} is called with ARG a second time"]
end
end
# Thread unsafe modification of ENV
class TestEnvModifificationConfig < TestConfigFileBase
def test_double_bind_port
port = (rand(10_000) + 30_000).to_s
with_env("PORT" => port) do
conf = Puma::Configuration.new do |user_config, file_config, default_config|
user_config.bind "tcp://#{Puma::Configuration::DEFAULTS[:tcp_host]}:#{port}"
file_config.load "test/config/app.rb"
end
conf.load
assert_equal ["tcp://0.0.0.0:#{port}"], conf.options[:binds]
end
end
end
class TestConfigEnvVariables < TestConfigFileBase
def test_config_loads_correct_min_threads
assert_equal 0, Puma::Configuration.new.options.default_options[:min_threads]
with_env("MIN_THREADS" => "7") do
conf = Puma::Configuration.new
assert_equal 7, conf.options.default_options[:min_threads]
end
with_env("PUMA_MIN_THREADS" => "8") do
conf = Puma::Configuration.new
assert_equal 8, conf.options.default_options[:min_threads]
end
end
def test_config_loads_correct_max_threads
conf = Puma::Configuration.new
with_env("MAX_THREADS" => "7") do
conf = Puma::Configuration.new
assert_equal 7, conf.options.default_options[:max_threads]
end
with_env("PUMA_MAX_THREADS" => "8") do
conf = Puma::Configuration.new
assert_equal 8, conf.options.default_options[:max_threads]
end
end
def test_config_loads_workers_from_env
with_env("WEB_CONCURRENCY" => "9") do
conf = Puma::Configuration.new
assert_equal 9, conf.options.default_options[:workers]
end
end
def test_config_does_not_preload_app_if_not_using_workers
with_env("WEB_CONCURRENCY" => "0") do
conf = Puma::Configuration.new
assert_equal false, conf.options.default_options[:preload_app]
end
end
def test_config_preloads_app_if_using_workers
with_env("WEB_CONCURRENCY" => "2") do
preload = Puma.forkable?
conf = Puma::Configuration.new
assert_equal preload, conf.options.default_options[:preload_app]
end
end
end
class TestConfigFileWithFakeEnv < TestConfigFileBase
def setup
FileUtils.mkpath("config/puma")
File.write("config/puma/fake-env.rb", "")
end
def test_config_files_with_app_env
with_env('APP_ENV' => 'fake-env') do
conf = Puma::Configuration.new do
end
assert_equal ['config/puma/fake-env.rb'], conf.config_files
end
end
def test_config_files_with_rack_env
with_env('RACK_ENV' => 'fake-env') do
conf = Puma::Configuration.new do
end
assert_equal ['config/puma/fake-env.rb'], conf.config_files
end
end
def test_config_files_with_rails_env
with_env('RAILS_ENV' => 'fake-env', 'RACK_ENV' => nil) do
conf = Puma::Configuration.new do
end
assert_equal ['config/puma/fake-env.rb'], conf.config_files
end
end
def test_config_files_with_specified_environment
conf = Puma::Configuration.new do
end
conf.options[:environment] = 'fake-env'
assert_equal ['config/puma/fake-env.rb'], conf.config_files
end
def teardown
FileUtils.rm_r("config/puma")
end
end