diff options
-rw-r--r-- | Gemfile.lock | 4 | ||||
-rw-r--r-- | actionmailbox/lib/action_mailbox/test_helper.rb | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 3 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/query_methods.rb | 9 | ||||
-rw-r--r-- | activerecord/test/cases/relation_test.rb | 1 | ||||
-rw-r--r-- | activesupport/CHANGELOG.md | 8 | ||||
-rw-r--r-- | activesupport/activesupport.gemspec | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/dependencies/zeitwerk_integration.rb | 13 | ||||
-rw-r--r-- | activesupport/lib/active_support/descendants_tracker.rb | 11 | ||||
-rw-r--r-- | railties/Rakefile | 41 | ||||
-rw-r--r-- | railties/test/application/zeitwerk_integration_test.rb | 71 |
11 files changed, 135 insertions, 40 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index b1f9991adc..8e219f3f31 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,7 +71,7 @@ PATH i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - zeitwerk (~> 2.0) + zeitwerk (~> 2.1) rails (6.0.0.beta3) actioncable (= 6.0.0.beta3) actionmailbox (= 6.0.0.beta3) @@ -526,7 +526,7 @@ GEM websocket-extensions (0.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.0.0) + zeitwerk (2.1.0) PLATFORMS java diff --git a/actionmailbox/lib/action_mailbox/test_helper.rb b/actionmailbox/lib/action_mailbox/test_helper.rb index 0ec9152844..d248fa8734 100644 --- a/actionmailbox/lib/action_mailbox/test_helper.rb +++ b/actionmailbox/lib/action_mailbox/test_helper.rb @@ -29,16 +29,16 @@ module ActionMailbox create_inbound_email_from_fixture(*args).tap(&:route) end - # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_mail+ - # and immediately route it to processing. + # Create an +InboundEmail+ using the same arguments as +create_inbound_email_from_mail+ and immediately route it to + # processing. def receive_inbound_email_from_mail(**kwargs) create_inbound_email_from_mail(**kwargs).tap(&:route) end - # Create an +InboundEmail+ from fixture using the same arguments as +create_inbound_email_from_source+ - # and immediately route it to processing. - def receive_inbound_email_from_source(**kwargs) - create_inbound_email_from_source(**kwargs).tap(&:route) + # Create an +InboundEmail+ using the same arguments as +create_inbound_email_from_source+ and immediately route it + # to processing. + def receive_inbound_email_from_source(*args) + create_inbound_email_from_source(*args).tap(&:route) end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f8c2e48808..91318a0af1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -422,9 +422,10 @@ module ActiveRecord } # Returns the version of the connected PostgreSQL server. - def get_database_version + def get_database_version # :nodoc: @connection.server_version end + alias :postgresql_version :database_version def default_index_type?(index) # :nodoc: index.using == :btree || super diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7ea18c3069..90b5e9a118 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -252,9 +252,6 @@ module ActiveRecord def _select!(*fields) # :nodoc: fields.reject!(&:blank?) fields.flatten! - fields.map! do |field| - klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field - end self.select_values += fields self end @@ -1164,9 +1161,9 @@ module ActiveRecord case field when Symbol field = field.to_s - arel_column(field) { connection.quote_table_name(field) } + arel_column(field, &connection.method(:quote_table_name)) when String - arel_column(field) { field } + arel_column(field, &:itself) when Proc field.call else @@ -1182,7 +1179,7 @@ module ActiveRecord if klass.columns_hash.key?(field) && (!from || table_name_matches?(from)) arel_attribute(field) else - yield + yield field end end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 90b208092f..3f370e5ede 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -292,6 +292,7 @@ module ActiveRecord klass.create!(description: "foo") assert_equal ["foo"], klass.select(:description).from(klass.all).map(&:desc) + assert_equal ["foo"], klass.reselect(:description).from(klass.all).map(&:desc) end def test_relation_merging_with_merged_joins_as_strings diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index cf72673dbb..57e03b5e12 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,11 @@ +* The Zeitwerk compatibility interface for `ActiveSupport::Dependencies` no + longer implements `autoloaded_constants` or `autoloaded?` (undocumented, + anyway). Experience shows introspection does not have many use cases, and + troubleshooting is done by logging. With this design trade-off we are able + to use even less memory in all environments. + + *Xavier Noria* + * Depends on Zeitwerk 2, which stores less metadata if reloading is disabled and hence uses less memory when `config.cache_classes` is `true`, a standard setup in production. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index e719fc6176..546cc1797a 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -34,5 +34,5 @@ Gem::Specification.new do |s| s.add_dependency "tzinfo", "~> 1.1" s.add_dependency "minitest", "~> 5.1" s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2" - s.add_dependency "zeitwerk", "~> 2.0" + s.add_dependency "zeitwerk", "~> 2.1" end diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb index faf9edef27..b0f7a3f9cc 100644 --- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb +++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb @@ -21,17 +21,8 @@ module ActiveSupport ActiveSupport::Inflector.safe_constantize(cpath) end - def autoloaded_constants - cpaths = [] - Rails.autoloaders.each do |autoloader| - cpaths.concat(autoloader.loaded_cpaths.to_a) - end - cpaths - end - - def autoloaded?(object) - cpath = object.is_a?(Module) ? object.name : object.to_s - Rails.autoloaders.any? { |autoloader| autoloader.loaded?(cpath) } + def to_unload?(cpath) + Rails.autoloaders.main.to_unload?(cpath) end def verbose=(verbose) diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 2dca990712..fe0c6991aa 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -22,11 +22,18 @@ module ActiveSupport def clear if defined? ActiveSupport::Dependencies + # to_unload? is only defined in Zeitwerk mode. + to_unload = if Dependencies.respond_to?(:to_unload?) + ->(klass) { Dependencies.to_unload?(klass.name) } + else + ->(klass) { Dependencies.autoloaded?(klass) } + end + @@direct_descendants.each do |klass, descendants| - if ActiveSupport::Dependencies.autoloaded?(klass) + if to_unload[klass] @@direct_descendants.delete(klass) else - descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + descendants.reject! { |v| to_unload[v] } end end else diff --git a/railties/Rakefile b/railties/Rakefile index 4ae546b202..0f305ea332 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -11,6 +11,33 @@ task test: "test:isolated" namespace :test do task :isolated do + estimated_duration = { + "test/application/test_runner_test.rb" => 201, + "test/application/assets_test.rb" => 131, + "test/application/rake/migrations_test.rb" => 65, + "test/generators/scaffold_generator_test.rb" => 57, + "test/generators/plugin_test_runner_test.rb" => 57, + "test/application/test_test.rb" => 52, + "test/application/configuration_test.rb" => 49, + "test/generators/app_generator_test.rb" => 43, + "test/application/rake/dbs_test.rb" => 43, + "test/application/rake_test.rb" => 33, + "test/generators/plugin_generator_test.rb" => 30, + "test/railties/engine_test.rb" => 27, + "test/generators/scaffold_controller_generator_test.rb" => 23, + "test/railties/generators_test.rb" => 19, + "test/application/console_test.rb" => 16, + "test/engine/commands_test.rb" => 15, + "test/application/routing_test.rb" => 15, + "test/application/mailer_previews_test.rb" => 15, + "test/application/rake/multi_dbs_test.rb" => 13, + "test/application/asset_debugging_test.rb" => 12, + "test/application/bin_setup_test.rb" => 11, + "test/engine/test_test.rb" => 10, + "test/application/runner_test.rb" => 10, + } + estimated_duration.default = 1 + dash_i = [ "test", "lib", @@ -39,13 +66,23 @@ namespace :test do test_patterns = dirs.map { |dir| "test/#{dir}/*_test.rb" } test_files = Dir[*test_patterns].select do |file| !file.start_with?("test/fixtures/") && !file.start_with?("test/isolation/assets/") - end.sort + end if ENV["BUILDKITE_PARALLEL_JOB_COUNT"] n = ENV["BUILDKITE_PARALLEL_JOB"].to_i m = ENV["BUILDKITE_PARALLEL_JOB_COUNT"].to_i - test_files = test_files.each_slice(m).map { |slice| slice[n] }.compact + buckets = Array.new(m) { [] } + allocations = Array.new(m) { 0 } + test_files.sort_by { |file| [-estimated_duration[file], file] }.each do |file| + idx = allocations.index(allocations.min) + buckets[idx] << file + allocations[idx] += estimated_duration[file] + end + + puts "Running #{buckets[n].size} of #{test_files.size} test files, estimated duration #{allocations[n]}s" + + test_files = buckets[n] end test_files.each do |file| diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb index a9da060347..b248459e47 100644 --- a/railties/test/application/zeitwerk_integration_test.rb +++ b/railties/test/application/zeitwerk_integration_test.rb @@ -98,24 +98,35 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase assert_nil deps.safe_constantize("Admin") end - test "autoloaded_constants returns autoloaded constant paths" do - app_file "app/models/admin/user.rb", "class Admin::User; end" + test "to_unload? says if a constant would be unloaded (main)" do + app_file "app/models/user.rb", "class User; end" app_file "app/models/post.rb", "class Post; end" boot - assert Admin::User - assert_equal ["Admin", "Admin::User"], deps.autoloaded_constants + assert Post + assert deps.to_unload?("Post") + assert_not deps.to_unload?("User") + end + + test "to_unload? says if a constant would be unloaded (once)" do + add_to_config 'config.autoload_once_paths << "#{Rails.root}/extras"' + app_file "extras/foo.rb", "class Foo; end" + app_file "extras/bar.rb", "class Bar; end" + boot + + assert Foo + assert_not deps.to_unload?("Foo") + assert_not deps.to_unload?("Bar") end - test "autoloaded? says if a constant has been autoloaded" do + test "to_unload? says if a constant would be unloaded (reloading disabled)" do app_file "app/models/user.rb", "class User; end" app_file "app/models/post.rb", "class Post; end" - boot + boot("production") assert Post - assert deps.autoloaded?("Post") - assert deps.autoloaded?(Post) - assert_not deps.autoloaded?("User") + assert_not deps.to_unload?("Post") + assert_not deps.to_unload?("User") end test "eager loading loads the application code" do @@ -315,4 +326,46 @@ class ZeitwerkIntegrationTest < ActiveSupport::TestCase assert_nil autoloader.logger end end + + # This is here because to guarantee classic mode works as always, Zeitwerk + # integration does not touch anything in classic. The descendants tracker is a + # very small one-liner exception. We leave its main test suite untouched, and + # add some minimal safety net here. + # + # When time passes, things are going to be reorganized (famous last words). + test "descendants tracker" do + class ::ZeitwerkDTIntegrationTestRoot + extend ActiveSupport::DescendantsTracker + end + class ::ZeitwerkDTIntegrationTestChild < ::ZeitwerkDTIntegrationTestRoot; end + class ::ZeitwerkDTIntegrationTestGrandchild < ::ZeitwerkDTIntegrationTestChild; end + + begin + app_file "app/models/user.rb", "class User < ZeitwerkDTIntegrationTestRoot; end" + app_file "app/models/post.rb", "class Post < ZeitwerkDTIntegrationTestRoot; end" + app_file "app/models/tutorial.rb", "class Tutorial < Post; end" + boot + + assert User + assert Tutorial + + direct_descendants = [ZeitwerkDTIntegrationTestChild, User, Post].to_set + assert_equal direct_descendants, ZeitwerkDTIntegrationTestRoot.direct_descendants.to_set + + descendants = direct_descendants.merge([ZeitwerkDTIntegrationTestGrandchild, Tutorial]) + assert_equal descendants, ZeitwerkDTIntegrationTestRoot.descendants.to_set + + ActiveSupport::DescendantsTracker.clear + + direct_descendants = [ZeitwerkDTIntegrationTestChild].to_set + assert_equal direct_descendants, ZeitwerkDTIntegrationTestRoot.direct_descendants.to_set + + descendants = direct_descendants.merge([ZeitwerkDTIntegrationTestGrandchild]) + assert_equal descendants, ZeitwerkDTIntegrationTestRoot.descendants.to_set + ensure + Object.send(:remove_const, :ZeitwerkDTIntegrationTestRoot) + Object.send(:remove_const, :ZeitwerkDTIntegrationTestChild) + Object.send(:remove_const, :ZeitwerkDTIntegrationTestGrandchild) + end + end end |