get rspec tests running in ar adapter gem.

This commit is contained in:
Micah Geisel 2018-05-23 10:38:23 -07:00
parent ed0e38f441
commit c39366b11a
35 changed files with 644 additions and 125 deletions

5
.gitignore vendored
View File

@ -7,8 +7,9 @@ bundled_gems/
vendor/
examples/db/*.db
examples/config/database.yml
db/config.yml
db/test.sqlite3
spec/support/config.yml
tmp/*
!tmp/.keep
.rbenv-version
.rvmrc
.ruby-version

View File

@ -11,8 +11,7 @@ upstream:
## 2. Make sure the tests run fine
- `bundle install`
- Copy `db/sample.config.yml` to `db/config.yml` and edit it
- Make sure to create the databases specified in `db/config.yml`
- Copy `spec/support/sample.config.yml` to `spec/support/config.yml` and edit it
- Run the tests with `bundle exec rspec`
Note that if you don't have all the supported databases installed and running,

View File

@ -1,12 +1,14 @@
PATH
remote: .
specs:
database_cleaner (1.8.0)
PATH
remote: adapters
specs:
database_cleaner-active_record (0.1.0)
PATH
remote: .
specs:
database_cleaner (1.7.0)
activerecord
database_cleaner (~> 1.8.0)
GEM
remote: https://rubygems.org/

View File

@ -5,7 +5,9 @@
/doc/
/pkg/
/spec/reports/
/spec/support/config.yml
/tmp/
!/tmp/.keep
# rspec failure tracking
.rspec_status

View File

@ -1,3 +1,4 @@
require 'database_cleaner/active_record/version'
require 'database_cleaner'
require 'database_cleaner/active_record/deletion'
require 'database_cleaner/active_record/transaction'

View File

@ -0,0 +1,166 @@
require 'active_record'
require 'database_cleaner/active_record/base'
require 'database_cleaner/spec'
class FakeModel
def self.connection
:fake_connection
end
end
RSpec.describe DatabaseCleaner::ActiveRecord do
it { is_expected.to respond_to(:available_strategies) }
describe "config_file_location" do
after do
# prevent global state leakage
DatabaseCleaner::ActiveRecord.config_file_location=nil
DatabaseCleaner.app_root = nil
end
it "should default to \#{DatabaseCleaner.app_root}/config/database.yml" do
DatabaseCleaner::ActiveRecord.config_file_location = nil
DatabaseCleaner.app_root = "/path/to"
expect(DatabaseCleaner::ActiveRecord.config_file_location).to eq '/path/to/config/database.yml'
end
end
end
module DatabaseCleaner
module ActiveRecord
class ExampleStrategy
include DatabaseCleaner::ActiveRecord::Base
end
RSpec.describe ExampleStrategy do
let(:config_location) { '/path/to/config/database.yml' }
around do |example|
DatabaseCleaner::ActiveRecord.config_file_location = config_location
example.run
DatabaseCleaner::ActiveRecord.config_file_location = nil
end
it_should_behave_like "a generic strategy"
describe "db" do
it "should store my desired db" do
subject.db = :my_db
expect(subject.db).to eq :my_db
end
it "should default to :default" do
expect(subject.db).to eq :default
end
end
describe "db=" do
let(:config_location) { "spec/support/example.database.yml" }
it "should process erb in the config" do
subject.db = :my_db
expect(subject.connection_hash).to eq({ "database" => "one" })
end
context 'when config file differs from established ActiveRecord configuration' do
before do
allow(::ActiveRecord::Base).to receive(:configurations).and_return({ "my_db" => { "database" => "two"} })
end
it 'uses the ActiveRecord configuration' do
subject.db = :my_db
expect(subject.connection_hash).to eq({ "database" => "two"})
end
end
context 'when config file agrees with ActiveRecord configuration' do
before do
allow(::ActiveRecord::Base).to receive(:configurations).and_return({ "my_db" => { "database" => "one"} })
end
it 'uses the config file' do
subject.db = :my_db
expect(subject.connection_hash).to eq({ "database" => "one"})
end
end
context 'when ::ActiveRecord::Base.configurations nil' do
before do
allow(::ActiveRecord::Base).to receive(:configurations).and_return(nil)
end
it 'uses the config file' do
subject.db = :my_db
expect(subject.connection_hash).to eq({ "database" => "one"})
end
end
context 'when ::ActiveRecord::Base.configurations empty' do
before do
allow(::ActiveRecord::Base).to receive(:configurations).and_return({})
end
it 'uses the config file' do
subject.db = :my_db
expect(subject.connection_hash).to eq({ "database" => "one"})
end
end
context 'when config file is not available' do
before do
allow(File).to receive(:file?).with(config_location).and_return(false)
end
it "should skip config" do
subject.db = :my_db
expect(subject.connection_hash).not_to be
end
end
it "skips the file when the model is set" do
subject.db = FakeModel
expect(subject.connection_hash).not_to be
end
it "skips the file when the db is set to :default" do
# to avoid https://github.com/bmabey/database_cleaner/issues/72
subject.db = :default
expect(subject.connection_hash).not_to be
end
end
describe "connection_class" do
it "should default to ActiveRecord::Base" do
expect(subject.connection_class).to eq ::ActiveRecord::Base
end
context "with database models" do
context "connection_hash is set" do
it "reuses the model's connection" do
subject.connection_hash = {}
subject.db = FakeModel
expect(subject.connection_class).to eq FakeModel
end
end
context "connection_hash is not set" do
it "reuses the model's connection" do
subject.db = FakeModel
expect(subject.connection_class).to eq FakeModel
end
end
end
context "when connection_hash is set" do
let(:hash) { {} }
before { subject.connection_hash = hash }
it "establishes a connection with it" do
expect(::ActiveRecord::Base).to receive(:establish_connection).with(hash)
expect(subject.connection_class).to eq ::ActiveRecord::Base
end
end
end
end
end
end

View File

@ -0,0 +1,155 @@
require 'database_cleaner/active_record/transaction'
require 'active_record'
module DatabaseCleaner
module ActiveRecord
RSpec.describe Transaction do
let(:connection) { double("connection") }
let(:connection_2) { double("connection_2") }
let(:connection_pool) { double("connection_pool") }
before do
allow(::ActiveRecord::Base).to receive(:connection_pool).and_return(connection_pool)
allow(connection_pool).to receive(:connections).and_return([connection])
allow(::ActiveRecord::Base).to receive(:connection).and_return(connection)
end
describe "#start" do
[:begin_transaction, :begin_db_transaction].each do |begin_transaction_method|
context "using #{begin_transaction_method}" do
before do
allow(connection).to receive(:transaction)
allow(connection).to receive(begin_transaction_method)
end
it "should increment open transactions if possible" do
expect(connection).to receive(:increment_open_transactions)
subject.start
end
it "should tell ActiveRecord to increment connection if its not possible to increment current connection" do
expect(::ActiveRecord::Base).to receive(:increment_open_transactions)
subject.start
end
it "should start a transaction" do
allow(connection).to receive(:increment_open_transactions)
expect(connection).to receive(begin_transaction_method)
expect(connection).to receive(:transaction)
subject.start
end
end
end
end
describe "#clean" do
context "manual accounting of transaction count" do
it "should start a transaction" do
expect(connection).to receive(:open_transactions).and_return(1)
allow(connection).to receive(:decrement_open_transactions)
expect(connection).to receive(:rollback_db_transaction)
subject.clean
end
it "should decrement open transactions if possible" do
expect(connection).to receive(:open_transactions).and_return(1)
allow(connection).to receive(:rollback_db_transaction)
expect(connection).to receive(:decrement_open_transactions)
subject.clean
end
it "should not try to decrement or rollback if open_transactions is 0 for whatever reason" do
expect(connection).to receive(:open_transactions).and_return(0)
subject.clean
end
it "should decrement connection via ActiveRecord::Base if connection won't" do
expect(connection).to receive(:open_transactions).and_return(1)
allow(connection).to receive(:rollback_db_transaction)
expect(::ActiveRecord::Base).to receive(:decrement_open_transactions)
subject.clean
end
it "should rollback open transactions in all connections" do
allow(connection_pool).to receive(:connections).and_return([connection, connection_2])
expect(connection).to receive(:open_transactions).and_return(1)
allow(connection).to receive(:rollback_db_transaction)
expect(connection_2).to receive(:open_transactions).and_return(1)
allow(connection_2).to receive(:rollback_db_transaction)
expect(::ActiveRecord::Base).to receive(:decrement_open_transactions).twice
subject.clean
end
it "should rollback open transactions in all connections with an open transaction" do
allow(connection_pool).to receive(:connections).and_return([connection, connection_2])
expect(connection).to receive(:open_transactions).and_return(1)
allow(connection).to receive(:rollback_db_transaction)
expect(connection_2).to receive(:open_transactions).and_return(0)
expect(::ActiveRecord::Base).to receive(:decrement_open_transactions).exactly(1).times
subject.clean
end
end
context "automatic accounting of transaction count (AR 4)" do
before { stub_const("ActiveRecord::VERSION::MAJOR", 4) }
it "should start a transaction" do
allow(connection).to receive(:rollback_db_transaction)
expect(connection).to receive(:open_transactions).and_return(1)
expect(connection).not_to receive(:decrement_open_transactions)
expect(connection).to receive(:rollback_transaction)
subject.clean
end
it "should decrement open transactions if possible" do
allow(connection).to receive(:rollback_transaction)
expect(connection).to receive(:open_transactions).and_return(1)
expect(connection).not_to receive(:decrement_open_transactions)
subject.clean
end
it "should not try to decrement or rollback if open_transactions is 0 for whatever reason" do
expect(connection).to receive(:open_transactions).and_return(0)
subject.clean
end
it "should decrement connection via ActiveRecord::Base if connection won't" do
expect(connection).to receive(:open_transactions).and_return(1)
allow(connection).to receive(:rollback_transaction)
expect(::ActiveRecord::Base).not_to receive(:decrement_open_transactions)
subject.clean
end
end
end
describe "#connection_maintains_transaction_count?" do
it "should return true if the major active record version is < 4" do
stub_const("ActiveRecord::VERSION::MAJOR", 3)
expect(subject.connection_maintains_transaction_count?).to be_truthy
end
it "should return false if the major active record version is > 3" do
stub_const("ActiveRecord::VERSION::MAJOR", 4)
expect(subject.connection_maintains_transaction_count?).to be_falsey
end
end
end
end
end

View File

@ -0,0 +1,120 @@
require 'support/active_record_helper'
require 'database_cleaner/active_record/truncation'
RSpec.describe DatabaseCleaner::ActiveRecord::Truncation do
ActiveRecordHelper.with_all_dbs do |helper|
context "using a #{helper.db} connection" do
around do |example|
helper.setup
example.run
helper.teardown
end
let(:connection) { helper.connection }
before do
allow(connection).to receive(:disable_referential_integrity).and_yield
allow(connection).to receive(:database_cleaner_view_cache).and_return([])
end
describe '#clean' do
context "with records" do
before do
2.times { User.create! }
2.times { Agent.create! }
end
it "should truncate all tables" do
expect { subject.clean }
.to change { [User.count, Agent.count] }
.from([2,2])
.to([0,0])
end
it "should reset AUTO_INCREMENT index of table" do
subject.clean
expect(User.create.id).to eq 1
end
xit "should not reset AUTO_INCREMENT index of table if :reset_ids is false" do
described_class.new(reset_ids: false).clean
expect(User.create.id).to eq 3
end
it "should truncate all tables except for schema_migrations" do
subject.clean
count = connection.select_value("select count(*) from schema_migrations;").to_i
expect(count).to eq 2
end
it "should only truncate the tables specified in the :only option when provided" do
expect { described_class.new(only: ['agents']).clean }
.to change { [User.count, Agent.count] }
.from([2,2])
.to([2,0])
end
it "should not truncate the tables specified in the :except option" do
expect { described_class.new(except: ['users']).clean }
.to change { [User.count, Agent.count] }
.from([2,2])
.to([2,0])
end
it "should raise an error when :only and :except options are used" do
expect {
described_class.new(except: ['widgets'], only: ['widgets'])
}.to raise_error(ArgumentError)
end
it "should raise an error when invalid options are provided" do
expect { described_class.new(foo: 'bar') }.to raise_error(ArgumentError)
end
it "should not truncate views" do
allow(connection).to receive(:database_cleaner_table_cache).and_return(%w[widgets dogs])
allow(connection).to receive(:database_cleaner_view_cache).and_return(["widgets"])
expect(connection).to receive(:truncate_tables).with(['dogs'])
subject.clean
end
end
describe "with pre_count optimization option" do
subject { described_class.new(pre_count: true) }
it "only truncates non-empty tables" do
pending if helper.db == :sqlite3
pending if helper.db == :postgres
User.create!
expect(connection).to receive(:truncate_tables).with(['users'])
subject.clean
end
end
context 'when :cache_tables is set to true' do
it 'caches the list of tables to be truncated' do
expect(connection).to receive(:database_cleaner_table_cache).and_return([])
expect(connection).not_to receive(:tables)
allow(connection).to receive(:truncate_tables)
described_class.new(cache_tables: true).clean
end
end
context 'when :cache_tables is set to false' do
it 'does not cache the list of tables to be truncated' do
expect(connection).not_to receive(:database_cleaner_table_cache)
expect(connection).to receive(:tables).and_return([])
allow(connection).to receive(:truncate_tables)
described_class.new(cache_tables: false).clean
end
end
end
end
end
end

View File

@ -1,9 +0,0 @@
RSpec.describe DatabaseCleaner::ActiveRecord do
it "has a version number" do
expect(DatabaseCleaner::ActiveRecord::VERSION).not_to be nil
end
it "does something useful" do
expect(false).to eq(true)
end
end

View File

@ -1,14 +1,14 @@
require "bundler/setup"
require "database_cleaner/active_record"
require 'database_cleaner'
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
config.example_status_persistence_file_path = ".rspec_status"
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!
config.expect_with :rspec do |c|
c.syntax = :expect
end
end

View File

@ -0,0 +1,40 @@
require 'active_record'
require 'database_cleaner/spec/database_helper'
class ActiveRecordHelper < DatabaseCleaner::Spec::DatabaseHelper
def setup
patch_mysql_adapters
Kernel.const_set "User", Class.new(ActiveRecord::Base)
Kernel.const_set "Agent", Class.new(ActiveRecord::Base)
super
connection.execute "CREATE TABLE IF NOT EXISTS schema_migrations (version VARCHAR(255));"
connection.execute "INSERT INTO schema_migrations VALUES (1), (2);"
end
def teardown
connection.execute "DROP TABLE schema_migrations;"
super
Kernel.send :remove_const, "User" if defined?(User)
Kernel.send :remove_const, "Agent" if defined?(Agent)
end
private
def establish_connection(config = default_config)
ActiveRecord::Base.establish_connection(config)
@connection = ActiveRecord::Base.connection
end
def patch_mysql_adapters
# remove DEFAULT NULL from column definition, which is an error on primary keys in MySQL 5.7.3+
primary_key_sql = "int(11) auto_increment PRIMARY KEY"
ActiveRecord::ConnectionAdapters::MysqlAdapter::NATIVE_DATABASE_TYPES[:primary_key] = primary_key_sql
ActiveRecord::ConnectionAdapters::Mysql2Adapter::NATIVE_DATABASE_TYPES[:primary_key] = primary_key_sql
end
end

View File

@ -0,0 +1,3 @@
my_db:
database: <%= "ONE".downcase %>

View File

@ -27,7 +27,7 @@ postgres:
sqlite3:
adapter: sqlite3
database: db/test.sqlite3
database: tmp/database_cleaner_test.sqlite3
pool: 5
timeout: 5000
encoding: utf8

View File

@ -0,0 +1,2 @@
require "database_cleaner/spec/database_helper"
require "database_cleaner/spec/shared_examples"

View File

@ -0,0 +1,82 @@
require 'yaml'
module DatabaseCleaner
module Spec
class DatabaseHelper < Struct.new(:db)
def self.with_all_dbs &block
%w[mysql mysql2 sqlite3 postgres].map(&:to_sym).each do |db|
yield new(db)
end
end
def setup
create_db
establish_connection
load_schema
end
attr_reader :connection
def teardown
drop_db
end
private
def establish_connection(config = default_config)
raise NotImplementedError
end
def create_db
if db == :sqlite3
# NO-OP
elsif db == :postgres
establish_connection default_config.merge('database' => 'postgres')
connection.execute "CREATE DATABASE #{default_config['database']}" rescue nil
else
establish_connection default_config.merge("database" => nil)
connection.execute "CREATE DATABASE IF NOT EXISTS #{default_config['database']}"
end
end
def load_schema
connection.execute <<-SQL
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name INTEGER
);
SQL
connection.execute <<-SQL
CREATE TABLE IF NOT EXISTS agents (
name INTEGER
);
SQL
end
def drop_db
if db == :sqlite3
begin
File.unlink(db_config['sqlite3']['database'])
rescue Errno::ENOENT
end
elsif db == :postgres
# FIXME
connection.execute "DROP TABLE IF EXISTS users"
connection.execute "DROP TABLE IF EXISTS agents"
else
connection.execute "DROP DATABASE IF EXISTS #{default_config['database']}"
end
end
def db_config
config_path = 'spec/support/config.yml'
@db_config ||= YAML.load(IO.read(config_path))
end
def default_config
db_config[db.to_s]
end
end
end
end

View File

@ -1,6 +1,6 @@
require 'active_record'
require 'database_cleaner/active_record/base'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
class FakeModel
def self.connection

View File

@ -1,5 +1,5 @@
require 'database_cleaner/data_mapper/base'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
module DatabaseCleaner
RSpec.describe DataMapper do

View File

@ -1,5 +1,5 @@
require 'database_cleaner/data_mapper/transaction'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
#require 'data_mapper'
module DatabaseCleaner

View File

@ -1,5 +1,5 @@
require 'database_cleaner/data_mapper/truncation'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
module DatabaseCleaner
module DataMapper

View File

@ -1,4 +1,4 @@
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
require 'database_cleaner/generic/base'
require 'active_record'

View File

@ -1,5 +1,5 @@
require 'database_cleaner/mongo_mapper/base'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
module DatabaseCleaner
RSpec.describe MongoMapper do

View File

@ -1,5 +1,5 @@
require 'database_cleaner/neo4j/base'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
module DatabaseCleaner
RSpec.describe Neo4j do

View File

@ -1,6 +1,6 @@
require 'neo4j-core'
require 'database_cleaner/neo4j/transaction'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
module DatabaseCleaner
module Neo4j

View File

@ -1,6 +1,6 @@
require 'redis'
require 'database_cleaner/redis/base'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
module DatabaseCleaner
RSpec.describe Redis do

View File

@ -1,5 +1,5 @@
require 'database_cleaner/sequel/base'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
require 'sequel'
module DatabaseCleaner

View File

@ -1,5 +1,5 @@
require 'database_cleaner/sequel/deletion'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
require 'support/sequel_helper'
module DatabaseCleaner

View File

@ -1,5 +1,5 @@
require 'database_cleaner/sequel/transaction'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
require 'support/sequel_helper'
module DatabaseCleaner

View File

@ -1,5 +1,5 @@
require 'database_cleaner/sequel/truncation'
require 'database_cleaner/shared_strategy'
require 'database_cleaner/spec'
require 'support/sequel_helper'
module DatabaseCleaner

View File

@ -1,7 +1,7 @@
require 'active_record'
require 'support/database_helper'
require 'database_cleaner/spec/database_helper'
class ActiveRecordHelper < DatabaseHelper
class ActiveRecordHelper < DatabaseCleaner::Spec::DatabaseHelper
def setup
patch_mysql_adapters

View File

@ -1,8 +1,8 @@
require 'dm-core'
require 'dm-sqlite-adapter'
require 'support/database_helper'
require 'database_cleaner/spec/database_helper'
class DataMapperHelper < DatabaseHelper
class DataMapperHelper < DatabaseCleaner::Spec::DatabaseHelper
def setup
super

View File

@ -1,79 +0,0 @@
require 'yaml'
class DatabaseHelper < Struct.new(:db)
def self.with_all_dbs &block
%w[mysql mysql2 sqlite3 postgres].map(&:to_sym).each do |db|
yield new(db)
end
end
def setup
create_db
establish_connection
load_schema
end
attr_reader :connection
def teardown
drop_db
end
private
def establish_connection(config = default_config)
raise NotImplementedError
end
def create_db
if db == :sqlite3
# NO-OP
elsif db == :postgres
establish_connection default_config.merge('database' => 'postgres')
connection.execute "CREATE DATABASE #{default_config['database']}" rescue nil
else
establish_connection default_config.merge("database" => nil)
connection.execute "CREATE DATABASE IF NOT EXISTS #{default_config['database']}"
end
end
def load_schema
connection.execute <<-SQL
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name INTEGER
);
SQL
connection.execute <<-SQL
CREATE TABLE IF NOT EXISTS agents (
name INTEGER
);
SQL
end
def drop_db
if db == :sqlite3
begin
File.unlink(db_config['sqlite3']['database'])
rescue Errno::ENOENT
end
elsif db == :postgres
# FIXME
connection.execute "DROP TABLE IF EXISTS users"
connection.execute "DROP TABLE IF EXISTS agents"
else
connection.execute "DROP DATABASE IF EXISTS #{default_config['database']}"
end
end
def db_config
config_path = 'db/config.yml'
@db_config ||= YAML.load(IO.read(config_path))
end
def default_config
db_config[db.to_s]
end
end

View File

@ -0,0 +1,34 @@
mysql:
adapter: mysql
database: database_cleaner_test
username: root
password:
host: 127.0.0.1
port: 3306
encoding: utf8
mysql2:
adapter: mysql2
database: database_cleaner_test
username: root
password:
host: 127.0.0.1
port: 3306
encoding: utf8
postgres:
adapter: postgresql
database: database_cleaner_test
username: postgres
password:
host: 127.0.0.1
encoding: unicode
template: template0
sqlite3:
adapter: sqlite3
database: tmp/database_cleaner_test.sqlite3
pool: 5
timeout: 5000
encoding: utf8

View File

@ -1,7 +1,7 @@
require 'sequel'
require 'support/database_helper'
require 'database_cleaner/spec/database_helper'
class SequelHelper < DatabaseHelper
class SequelHelper < DatabaseCleaner::Spec::DatabaseHelper
private
def establish_connection(config = default_config)

0
tmp/.keep Normal file
View File