ruby-on-railsrubyruby-on-rails-8

Rails 8.0.1 upgrade, railties-8.0.1 gives warning: already initialized constant STATS_DIRECTORIES


Just upgrading a Rails 7.2.2 app to Rails 8.0.1. Before the upgrade, all tests run cleanly. App was upgraded to ruby 3.4.2 just before (and all tests passed with no warnings). After upgrade, this warning is emitted:

/home/<username>/.asdf/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/railties-8.0.1/lib/rails/tasks/statistics.rake:4: warning: already initialized constant STATS_DIRECTORIES Time: 00:00:10,  ETA: 00:03:50
/home/<username>/.asdf/installs/ruby/3.4.2/lib/ruby/gems/3.4.0/gems/railties-8.0.1/lib/rails/tasks/statistics.rake:4: warning: previous definition of STATS_DIRECTORIES was here

I have traced the issue to one test suite for a Rake Task that is run regularly in production by Heroku Scheduler.

The test looks like:

require "test_helper"

class TimedEmailsTaskTest < ActionDispatch::IntegrationTest
  setup do
    Rails.application.load_tasks

    @my_object = create(:object)
  end

  teardown do
    ActionMailer::Base.deliveries.clear
    Rake::Task["my_regular_emails_task"].reenable
  end

  ... 4 tests that assert things using Rake::Task["my_regular_emails_task"].invoke ...

end

The offending line is Rails.application.load_tasks, which I tracked down with the help of this github issue. Originally the warning was emitted once for each test in this suite; I can get it down to once by using:

    Rails.application.load_tasks unless defined?(Rake::Task) && Rake::Task.tasks.any?

With the help of some good old puts debugging, I have tracked the initial "hit" on statistics.rake:4 to my Rakefile (in the app root directory), which is the standard default file:

require_relative "config/application"

Rails.application.load_tasks

So, the Rakefile runs, sets the constant STATS_DIRECTORIES, but the tasks aren't available in tests without running Rails.application.load_tasks again, which emits the warning. Interestingly, If I just run the single test file, the Rakefile doesn't run and warning isn't emitted; it only happens when running multiple tests (e.g. rails test or rails test:integration).

I have spent a few hours with this, including trying to move statements to test_helper.rb with no meaningful change in the outcome. I'm also unhappy with the possibility of creating flaky tests that either emit a warning when running the suite and pass silently when running the one file, or fail when running the one file and pass (with warning) when running the full suite. I'm somewhat confused about how Rakefile works and when it runs, and confused why this wasn't an issue on Rails 7. Is there an insight I'm missing? Is there an obvious misconfiguration here or more idiomatic approach that would solve this?

PS: This same railties line was emitting warnings for people back on Rails 4, but that question was never resolved so isn't much help.

EDIT:

The revised task looks like:

# ./lib/tasks/emails.rake

desc "send all notification emails"
task send_timed_email_notices: :environment do
  TimedEmailNotices.send_all
end

Solution

  • I had the same problem when testing rake tasks. The following example works for me:

    require 'test_helper'
    require 'rake'
    
    class PersonTaskTest < ActiveSupport::TestCase
      def setup
        Rake.application.rake_require('tasks/person')
        Rake::Task.define_task(:environment)
      end
      
      def teardown
        Rake::Task.clear
      end
    
      test "person:seed_10 create 10 people" do
        initial_count = Person.count
    
        Rake::Task["person:seed_10"].reenable
        Rake::Task["person:seed_10"].invoke
    
        assert_equal initial_count + 10, Person.count
      end
    end