2019-02-11 15:44:25 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2021-08-22 18:24:26 -04:00
|
|
|
require "set"
|
2019-02-11 15:44:25 -05:00
|
|
|
require "isolation/abstract_unit"
|
|
|
|
|
|
|
|
class ZeitwerkIntegrationTest < ActiveSupport::TestCase
|
|
|
|
include ActiveSupport::Testing::Isolation
|
|
|
|
|
|
|
|
def setup
|
|
|
|
build_app
|
|
|
|
end
|
|
|
|
|
|
|
|
def boot(env = "development")
|
|
|
|
app(env)
|
|
|
|
end
|
|
|
|
|
|
|
|
def teardown
|
|
|
|
teardown_app
|
|
|
|
end
|
|
|
|
|
|
|
|
def deps
|
|
|
|
ActiveSupport::Dependencies
|
|
|
|
end
|
|
|
|
|
2021-08-23 11:49:48 -04:00
|
|
|
test "The integration is minimally looking good" do
|
2019-02-11 15:44:25 -05:00
|
|
|
boot
|
|
|
|
|
2019-02-14 18:12:57 -05:00
|
|
|
assert Rails.autoloaders.zeitwerk_enabled?
|
|
|
|
assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main
|
|
|
|
assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once
|
|
|
|
assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a
|
2019-02-11 15:44:25 -05:00
|
|
|
end
|
|
|
|
|
2019-02-23 17:37:20 -05:00
|
|
|
test "autoloaders inflect with Active Support" do
|
|
|
|
app_file "config/initializers/inflections.rb", <<-RUBY
|
|
|
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
|
|
inflect.acronym 'RESTful'
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
2019-02-23 18:25:42 -05:00
|
|
|
app_file "app/controllers/restful_controller.rb", <<-RUBY
|
|
|
|
class RESTfulController < ApplicationController
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
2019-02-23 17:37:20 -05:00
|
|
|
boot
|
|
|
|
|
|
|
|
basename = "restful_controller"
|
|
|
|
abspath = "#{Rails.root}/app/controllers/#{basename}.rb"
|
|
|
|
camelized = "RESTfulController"
|
|
|
|
|
|
|
|
Rails.autoloaders.each do |autoloader|
|
|
|
|
assert_equal camelized, autoloader.inflector.camelize(basename, abspath)
|
|
|
|
end
|
2019-02-23 18:25:42 -05:00
|
|
|
|
|
|
|
assert RESTfulController
|
2019-02-23 17:37:20 -05:00
|
|
|
end
|
|
|
|
|
2019-07-25 17:02:03 -04:00
|
|
|
|
2021-08-16 23:23:51 -04:00
|
|
|
test "the once autoloader can autoload from initializers" do
|
|
|
|
app_file "extras0/x.rb", "X = 0"
|
|
|
|
app_file "extras1/y.rb", "Y = 0"
|
|
|
|
|
|
|
|
# We should be able to configure autoload_once_paths in
|
|
|
|
# config/application.rb and in config/environments/*.rb.
|
|
|
|
add_to_config 'config.autoload_once_paths << "#{Rails.root}/extras0"'
|
|
|
|
add_to_env_config "development", 'config.autoload_once_paths << "#{Rails.root}/extras1"'
|
|
|
|
|
|
|
|
# Collections should br frozen after bootstrap, and you are ready to
|
|
|
|
# autoload with the once autoloader. In particular, from initializers.
|
|
|
|
$config_autoload_once_paths_is_frozen = false
|
|
|
|
$global_autoload_once_paths_is_frozen = false
|
|
|
|
add_to_config <<~RUBY
|
|
|
|
initializer :test_autoload_once_paths_is_frozen, after: :bootstrap_hook do
|
|
|
|
$config_autoload_once_paths_is_frozen = config.autoload_once_paths.frozen?
|
|
|
|
$global_autoload_once_paths_is_frozen = ActiveSupport::Dependencies.autoload_once_paths.frozen?
|
|
|
|
X
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
|
|
|
app_file "config/initializers/autoload_Y.rb", "Y"
|
|
|
|
|
|
|
|
# Preconditions.
|
|
|
|
assert_not Object.const_defined?(:X)
|
|
|
|
assert_not Object.const_defined?(:Y)
|
|
|
|
|
|
|
|
boot
|
|
|
|
|
|
|
|
assert Object.const_defined?(:X)
|
|
|
|
assert Object.const_defined?(:Y)
|
|
|
|
assert $config_autoload_once_paths_is_frozen
|
|
|
|
assert $global_autoload_once_paths_is_frozen
|
|
|
|
end
|
|
|
|
|
|
|
|
test "the once autoloader can eager load" do
|
|
|
|
app_file "app/serializers/money_serializer.rb", "MoneySerializer = :dummy_value"
|
|
|
|
|
|
|
|
add_to_config 'config.autoload_once_paths << "#{Rails.root}/app/serializers"'
|
|
|
|
add_to_config 'config.eager_load_paths << "#{Rails.root}/app/serializers"'
|
|
|
|
|
|
|
|
assert_not Object.const_defined?(:MoneySerializer)
|
|
|
|
|
|
|
|
boot("production")
|
|
|
|
|
|
|
|
assert Object.const_defined?(:MoneySerializer)
|
|
|
|
end
|
|
|
|
|
2019-02-11 15:44:25 -05:00
|
|
|
test "eager loading loads the application code" do
|
|
|
|
$zeitwerk_integration_test_user = false
|
|
|
|
$zeitwerk_integration_test_post = false
|
|
|
|
|
|
|
|
app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true"
|
|
|
|
app_file "app/models/post.rb", "class Post; end; $zeitwerk_integration_test_post = true"
|
2019-03-30 07:20:00 -04:00
|
|
|
|
2019-02-11 15:44:25 -05:00
|
|
|
boot("production")
|
|
|
|
|
|
|
|
assert $zeitwerk_integration_test_user
|
|
|
|
assert $zeitwerk_integration_test_post
|
|
|
|
end
|
|
|
|
|
2020-01-07 09:13:02 -05:00
|
|
|
test "eager loading loads the application code if invoked manually too (regression test)" do
|
|
|
|
$zeitwerk_integration_test_user = false
|
|
|
|
$zeitwerk_integration_test_post = false
|
|
|
|
|
|
|
|
app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true"
|
|
|
|
app_file "app/models/post.rb", "class Post; end; $zeitwerk_integration_test_post = true"
|
|
|
|
|
|
|
|
boot
|
|
|
|
|
|
|
|
# Preconditions.
|
2020-01-07 15:41:33 -05:00
|
|
|
assert_not $zeitwerk_integration_test_user
|
|
|
|
assert_not $zeitwerk_integration_test_post
|
2020-01-07 09:13:02 -05:00
|
|
|
|
|
|
|
Rails.application.eager_load!
|
|
|
|
|
|
|
|
# Postconditions.
|
|
|
|
assert $zeitwerk_integration_test_user
|
|
|
|
assert $zeitwerk_integration_test_post
|
|
|
|
end
|
|
|
|
|
2019-04-07 06:30:34 -04:00
|
|
|
test "reloading is enabled if config.cache_classes is false" do
|
|
|
|
boot
|
|
|
|
|
|
|
|
assert Rails.autoloaders.main.reloading_enabled?
|
|
|
|
assert_not Rails.autoloaders.once.reloading_enabled?
|
|
|
|
end
|
|
|
|
|
|
|
|
test "reloading is disabled if config.cache_classes is true" do
|
|
|
|
boot("production")
|
|
|
|
|
|
|
|
assert_not Rails.autoloaders.main.reloading_enabled?
|
|
|
|
assert_not Rails.autoloaders.once.reloading_enabled?
|
|
|
|
end
|
|
|
|
|
2019-03-30 07:20:00 -04:00
|
|
|
test "eager loading loads code in engines" do
|
|
|
|
$test_blog_engine_eager_loaded = false
|
|
|
|
|
|
|
|
engine("blog") do |bukkit|
|
|
|
|
bukkit.write("lib/blog.rb", "class BlogEngine < Rails::Engine; end")
|
|
|
|
bukkit.write("app/models/post.rb", "Post = $test_blog_engine_eager_loaded = true")
|
|
|
|
end
|
|
|
|
|
|
|
|
boot("production")
|
|
|
|
|
|
|
|
assert $test_blog_engine_eager_loaded
|
|
|
|
end
|
|
|
|
|
2019-02-11 15:44:25 -05:00
|
|
|
test "eager loading loads anything managed by Zeitwerk" do
|
2019-02-12 06:07:22 -05:00
|
|
|
$zeitwerk_integration_test_user = false
|
2019-02-11 15:44:25 -05:00
|
|
|
app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true"
|
|
|
|
|
|
|
|
$zeitwerk_integration_test_extras = false
|
|
|
|
app_dir "extras"
|
|
|
|
app_file "extras/webhook_hacks.rb", "WebhookHacks = 1; $zeitwerk_integration_test_extras = true"
|
|
|
|
|
|
|
|
require "zeitwerk"
|
|
|
|
autoloader = Zeitwerk::Loader.new
|
|
|
|
autoloader.push_dir("#{app_path}/extras")
|
|
|
|
autoloader.setup
|
|
|
|
|
|
|
|
boot("production")
|
|
|
|
|
|
|
|
assert $zeitwerk_integration_test_user
|
|
|
|
assert $zeitwerk_integration_test_extras
|
|
|
|
end
|
|
|
|
|
2019-03-30 08:20:37 -04:00
|
|
|
test "autoload directories not present in eager load paths are not eager loaded" do
|
2019-03-30 03:38:41 -04:00
|
|
|
$zeitwerk_integration_test_user = false
|
|
|
|
app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true"
|
|
|
|
|
|
|
|
$zeitwerk_integration_test_lib = false
|
|
|
|
app_dir "lib"
|
|
|
|
app_file "lib/webhook_hacks.rb", "WebhookHacks = 1; $zeitwerk_integration_test_lib = true"
|
|
|
|
|
|
|
|
$zeitwerk_integration_test_extras = false
|
|
|
|
app_dir "extras"
|
|
|
|
app_file "extras/websocket_hacks.rb", "WebsocketHacks = 1; $zeitwerk_integration_test_extras = true"
|
|
|
|
|
|
|
|
add_to_config "config.autoload_paths << '#{app_path}/lib'"
|
|
|
|
add_to_config "config.autoload_once_paths << '#{app_path}/extras'"
|
|
|
|
|
|
|
|
boot("production")
|
|
|
|
|
|
|
|
assert $zeitwerk_integration_test_user
|
2019-04-02 19:40:15 -04:00
|
|
|
assert_not $zeitwerk_integration_test_lib
|
|
|
|
assert_not $zeitwerk_integration_test_extras
|
2019-03-30 03:38:41 -04:00
|
|
|
|
|
|
|
assert WebhookHacks
|
|
|
|
assert WebsocketHacks
|
|
|
|
|
|
|
|
assert $zeitwerk_integration_test_lib
|
|
|
|
assert $zeitwerk_integration_test_extras
|
|
|
|
end
|
|
|
|
|
2021-03-07 16:34:54 -05:00
|
|
|
test "autoload_paths not in autoload_once_paths are set as root dirs of main, and in the same order" do
|
2019-02-11 15:44:25 -05:00
|
|
|
boot
|
|
|
|
|
2021-03-07 16:34:54 -05:00
|
|
|
existing_autoload_paths = \
|
|
|
|
deps.autoload_paths.select { |dir| File.directory?(dir) } -
|
|
|
|
deps.autoload_once_paths
|
2019-03-15 17:50:04 -04:00
|
|
|
assert_equal existing_autoload_paths, Rails.autoloaders.main.dirs
|
|
|
|
end
|
|
|
|
|
|
|
|
test "autoload_once_paths go to the once autoloader, and in the same order" do
|
|
|
|
extras = %w(e1 e2 e3)
|
|
|
|
extras.each do |extra|
|
|
|
|
app_dir extra
|
|
|
|
add_to_config %(config.autoload_once_paths << "\#{Rails.root}/#{extra}")
|
|
|
|
end
|
|
|
|
|
|
|
|
boot
|
|
|
|
|
|
|
|
extras = extras.map { |extra| "#{app_path}/#{extra}" }
|
|
|
|
extras.each do |extra|
|
|
|
|
assert_not_includes Rails.autoloaders.main.dirs, extra
|
|
|
|
end
|
2021-03-07 16:34:54 -05:00
|
|
|
|
|
|
|
e1_index = Rails.autoloaders.once.dirs.index(extras.first)
|
|
|
|
assert e1_index
|
|
|
|
assert_equal extras, Rails.autoloaders.once.dirs.slice(e1_index, extras.length)
|
2019-02-11 15:44:25 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
test "clear reloads the main autoloader, and does not reload the once one" do
|
|
|
|
boot
|
|
|
|
|
|
|
|
$zeitwerk_integration_reload_test = []
|
|
|
|
|
2019-02-14 18:12:57 -05:00
|
|
|
main_autoloader = Rails.autoloaders.main
|
|
|
|
def main_autoloader.reload
|
|
|
|
$zeitwerk_integration_reload_test << :main_autoloader
|
2019-02-11 15:44:25 -05:00
|
|
|
super
|
|
|
|
end
|
|
|
|
|
2019-02-14 18:12:57 -05:00
|
|
|
once_autoloader = Rails.autoloaders.once
|
2019-02-11 15:44:25 -05:00
|
|
|
def once_autoloader.reload
|
|
|
|
$zeitwerk_integration_reload_test << :once_autoloader
|
|
|
|
super
|
|
|
|
end
|
|
|
|
|
|
|
|
ActiveSupport::Dependencies.clear
|
|
|
|
|
2019-02-14 18:12:57 -05:00
|
|
|
assert_equal %i(main_autoloader), $zeitwerk_integration_reload_test
|
2019-02-11 15:44:25 -05:00
|
|
|
end
|
2019-02-18 14:06:28 -05:00
|
|
|
|
2021-08-09 07:15:10 -04:00
|
|
|
test "reloading invokes before_remove_const" do
|
|
|
|
$before_remove_const_invoked = false
|
|
|
|
|
|
|
|
app_file "app/models/foo.rb", <<~RUBY
|
|
|
|
# While the most common use case is classes/modules, the contract does not
|
|
|
|
# require values to be so. Let's weaken the test down to Object.new.
|
|
|
|
Foo = Object.new
|
|
|
|
def Foo.before_remove_const
|
|
|
|
$before_remove_const_invoked = true
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
|
|
|
app_file "app/models/bar.rb", <<~RUBY
|
|
|
|
# This object does not implement before_remove_const. We define it to make
|
|
|
|
# sure reloading does not raise. That is, it does not blindly invoke the
|
|
|
|
# hook on all unloaded objects.
|
|
|
|
Bar = Object.new
|
|
|
|
RUBY
|
|
|
|
|
|
|
|
boot
|
|
|
|
|
|
|
|
assert Foo
|
|
|
|
assert Bar
|
|
|
|
ActiveSupport::Dependencies.clear
|
|
|
|
|
|
|
|
assert $before_remove_const_invoked
|
|
|
|
end
|
|
|
|
|
2021-08-22 18:24:26 -04:00
|
|
|
test "reloading clears autoloaded tracked classes" do
|
|
|
|
eval <<~RUBY
|
|
|
|
class Parent
|
|
|
|
extend ActiveSupport::DescendantsTracker
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
|
|
|
app_file "app/models/child.rb", <<~RUBY
|
|
|
|
class Child < #{self.class.name}::Parent
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
|
|
|
app_file "app/models/grandchild.rb", <<~RUBY
|
|
|
|
class Grandchild < Child
|
|
|
|
end
|
|
|
|
RUBY
|
|
|
|
|
|
|
|
boot
|
|
|
|
assert Grandchild
|
|
|
|
|
|
|
|
# Preconditions, we add some redundancy about descendants tracking.
|
|
|
|
assert_equal Set[Child, Grandchild], ActiveSupport::Dependencies._autoloaded_tracked_classes
|
|
|
|
assert_equal [Child, Grandchild], Parent.descendants
|
|
|
|
|
|
|
|
Rails.application.reloader.reload!
|
|
|
|
|
|
|
|
assert_empty ActiveSupport::Dependencies._autoloaded_tracked_classes
|
|
|
|
assert_equal [], Parent.descendants
|
|
|
|
end
|
|
|
|
|
2019-02-21 04:26:36 -05:00
|
|
|
test "autoloaders.logger=" do
|
|
|
|
boot
|
|
|
|
|
|
|
|
logger = ->(_msg) { }
|
|
|
|
Rails.autoloaders.logger = logger
|
|
|
|
|
|
|
|
Rails.autoloaders.each do |autoloader|
|
2019-02-21 17:35:09 -05:00
|
|
|
assert_same logger, autoloader.logger
|
2019-02-21 04:26:36 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
Rails.autoloaders.logger = Rails.logger
|
|
|
|
|
|
|
|
Rails.autoloaders.each do |autoloader|
|
2019-02-21 17:35:09 -05:00
|
|
|
assert_same Rails.logger, autoloader.logger
|
2019-02-21 04:26:36 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
Rails.autoloaders.logger = nil
|
|
|
|
|
|
|
|
Rails.autoloaders.each do |autoloader|
|
|
|
|
assert_nil autoloader.logger
|
|
|
|
end
|
|
|
|
end
|
2019-08-13 12:14:50 -04:00
|
|
|
|
|
|
|
test "autoloaders.log!" do
|
|
|
|
app_file "extras/utils.rb", "module Utils; end"
|
|
|
|
|
|
|
|
add_to_config %(config.autoload_once_paths << "\#{Rails.root}/extras")
|
|
|
|
add_to_config "Rails.autoloaders.log!"
|
|
|
|
|
|
|
|
out, _err = capture_io { boot }
|
|
|
|
|
|
|
|
assert_match %r/^Zeitwerk@rails.main: autoload set for ApplicationRecord/, out
|
|
|
|
assert_match %r/^Zeitwerk@rails.once: autoload set for Utils/, out
|
|
|
|
end
|
2019-02-11 15:44:25 -05:00
|
|
|
end
|