DatabaseCleaner / database_cleaner-active_record

Strategies for cleaning databases using ActiveRecord. Can be used to ensure a clean state for testing.
MIT License
64 stars 63 forks source link

Issue with connection being held/replace incorrectly #17

Closed epinault closed 1 year ago

epinault commented 9 years ago

I am migrating from Rails 4.0 to 4.2. Everything worked fine with Rails 4.0. The issue I am having is with rails 4.2 (something might have changed)

Anyway, I suspect database cleaner is the problem in my tests. When I run test independently, all works well. But when I run my full suite of test, I am getting

ActiveRecord::NoDatabaseError:         ActiveRecord::NoDatabaseError: No such file or directory @ dir_s_mkdir - /mnt/hgfs/zumobi/feeder/tmp/microsites/f213d554-bc6d-43fa-885e-f89f27cbf23a

The issue from what I can tell is that I have a normal set of active record models that talks to a postgres db by default. But I also have a small separate set of sqlite to generate sqlite3 db using data stored in postgres and those model use as well active record. Those special model to use activerecord setup their own establish_connection with

Mymodel.establish_connection(connection_details)

But seems like DatabaseCleaner is not liking this. Iit seems to me that after it run the sqlite3 db test against those special models, the connection is remaining against that sqlite db (which is gone as the end of the tests concerning sqlite3 models). Then when other model get tested that uses Postgres, It still trying to access the sqlite 3 db

Any idea what I can do to fix this? I can try to make a repro model but would need more time

epinault commented 9 years ago

I forgot to mention but the issue is that I need dynamic connection to sqlite DB . So I use the establish connection with a connection hash.. So I am not sure how to use Multiple ORM in this setup where I don t have control over the connection details and have database cleaner working proper. Is there a way to tell it to ignore for specific class and not run anything for database Cleaner?

epinault commented 9 years ago

So it is definitely an issue with DatabaseCleaner because code works fine when not in test mode. Also I added the following to force reset connection after each test. Seems to fix the issue but I am wondering is there is a clean way to do this when connection are not known or establish dynamically (not in database.yml)

ActiveRecord::Base.establish_connection(:test)
epinault commented 9 years ago

guess it is mostly working but not reliable still :( Looking at the code and see if I can find a way to improve it

epinault commented 9 years ago

So seems that to support a common before I had to do the following. Then all the test cases are now calling before and after common code (though in this example I duplicated for testing purpose)

class ActiveSupport::TestCase
  include SharedHelpers
  include ResqueUnit::Assertions
  include ResqueUnit::SchedulerAssertions

  before do
    p :common_before_at
    ActiveRecord::Base.establish_connection(:test)
    DatabaseCleaner.start
  end

  after do
    p :common_after_at
    DatabaseCleaner.clean
  end
end

# database cleaner
DatabaseCleaner.clean_with :truncation
DatabaseCleaner[:active_record].strategy = :transaction

class MiniTest::Spec
  include FactoryGirl::Syntax::Methods
  include ResqueUnit::Assertions
  include ResqueUnit::SchedulerAssertions

  before do
    p :common_before_ms
    ActiveRecord::Base.establish_connection(:test)
    DatabaseCleaner.start
  end

  after do
    p :common_after_ms
    DatabaseCleaner.clean
  end

  include SharedHelpers
end
epinault commented 9 years ago

Ok so I am seeing where the error is thrown from but I am not sure why the connection is invalid I see only one connection configured. But somehow when switching from one to another type of connection, it seems to get confused/overriden. Still debugging but something nasty is happening at the connection level

home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/sqlite3_adapter.rb:37:in rescue in sqlite3_connection' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/sqlite3_adapter.rb:13:insqlite3_connection' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:438:in new_connection' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:448:incheckout_new_connection' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:422:in acquire_connection' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:349:inblock in checkout' /home/preseed/.rubies/ruby-2.1.5/lib/ruby/2.1.0/monitor.rb:211:in mon_synchronize' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:348:incheckout' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:263:in block in connection' /home/preseed/.rubies/ruby-2.1.5/lib/ruby/2.1.0/monitor.rb:211:inmon_synchronize' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:262:in connection' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/fixtures.rb:974:inmap' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/fixtures.rb:974:in enlist_fixture_connections' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/fixtures.rb:944:insetup_fixtures' /home/preseed/.gem/ruby/2.1.5/gems/activerecord-4.2.4/lib/active_record/fixtures.rb:826:in before_setup' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/spec.rb:312:inbefore_setup' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:106:in block (3 levels) in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:205:incapture_exceptions' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:105:in block (2 levels) in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:256:intime_it' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:104:in block in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:334:inon_signal' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:276:in with_info_handler' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest/test.rb:103:inrun' /home/preseed/.gem/ruby/2.1.5/gems/minitest-reporters-1.1.0/lib/minitest/reporters.rb:47:in run_with_hooks' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:781:inrun_one_method' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:308:in run_one_method' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:296:inblock (2 levels) in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:295:in each' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:295:inblock in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:334:in on_signal' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:321:inwith_info_handler' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:294:in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:155:inblock in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:155:in map' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:155:inrun' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:129:in run' /home/preseed/.gem/ruby/2.1.5/gems/minitest-5.8.0/lib/minitest.rb:56:inblock in autorun'

martinstreicher commented 9 years ago

I do not know if this is related, but it seems so.

Today, after using the same configuration for more than a year, PostgreSQL queries started to hang when in the debugger byebug. At the prompt, any ActiveRecord queries would just hang. Looking at the process list yielded this:

strike  1283   0.0  0.0  2650732   6080   ??  Ss    5:35PM   0:00.01 postgres: strike prompt_test ::1(49617) PARSE waiting    
strike  1279   0.0  0.0  2652652  14624   ??  Ss    5:35PM   0:00.17 postgres: strike prompt_test ::1(49601) idle in transaction    

According to some posts, this is an indication that the query has finished, but that the connection is open and idle. When this occurs, the app simply hangs waiting for the results to come back. I can kill those processes, but of course, the results are nuked in the process and ActiveRecord gets an exception.

This is my original config (which has worked for a super long time and broke today):

DatabaseCleaner.strategy = :transaction
DatabaseCleaner.logger = Rails.logger

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.start
    DatabaseCleaner.clean_with(:truncation)
    FactoryGirl.lint
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  # config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!
end

If I change the line...

DatabaseCleaner.strategy = :transaction

to

DatabaseCleaner.strategy = :truncation

... everything starts working again in the debugger. Oddly, this does not seem to affect the regular results of rspec, just the results of queries inside byebug.

martinstreicher commented 9 years ago

This is definitely occurring on two machines now. Any ideas?

martinstreicher commented 9 years ago

I am using this configuration now:

DatabaseCleaner.strategy = :truncation, { pre_count: true, cache_tables: true }
DatabaseCleaner.logger   = Rails.logger

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.start
    DatabaseCleaner.clean_with(:truncation)
    FactoryGirl.lint
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end

  config.infer_spec_type_from_file_location!
  config.use_transactional_fixtures = false
end

If I try to use the transaction strategy, I cannot do any database queries from within byebug. It just hangs up.

serg-kovalev commented 8 years ago

@epinault, http://edgeapi.rubyonrails.org/classes/ActiveSupport/Testing/SetupAndTeardown.html

Probably you can try to use setup and teardown instead of before and after

etagwerker commented 8 years ago

@martinstreicher You don't need to explicitly call DatabaseCleaner.start when you are using DatabaseCleaner.cleaning - Could you try running your spec without DatabaseCleaner.start?

epinault commented 8 years ago

@serg-kovalev will give it a try