Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveSupport::TestCase teardown error: undefined method `keys' for nil #500

Closed
searls opened this issue Mar 24, 2024 · 3 comments · Fixed by #503
Closed

ActiveSupport::TestCase teardown error: undefined method `keys' for nil #500

searls opened this issue Mar 24, 2024 · 3 comments · Fixed by #503

Comments

@searls
Copy link

searls commented Mar 24, 2024

Getting the following error. I've seen it four or five times but every time I've cleared it I haven't been able to create a minimal reproduction:

NoMethodError: undefined method `keys' for nil
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/dotenv-3.1.0/lib/dotenv/diff.rb:25:in `added'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/dotenv-3.1.0/lib/dotenv/diff.rb:47:in `any?'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/dotenv-3.1.0/lib/dotenv.rb:79:in `restore'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/dotenv-3.1.0/lib/dotenv/autorestore.rb:21:in `block (3 levels) in <main>'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:407:in `instance_exec'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:407:in `block in make_lambda'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:208:in `call'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:563:in `block in invoke_after'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:563:in `each'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:563:in `invoke_after'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/callbacks.rb:111:in `run_callbacks'
    /Users/justin/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/bundler/gems/rails-9e01d93547e2/activesupport/lib/active_support/testing/setup_and_teardown.rb:46:in `after_teardown'

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

The issue appears to be with the ActiveSupport::TestCase support, in the teardown:

# lib/dotenv/autorestore.rb:21
# Restore ENV after each test
      teardown do
        Dotenv.restore

Which calls:

# lib/dotenv.rb
def restore(env = @diff&.a, safe: Thread.current == Thread.main)
    diff = Dotenv::Diff.new(b: env)
    return unless diff.any?

But at this point env is nil. I can see that @diff is set by:

# lib/dotenv.rb:66
def save
    instrument(:save) do |payload|
      @diff = payload[:diff] = Dotenv::Diff.new
    end
  end

But the above instrument(:save) block is never called, so @diffis never set. Whenany?is then called on theDotenv::Diffobject that has a nilb` value:

# lib/dotenv/diff.rb:47
# Returns true if any keys were added, removed, or changed
    def any?
      [added, removed, changed].any?(&:any?)
    end

All three of those added, removed, and changed methods will raise, because b is nil and they all call either b.slice or b.keys.

I'm not familiar with how dotenv works, so I don't know if the issue is that save isn't getting called, or if there's a missing guard clause to make this all nil-safe

System configuration

dotenv version: 3.1.0

Rails version:

I'm on the current tip of rails/rails#main: rails/rails@68b20b6

Just verified it also occurs on 7.1.3.2

Ruby version:

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]

@searls
Copy link
Author

searls commented Mar 24, 2024

Ah, I think I figured out how to repro: this happens when the test raises (in my case, I had a typo in a model, so loading the environment in the test_helper raised). That error gets swallowed, though, (for the sake of minitest's reporter, I imagine) and dotenv ends up at the top of the backtrace.

Dropping a random raise in any autoloaded class will do the trick:

class User < ApplicationRecord
  raise
end

@KapilSachdev
Copy link

This was happening because of hotwired/turbo-rails#587 in my case.

@bkeepers
Copy link
Owner

bkeepers commented Apr 30, 2024

@searls I'm still having issue reproducing. Where are you referencing the User model to reproduce the error?

# app/models/failwhale.rb
class Failwhale
  raise
end


# test/test_helper.rb
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"

# I tried HERE first
# Failwhale

module ActiveSupport
  class TestCase
    # Run tests in parallel with specified workers
    parallelize(workers: :number_of_processors)

    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
    fixtures :all

    # and HERE
    # Failwhale
  end
end


class MyTest < ActiveSupport::TestCase
  setup do
    # and HERE
    # Failwhale
  end

  # And HERE
  test "a test case" do
    Failwhale
  end
end

In all cases, the error got was:

❯ rails test
app/models/failwhale.rb:2:in `<class:Failwhale>': unhandled exception
	from app/models/failwhale.rb:1:in `<main>'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants