Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
cache: maven

- name: Setup JRuby
uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
with:
ruby-version: jruby-${{ matrix.jruby_version }}
bundler-cache: 'false' # Need to install later so we can vary from Gemfile.lock as required for JRuby version compatibility
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,20 @@ package and push the .jar every time a commit changes a source file).
* mvn release:perform (possibly with -DuseReleaseProfile=false due to Javadoc doclint failures for now)
* rake clean gem SKIP_SPECS=true and push the gem

## Adding testing for new Rails versions

* Add the new version to `.github/workflows/maven.yml` under the `matrix` section
* Add a new configuration to the `Appraisals` file, then
```bundle exec appraisal generate```
* Generate a new stub Rails application for the new version
```shell
VERSION=rails72
cd src/spec/stub
rm -rf $VERSION && BUNDLE_GEMFILE=~/Projects/community/jruby-rack/gemfiles/${VERSION}_rack22.gemfile bundle exec rails new $VERSION --minimal --skip-git --skip-docker --skip-active-model --skip-active-record --skip-test --skip-system-test --skip-dev-gems --skip-bundle --skip-keeps --skip-asset-pipeline --skip-ci --skip-brakeman --skip-rubocop
```
* Manual changes to make to support testing
* In `config/production.rb` comment out the default `config.logger` value so jruby-rack applies its own `RailsLogger`.

## Support

Please use [github][4] to file bugs, patches and/or pull requests.
Expand Down
181 changes: 141 additions & 40 deletions src/spec/ruby/jruby/rack/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

before(:all) { require 'fileutils' }

#after(:all) { JRuby::Rack.context = nil }

describe 'rack (lambda)' do

before do
Expand Down Expand Up @@ -86,40 +84,141 @@

end

it "should have defined Rails stub tests" do
expect(File.foreach(__FILE__).select { |line| line.include?("describe") }).to include(/^ describe.*lib: :#{CURRENT_LIB}/),
"Expected rails stub tests to be defined for #{CURRENT_LIB} inside integration_spec.rb"
expect(File.exist?(File.join(STUB_DIR, CURRENT_LIB.to_s))).to be(true),
"Expected rails stub dir for #{CURRENT_LIB.to_s} to exist at #{File.join(STUB_DIR, CURRENT_LIB.to_s).inspect}"
end if CURRENT_LIB.to_s.include?('rails')

shared_examples_for 'a rails app', :shared => true do

let(:servlet_context) { new_servlet_context(base_path) }
base_path = "file://#{STUB_DIR}/#{CURRENT_LIB.to_s}"

it "initializes (pooling by default)" do
listener = org.jruby.rack.rails.RailsServletContextListener.new
listener.contextInitialized javax.servlet.ServletContextEvent.new(servlet_context)
let(:servlet_context) do
new_servlet_context(base_path).tap { |servlet_context| prepare_servlet_context(servlet_context, base_path) }
end

rack_factory = servlet_context.getAttribute("rack.factory")
expect(rack_factory).to be_a(RackApplicationFactory)
expect(rack_factory).to be_a(PoolingRackApplicationFactory)
expect(rack_factory).to respond_to(:realFactory)
expect(rack_factory.realFactory).to be_a(RailsRackApplicationFactory)
context "runtime" do

it "initializes (pooling by default)" do
listener = org.jruby.rack.rails.RailsServletContextListener.new
listener.contextInitialized javax.servlet.ServletContextEvent.new(servlet_context)

rack_factory = servlet_context.getAttribute("rack.factory")
expect(rack_factory).to be_a(RackApplicationFactory)
expect(rack_factory).to be_a(PoolingRackApplicationFactory)
expect(rack_factory).to respond_to(:realFactory)
expect(rack_factory.realFactory).to be_a(RailsRackApplicationFactory)

expect(servlet_context.getAttribute("rack.context")).to be_a(RackContext)
expect(servlet_context.getAttribute("rack.context")).to be_a(ServletRackContext)

expect(servlet_context.getAttribute("rack.context")).to be_a(RackContext)
expect(servlet_context.getAttribute("rack.context")).to be_a(ServletRackContext)
expect(rack_factory.getApplication).to be_a(DefaultRackApplication)
end

it "initializes threadsafe!" do
servlet_context.addInitParameter('jruby.max.runtimes', '1')

listener = org.jruby.rack.rails.RailsServletContextListener.new
listener.contextInitialized javax.servlet.ServletContextEvent.new(servlet_context)

rack_factory = servlet_context.getAttribute("rack.factory")
expect(rack_factory).to be_a(RackApplicationFactory)
expect(rack_factory).to be_a(SharedRackApplicationFactory)
expect(rack_factory.realFactory).to be_a(RailsRackApplicationFactory)

expect(rack_factory.getApplication).to be_a(DefaultRackApplication)
expect(rack_factory.getApplication).to be_a(DefaultRackApplication)
end
end

it "initializes threadsafe!" do
servlet_context.addInitParameter('jruby.max.runtimes', '1')
context "initialized" do

listener = org.jruby.rack.rails.RailsServletContextListener.new
listener.contextInitialized javax.servlet.ServletContextEvent.new(servlet_context)
before(:all) { copy_gemfile }

rack_factory = servlet_context.getAttribute("rack.factory")
expect(rack_factory).to be_a(RackApplicationFactory)
expect(rack_factory).to be_a(SharedRackApplicationFactory)
expect(rack_factory.realFactory).to be_a(RailsRackApplicationFactory)
before(:all) do
initialize_rails('production', "file://#{base_path}") do |servlet_context, _|
prepare_servlet_context(servlet_context, base_path)
end
end
after(:all) { restore_rails }

it "loaded rack ~> 2.2.0" do
@runtime = @rack_factory.getApplication.getRuntime
should_eval_as_not_nil "defined?(Rack.release)"
should_eval_as_eql_to "Rack.release.to_s[0, 3]", '2.2'
end

it "booted with a servlet logger" do
@runtime = @rack_factory.getApplication.getRuntime
should_eval_as_not_nil "defined?(Rails)"
should_eval_as_not_nil "Rails.logger"

# production.rb: config.log_level = 'info'
should_eval_as_eql_to "Rails.logger.level", Logger::INFO

# Rails 7.1+ wraps the default in a ActiveSupport::BroadcastLogger
if Rails::VERSION::STRING < '7.1'
should_eval_as_eql_to "Rails.logger.is_a? JRuby::Rack::Logger", true
should_eval_as_eql_to "Rails.logger.is_a? ActiveSupport::TaggedLogging", true
unwrap_logger = "logger = Rails.logger;"
else
should_eval_as_not_nil "defined?(ActiveSupport::BroadcastLogger)"
should_eval_as_eql_to "Rails.logger.is_a? ActiveSupport::BroadcastLogger", true
should_eval_as_eql_to "Rails.logger.broadcasts.size", 1
should_eval_as_eql_to "Rails.logger.broadcasts.first.is_a? JRuby::Rack::Logger", true
# NOTE: TaggedLogging is a module that extends the logger instance:
should_eval_as_eql_to "Rails.logger.broadcasts.first.is_a? ActiveSupport::TaggedLogging", true

should_eval_as_eql_to "Rails.logger.broadcasts.first.level", Logger::INFO

unwrap_logger = "logger = Rails.logger.broadcasts.first;"
end

# sanity check logger-silence works:
should_eval_as_eql_to "#{unwrap_logger} logger.silence { logger.warn('from-integration-spec') }", true

should_eval_as_eql_to "#{unwrap_logger} logger.real_logger.is_a?(Java::OrgJrubyRackServlet::DefaultServletRackContext)", true
end

it "sets up public_path" do
@runtime = @rack_factory.getApplication.getRuntime
should_eval_as_eql_to "Rails.public_path.to_s", "#{base_path}/public"
end

expect(rack_factory.getApplication).to be_a(DefaultRackApplication)
it "disables rack's chunked support (by default)" do
@runtime = @rack_factory.getApplication.getRuntime
expect_to_have_monkey_patched_chunked
end
end
end

describe 'rails 5.0', lib: :rails50 do
it_should_behave_like 'a rails app'
end

describe 'rails 5.2', lib: :rails52 do
it_should_behave_like 'a rails app'
end

describe 'rails 6.0', lib: :rails60 do
it_should_behave_like 'a rails app'
end

describe 'rails 6.1', lib: :rails61 do
it_should_behave_like 'a rails app'
end

describe 'rails 7.0', lib: :rails70 do
it_should_behave_like 'a rails app'
end

describe 'rails 7.1', lib: :rails71 do
it_should_behave_like 'a rails app'
end

describe 'rails 7.2', lib: :rails72 do
it_should_behave_like 'a rails app'
end

def expect_to_have_monkey_patched_chunked
Expand All @@ -135,8 +234,6 @@ def expect_to_have_monkey_patched_chunked
should_eval_as_eql_to script, "1\nsecond"
end

ENV_COPY = ENV.to_h

def initialize_rails(env = nil, servlet_context = @servlet_context)
if !servlet_context || servlet_context.is_a?(String)
base = servlet_context.is_a?(String) ? servlet_context : nil
Expand All @@ -149,41 +246,45 @@ def initialize_rails(env = nil, servlet_context = @servlet_context)
servlet_context.addInitParameter("jruby.runtime.env", the_env)

yield(servlet_context, listener) if block_given?

listener.contextInitialized javax.servlet.ServletContextEvent.new(servlet_context)
@rack_context = servlet_context.getAttribute("rack.context")
@rack_factory = servlet_context.getAttribute("rack.factory")
@servlet_context ||= servlet_context
end

def restore_rails
#ENV['RACK_ENV'] = ENV_COPY['RACK_ENV'] if ENV.key?('RACK_ENV')
#ENV['RAILS_ENV'] = ENV_COPY['RAILS_ENV'] if ENV.key?('RAILS_ENV')
@servlet_context = servlet_context
end

def new_servlet_context(base_path = nil)
servlet_context = org.jruby.rack.mock.RackLoggingMockServletContext.new base_path
servlet_context.logger = raise_logger
prepare_servlet_context servlet_context
servlet_context.logger = raise_logger('WARN').tap { |logger| logger.setEnabled(false) }
servlet_context
end

def prepare_servlet_context(servlet_context)
def prepare_servlet_context(servlet_context, base_path)
set_compat_version servlet_context
servlet_context.addInitParameter('rails.root', base_path)
servlet_context.addInitParameter('jruby.rack.layout_class', 'FileSystemLayout')
end

def set_compat_version(servlet_context = @servlet_context); require 'jruby'
compat_version = JRuby.runtime.getInstanceConfig.getCompatVersion # RUBY1_9
servlet_context.addInitParameter("jruby.compat.version", compat_version.to_s)
end

private

GEMFILES_DIR = File.expand_path('../../../gemfiles', STUB_DIR)

def copy_gemfile(name)
# e.g. 'rails30'
FileUtils.cp File.join(GEMFILES_DIR, "#{name}.gemfile"), File.join(STUB_DIR, "#{name}/WEB-INF/Gemfile")
FileUtils.cp File.join(GEMFILES_DIR, "#{name}.gemfile.lock"), File.join(STUB_DIR, "#{name}/WEB-INF/Gemfile.lock")
def copy_gemfile
name = CURRENT_LIB.to_s
raise "Environment variable BUNDLE_GEMFILE seems to not contain #{name}" unless ENV['BUNDLE_GEMFILE']&.include?(name)
FileUtils.cp ENV['BUNDLE_GEMFILE'], File.join(STUB_DIR, "#{name}/Gemfile")
FileUtils.cp "#{ENV['BUNDLE_GEMFILE']}.lock", File.join(STUB_DIR, "#{name}/Gemfile.lock")
Dir.chdir File.join(STUB_DIR, name)
end

ENV_COPY = ENV.to_h

def restore_rails
ENV['RACK_ENV'] = ENV_COPY['RACK_ENV'] if ENV.key?('RACK_ENV')
ENV['RAILS_ENV'] = ENV_COPY['RAILS_ENV'] if ENV.key?('RAILS_ENV')
end

end
19 changes: 19 additions & 0 deletions src/spec/stub/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
*/tmp
*/*.war

# Rails stub files we don't want
rails*/Rakefile
rails*/Gemfile
rails*/Gemfile.lock
rails*/bin
rails*/README.md
rails*/config.ru
rails*/.ruby-version
rails*/package.json

# Rails generates assets in samples that we don't really want
rails*/**/*.ico
rails*/**/*.png
rails*/**/*.svg
rails*/**/*.html
rails*/**/*.erb
rails*/**/*.js
rails*/**/*.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
2 changes: 2 additions & 0 deletions src/spec/stub/rails50/app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module ApplicationHelper
end
2 changes: 2 additions & 0 deletions src/spec/stub/rails50/app/jobs/application_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class ApplicationJob < ActiveJob::Base
end
25 changes: 25 additions & 0 deletions src/spec/stub/rails50/config/application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
# require "active_record/railtie"
require "action_controller/railtie"
# require "action_mailer/railtie"
require "action_view/railtie"
# require "action_cable/engine"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Rails50
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
end
end
6 changes: 6 additions & 0 deletions src/spec/stub/rails50/config/boot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require 'bundler/setup' # Set up gems listed in the Gemfile.

# workaround for https://github.com/ruby-concurrency/concurrent-ruby/issues/1077 since https://github.com/rails/rails/pull/54264 wont be backported earlier than 7.1.
require "logger"
5 changes: 5 additions & 0 deletions src/spec/stub/rails50/config/environment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Load the Rails application.
require_relative 'application'

# Initialize the Rails application.
Rails.application.initialize!
39 changes: 39 additions & 0 deletions src/spec/stub/rails50/config/environments/development.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.

# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false

# Do not eager load code on boot.
config.eager_load = false

# Show full error reports.
config.consider_all_requests_local = true

# Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true

config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age=172800'
}
else
config.action_controller.perform_caching = false

config.cache_store = :null_store
end

# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log


# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true

# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
# config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
Loading