From d6a3fbbde6864559c0b10530088f318abcd15a17 Mon Sep 17 00:00:00 2001 From: Daniel Azuma Date: Wed, 5 Apr 2023 14:50:48 -0700 Subject: [PATCH] feat: Support for Puma 6 and Rack 3 (#152) --- .toys/ci.rb | 62 +++++++++++++++++++++++++----- Gemfile | 4 +- functions_framework.gemspec | 4 +- lib/functions_framework/server.rb | 23 ++++++++--- lib/functions_framework/testing.rb | 2 +- test/test_server.rb | 21 ++++++++-- 6 files changed, 93 insertions(+), 23 deletions(-) diff --git a/.toys/ci.rb b/.toys/ci.rb index 84857fb..7758f57 100644 --- a/.toys/ci.rb +++ b/.toys/ci.rb @@ -14,7 +14,7 @@ desc "Run CI checks" -TESTS = ["unit", "rubocop", "yardoc", "build", "examples", "conformance"] +TESTS = ["unit", "dependencies", "rubocop", "yardoc", "build", "examples", "conformance"] flag :only TESTS.each do |name| @@ -26,26 +26,68 @@ def handle_result result if result.success? - puts "** #{result.name} passed\n\n", :green, :bold + puts "** Passed: #{result.name}\n\n", :green, :bold else - puts "** CI terminated: #{result.name} failed!", :red, :bold - exit 1 + puts "** Failed: #{result.name}\n\n", :red, :bold + @errors << result.name end end def run + @errors = [] ::Dir.chdir context_directory TESTS.each do |name| key = "test_#{name}".to_sym set key, !only if get(key).nil? end - exec ["toys", "test"], name: "Unit tests" if test_unit - exec ["toys", "rubocop"], name: "Style checker" if test_rubocop - exec ["toys", "yardoc"], name: "Docs generation" if test_yardoc - exec ["toys", "build"], name: "Gem build" if test_build + exec_separate_tool ["test"], name: "Unit tests" if test_unit + exec_separate_tool ["ci", "deps-matrix"], name: "Dependency matrix tests" if test_dependencies + exec_separate_tool ["rubocop"], name: "Style checker" if test_rubocop + exec_separate_tool ["yardoc"], name: "Docs generation" if test_yardoc + exec_separate_tool ["build"], name: "Gem build" if test_build ::Dir.foreach "examples" do |dir| next if dir =~ /^\.+$/ - exec ["toys", "test"], name: "Tests for #{dir} example", chdir: ::File.join("examples", dir) + exec_separate_tool ["test"], name: "Tests for #{dir} example", chdir: ::File.join("examples", dir) end if test_examples - exec ["toys", "conformance"], name: "Conformance tests" if test_conformance + exec_separate_tool ["conformance"], name: "Conformance tests" if test_conformance + @errors.each do |err| + puts "Failed: #{err}", :red, :bold + end + exit 1 unless @errors.empty? +end + +tool "deps-matrix" do + static :puma_versions, ["4.0", "5.0", "6.0"] + static :rack_versions, ["2.1", "3.0"] + + include :exec, result_callback: :handle_result + include :terminal + + def handle_result result + if result.success? + puts "** Passed: #{result.name}\n\n", :green, :bold + else + puts "** Failed: #{result.name}\n\n", :red, :bold + @errors << result.name + end + end + + def run + @errors = [] + ::Dir.chdir context_directory + puma_versions.each do |puma_version| + rack_versions.each do |rack_version| + name = "Puma #{puma_version} / Rack #{rack_version}" + env = { + "FF_DEPENDENCY_TEST_PUMA" => "~> #{puma_version}", + "FF_DEPENDENCY_TEST_RACK" => "~> #{rack_version}", + } + exec_separate_tool ["test", "test/test_server.rb"], env: env, name: name + end + end + @errors.each do |err| + puts "Failed: #{err}", :red, :bold + end + exit 1 unless @errors.empty? + end end diff --git a/Gemfile b/Gemfile index e341e68..918e4a2 100644 --- a/Gemfile +++ b/Gemfile @@ -16,9 +16,11 @@ source "https://rubygems.org" gemspec -gem "google-style", "~> 1.26.1" +gem "google-style", "~> 1.26.3" gem "minitest", "~> 5.16" gem "minitest-focus", "~> 1.2" gem "minitest-rg", "~> 5.2" +gem "puma", ENV["FF_DEPENDENCY_TEST_PUMA"] if ENV["FF_DEPENDENCY_TEST_PUMA"] +gem "rack", ENV["FF_DEPENDENCY_TEST_RACK"] if ENV["FF_DEPENDENCY_TEST_RACK"] gem "redcarpet", "~> 3.5" unless ::RUBY_PLATFORM == "java" gem "yard", "~> 0.9.25" diff --git a/functions_framework.gemspec b/functions_framework.gemspec index 05f236f..1143d03 100644 --- a/functions_framework.gemspec +++ b/functions_framework.gemspec @@ -46,8 +46,8 @@ version = ::FunctionsFramework::VERSION spec.required_ruby_version = ">= 2.6.0" spec.add_dependency "cloud_events", ">= 0.7.0", "< 2.a" - spec.add_dependency "puma", ">= 4.3.0", "< 6.a" - spec.add_dependency "rack", "~> 2.1" + spec.add_dependency "puma", ">= 4.3.0", "< 7.a" + spec.add_dependency "rack", ">= 2.1", "< 4.a" if spec.respond_to? :metadata spec.metadata["changelog_uri"] = diff --git a/lib/functions_framework/server.rb b/lib/functions_framework/server.rb index 6483450..947365c 100644 --- a/lib/functions_framework/server.rb +++ b/lib/functions_framework/server.rb @@ -82,10 +82,21 @@ def initialize function, globals def start synchronize do unless running? - @server = ::Puma::Server.new @app - @server.min_threads = @config.min_threads - @server.max_threads = @config.max_threads - @server.leak_stack_on_error = @config.show_error_details? + # Puma >= 6.0 interprets these settings from options + options = { + min_threads: @config.min_threads, + max_threads: @config.max_threads, + environment: @config.show_error_details? ? "development" : "production" + } + # Puma::Events.stdio for Puma < 6.0; otherwise nil for Puma >= 6.0 + events = ::Puma::Events.stdio if ::Puma::Events.respond_to? :stdio + @server = ::Puma::Server.new @app, events, options + if @server.respond_to? :min_threads= + # Puma < 6.0 sets server attributes for these settings + @server.min_threads = @config.min_threads + @server.max_threads = @config.max_threads + @server.leak_stack_on_error = @config.show_error_details? + end @server.binder.add_tcp_listener @config.bind_addr, @config.port @config.logger.info "FunctionsFramework: Serving function #{@function.name.inspect} " \ "on port #{@config.port}..." @@ -377,8 +388,8 @@ def string_response string, status, content_type: nil content_type = "#{content_type}; charset=#{string.encoding.name.downcase}" end headers = { - "Content-Type" => content_type, - "Content-Length" => string.bytesize + "content-type" => content_type, + "content-length" => string.bytesize } [status, headers, [string]] end diff --git a/lib/functions_framework/testing.rb b/lib/functions_framework/testing.rb index 302e231..6a13402 100644 --- a/lib/functions_framework/testing.rb +++ b/lib/functions_framework/testing.rb @@ -367,8 +367,8 @@ def build_standard_env url, headers ::Rack::QUERY_STRING => url.query, ::Rack::SERVER_NAME => url.host, ::Rack::SERVER_PORT => url.port, + ::Rack::SERVER_PROTOCOL => "HTTP/1.1", ::Rack::RACK_URL_SCHEME => url.scheme, - ::Rack::RACK_VERSION => ::Rack::VERSION, ::Rack::RACK_LOGGER => ::FunctionsFramework.logger, ::Rack::RACK_INPUT => ::StringIO.new, ::Rack::RACK_ERRORS => ::StringIO.new diff --git a/test/test_server.rb b/test/test_server.rb index 8382644..cf27257 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -43,7 +43,7 @@ let(:retry_interval) { 0.5 } let(:app_context) { {} } - def make_basic_server function + def make_basic_server function, show_error_details: true FunctionsFramework::Server.new function, app_context do |config| config.min_threads = 1 config.max_threads = 1 @@ -51,7 +51,7 @@ def make_basic_server function config.bind_addr = "127.0.0.1" config.rack_env = "development" config.logger = quiet_logger - config.show_error_details = true + config.show_error_details = show_error_details end end @@ -235,7 +235,7 @@ def query_server_with_retry server assert_match(/あああ/, err) end - it "interprets exceptions" do + it "interprets exceptions showing error details" do function = FunctionsFramework::Function.new "my-func", :http do |_request| raise "Whoops!" end @@ -245,6 +245,21 @@ def query_server_with_retry server end assert_equal "500", response.code assert_match(/RuntimeError: Whoops!\n\t#{__FILE__}/, response.body) + assert_match %r{test/test_server\.rb:}, response.body + assert_equal "text/plain; charset=utf-8", response["Content-Type"] + end + + it "interprets exceptions hiding error details" do + function = FunctionsFramework::Function.new "my-func", :http do |_request| + raise "Whoops!" + end + server = make_basic_server function, show_error_details: false + response = query_server_with_retry server do + ::Net::HTTP.get_response URI("#{server_url}/") + end + assert_equal "500", response.code + refute_match(/Whoops/, response.body) + assert_equal "Unexpected internal error", response.body assert_equal "text/plain; charset=utf-8", response["Content-Type"] end