mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
r2718@asus: jeremy | 2005-10-23 14:45:30 -0700
Ticket 2404 - fixture delete order r2719@asus: jeremy | 2005-10-23 15:01:13 -0700 Keep closer tabs on dirty, loaded, and declared fixtures. Closes #2404. r2720@asus: jeremy | 2005-10-23 16:09:19 -0700 ensure table names are strings. set dirty = dirty union loaded rather than dirty = loaded. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2714 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
76879f2541
commit
a2f0ae76d1
6 changed files with 171 additions and 107 deletions
|
@ -1,5 +1,7 @@
|
|||
*1.12.1* (October 19th, 2005)
|
||||
|
||||
* Keep closer tabs on dirty, loaded, and declared fixtures. #2404 [ryand-ruby@zenspider.com]
|
||||
|
||||
* Map Active Record time to SQL TIME. #2575, #2576 [Robby Russell <robby@planetargon.com>]
|
||||
|
||||
* Clarify semantics of ActiveRecord::Base#respond_to? #2560 [skaes@web.de]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'erb'
|
||||
require 'yaml'
|
||||
require 'csv'
|
||||
require 'set'
|
||||
|
||||
module YAML #:nodoc:
|
||||
class Omap #:nodoc:
|
||||
|
@ -122,7 +123,7 @@ end
|
|||
# ...
|
||||
#
|
||||
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger
|
||||
# the testing environment to automatically load the appropriate fixtures into the database before each test.
|
||||
# the testing environment to automatically load the appropriate fixtures into the database before each test.
|
||||
# To ensure consistent data, the environment deletes the fixtures before running the load.
|
||||
#
|
||||
# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
|
||||
|
@ -148,7 +149,7 @@ end
|
|||
# self.use_instantiated_fixtures = false
|
||||
#
|
||||
# - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
|
||||
# self.use_instantiated_fixtures = :no_instances
|
||||
# self.use_instantiated_fixtures = :no_instances
|
||||
#
|
||||
# Even if auto-instantiated fixtures are disabled, you can still access them
|
||||
# by name via special dynamic methods. Each method has the same name as the
|
||||
|
@ -180,37 +181,37 @@ end
|
|||
#
|
||||
# = Transactional fixtures
|
||||
#
|
||||
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
||||
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
||||
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
|
||||
#
|
||||
# class FooTest < Test::Unit::TestCase
|
||||
# self.use_transactional_fixtures = true
|
||||
# self.use_instantiated_fixtures = false
|
||||
#
|
||||
#
|
||||
# fixtures :foos
|
||||
#
|
||||
#
|
||||
# def test_godzilla
|
||||
# assert !Foo.find(:all).empty?
|
||||
# Foo.destroy_all
|
||||
# assert Foo.find(:all).empty?
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def test_godzilla_aftermath
|
||||
# assert !Foo.find(:all).empty?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
||||
#
|
||||
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
||||
# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
|
||||
#
|
||||
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
|
||||
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
|
||||
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
|
||||
#
|
||||
# When *not* to use transactional fixtures:
|
||||
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
||||
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
||||
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
|
||||
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
||||
# When *not* to use transactional fixtures:
|
||||
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
||||
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
||||
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
|
||||
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
||||
# Use InnoDB, MaxDB, or NDB instead.
|
||||
class Fixtures < YAML::Omap
|
||||
DEFAULT_FILTER_RE = /\.ya?ml$/
|
||||
|
@ -230,45 +231,53 @@ class Fixtures < YAML::Omap
|
|||
|
||||
ActiveRecord::Base.logger.level = old_logger_level
|
||||
end
|
||||
|
||||
|
||||
def self.instantiate_all_loaded_fixtures(object, load_instances=true)
|
||||
all_loaded_fixtures.each do |table_name, fixtures|
|
||||
Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
|
||||
all_loaded_fixtures.each do |fixtures|
|
||||
Fixtures.instantiate_fixtures(object, fixtures.table_name, fixtures, load_instances)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
cattr_accessor :all_loaded_fixtures
|
||||
self.all_loaded_fixtures = {}
|
||||
self.all_loaded_fixtures = []
|
||||
|
||||
def self.create_fixtures(fixtures_directory, *table_names)
|
||||
table_names = table_names.flatten
|
||||
connection = block_given? ? yield : ActiveRecord::Base.connection
|
||||
old_logger_level = ActiveRecord::Base.logger.level
|
||||
|
||||
begin
|
||||
ActiveRecord::Base.logger.level = Logger::ERROR
|
||||
|
||||
fixtures_map = {}
|
||||
fixtures = table_names.flatten.map do |table_name|
|
||||
fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s))
|
||||
end
|
||||
all_loaded_fixtures.merge! fixtures_map
|
||||
|
||||
|
||||
connection.transaction do
|
||||
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
||||
fixtures.each { |fixture| fixture.insert_fixtures }
|
||||
ActiveRecord::Base.logger.silence do
|
||||
fixtures = table_names.map do |table_name|
|
||||
Fixtures.new(connection, File.split(table_name.to_s).last, File.join(fixtures_directory, table_name.to_s))
|
||||
end
|
||||
|
||||
# Cap primary key sequences to max(pk).
|
||||
if connection.respond_to?(:reset_pk_sequence!)
|
||||
table_names.flatten.each do |table_name|
|
||||
connection.reset_pk_sequence!(table_name)
|
||||
connection.transaction do
|
||||
table_names.reverse.each do |table_name|
|
||||
connection.delete "DELETE FROM #{table_name}"
|
||||
end
|
||||
|
||||
fixtures.each { |f| f.insert_fixtures }
|
||||
|
||||
if connection.respond_to?(:reset_pk_sequence!)
|
||||
table_names.flatten.each do |table_name|
|
||||
connection.reset_pk_sequence!(table_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
all_loaded_fixtures.concat fixtures
|
||||
return fixtures.size > 1 ? fixtures : fixtures.first
|
||||
ensure
|
||||
ActiveRecord::Base.logger.level = old_logger_level
|
||||
end
|
||||
end
|
||||
|
||||
def self.delete_fixtures(table_names)
|
||||
ActiveRecord::Base.silence do
|
||||
connection = block_given? ? yield : ActiveRecord::Base.connection
|
||||
|
||||
connection.transaction do
|
||||
table_names.reverse.each do |table_name|
|
||||
connection.delete "DELETE FROM #{table_name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -423,83 +432,85 @@ module Test #:nodoc:
|
|||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
cattr_accessor :fixture_path
|
||||
cattr_accessor :dirty_fixture_table_names
|
||||
cattr_accessor :loaded_fixture_table_names
|
||||
class_inheritable_accessor :fixture_table_names
|
||||
class_inheritable_accessor :use_transactional_fixtures
|
||||
class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
||||
class_inheritable_accessor :pre_loaded_fixtures
|
||||
|
||||
self.dirty_fixture_table_names = []
|
||||
self.loaded_fixture_table_names = []
|
||||
self.fixture_table_names = []
|
||||
self.use_transactional_fixtures = false
|
||||
self.use_instantiated_fixtures = true
|
||||
self.pre_loaded_fixtures = false
|
||||
|
||||
@@already_loaded_fixtures = {}
|
||||
class << self
|
||||
def fixtures(*table_names)
|
||||
table_names = table_names.flatten.map { |n| n.to_s }
|
||||
self.fixture_table_names |= table_names
|
||||
self.dirty_fixture_table_names |= table_names
|
||||
|
||||
def self.fixtures(*table_names)
|
||||
table_names = table_names.flatten
|
||||
self.fixture_table_names |= table_names
|
||||
require_fixture_classes(table_names)
|
||||
setup_fixture_accessors(table_names)
|
||||
end
|
||||
require_fixture_classes(table_names)
|
||||
setup_fixture_accessors(table_names)
|
||||
end
|
||||
|
||||
def self.require_fixture_classes(table_names=nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
begin
|
||||
require Inflector.singularize(table_name.to_s)
|
||||
rescue LoadError
|
||||
# Let's hope the developer has included it himself
|
||||
def require_fixture_classes(table_names=nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
begin
|
||||
require table_name.to_s.singularize
|
||||
rescue LoadError
|
||||
# Let's hope the developer has included it himself
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.setup_fixture_accessors(table_names=nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
table_name = table_name.to_s.tr('.','_')
|
||||
define_method(table_name) do |fixture, *optionals|
|
||||
force_reload = optionals.shift
|
||||
@fixture_cache[table_name] ||= Hash.new
|
||||
@fixture_cache[table_name][fixture] = nil if force_reload
|
||||
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
||||
def setup_fixture_accessors(table_names = nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
table_name = table_name.to_s.tr('.', '_')
|
||||
class_eval <<-end_eval, __FILE__, __LINE__
|
||||
def #{table_name}(fixture, force_reload = false)
|
||||
fixture = fixture.to_s
|
||||
@fixture_cache ||= Hash.new { |h,k| h[k] = Hash.new }
|
||||
if force_reload or @fixture_cache['#{table_name}'][fixture].nil?
|
||||
@fixture_cache['#{table_name}'][fixture] = @loaded_fixtures['#{table_name}'][fixture].find
|
||||
end
|
||||
@fixture_cache['#{table_name}'][fixture]
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.uses_transaction(*methods)
|
||||
@uses_transaction ||= []
|
||||
@uses_transaction.concat methods.map { |m| m.to_s }
|
||||
end
|
||||
def uses_transaction(*methods)
|
||||
@uses_transaction ||= Set.new
|
||||
@uses_transaction += methods.flatten.map { |m| m.to_s }
|
||||
end
|
||||
|
||||
def self.uses_transaction?(method)
|
||||
@uses_transaction && @uses_transaction.include?(method.to_s)
|
||||
def uses_transaction?(*methods)
|
||||
@uses_transaction && methods.flatten.all? { |m| @uses_transaction.include?(m.to_s) }
|
||||
end
|
||||
end
|
||||
|
||||
def use_transactional_fixtures?
|
||||
use_transactional_fixtures &&
|
||||
!self.class.uses_transaction?(method_name)
|
||||
use_transactional_fixtures && !self.class.uses_transaction?(method_name)
|
||||
end
|
||||
|
||||
def setup_with_fixtures
|
||||
if pre_loaded_fixtures && !use_transactional_fixtures
|
||||
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
||||
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
||||
end
|
||||
|
||||
@fixture_cache = Hash.new
|
||||
|
||||
# Load fixtures once and begin transaction.
|
||||
if use_transactional_fixtures?
|
||||
if @@already_loaded_fixtures[self.class]
|
||||
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
||||
else
|
||||
load_fixtures
|
||||
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
||||
end
|
||||
reload_fixtures! unless @loaded_fixtures and dirty_fixture_table_names.empty?
|
||||
ActiveRecord::Base.lock_mutex
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
@@already_loaded_fixtures[self.class] = nil
|
||||
load_fixtures
|
||||
reload_fixtures!
|
||||
self.dirty_fixture_table_names |= loaded_fixture_table_names
|
||||
end
|
||||
|
||||
# Instantiate fixtures for every test if requested.
|
||||
|
@ -514,6 +525,10 @@ module Test #:nodoc:
|
|||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
ActiveRecord::Base.unlock_mutex
|
||||
end
|
||||
unless dirty_fixture_table_names.empty?
|
||||
Fixtures.delete_fixtures(dirty_fixture_table_names)
|
||||
dirty_fixture_table_names.clear
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :teardown, :teardown_with_fixtures
|
||||
|
@ -540,15 +555,24 @@ module Test #:nodoc:
|
|||
end
|
||||
|
||||
private
|
||||
def load_fixtures
|
||||
@loaded_fixtures = {}
|
||||
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names)
|
||||
unless fixtures.nil?
|
||||
if fixtures.instance_of?(Fixtures)
|
||||
@loaded_fixtures[fixtures.table_name] = fixtures
|
||||
else
|
||||
fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
|
||||
end
|
||||
def reload_fixtures!
|
||||
# Clear dirty fixtures and loaded fixtures which were not declared
|
||||
# for this test case.
|
||||
wipe = dirty_fixture_table_names + loaded_fixture_table_names - fixture_table_names
|
||||
Fixtures.delete_fixtures(wipe) unless wipe.empty?
|
||||
dirty_fixture_table_names.clear
|
||||
loaded_fixture_table_names.clear
|
||||
|
||||
case fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names)
|
||||
when Fixtures
|
||||
loaded_fixture_table_names.push fixtures.table_name.to_s
|
||||
@loaded_fixtures = { fixtures.table_name => fixtures }
|
||||
when Enumerable
|
||||
@loaded_fixtures = {}
|
||||
loaded_fixture_table_names.concat fixtures.map { |f|
|
||||
@loaded_fixtures[f.table_name] = f
|
||||
f.table_name.to_s
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -559,7 +583,8 @@ module Test #:nodoc:
|
|||
if pre_loaded_fixtures
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
|
||||
unless @@required_fixture_classes
|
||||
self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
|
||||
names = Fixtures.all_loaded_fixtures.map { |fixtures| fixtures.table_name }
|
||||
self.class.require_fixture_classes names
|
||||
@@required_fixture_classes = true
|
||||
end
|
||||
Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
|
||||
|
|
|
@ -2,18 +2,16 @@ require 'abstract_unit'
|
|||
require 'fixtures/topic'
|
||||
|
||||
class TestColumnAlias < Test::Unit::TestCase
|
||||
fixtures :topics
|
||||
|
||||
QUERY = if 'OCI' == ActiveRecord::Base.connection.adapter_name
|
||||
'SELECT id AS pk FROM topics WHERE ROWNUM < 2'
|
||||
else
|
||||
'SELECT id AS pk FROM topics'
|
||||
end
|
||||
|
||||
def test_column_alias
|
||||
topic = Topic.find(1)
|
||||
if ActiveRecord::ConnectionAdapters.const_defined? :OracleAdapter
|
||||
if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::OracleAdapter)
|
||||
records = topic.connection.select_all("SELECT id AS pk FROM topics WHERE ROWNUM < 2")
|
||||
assert_equal("pk", records[0].keys[0])
|
||||
end
|
||||
else
|
||||
records = topic.connection.select_all("SELECT id AS pk FROM topics")
|
||||
assert_equal("pk", records[0].keys[0])
|
||||
end
|
||||
records = Topic.connection.select_all(QUERY)
|
||||
assert_equal 'pk', records[0].keys[0]
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -69,7 +69,7 @@ end
|
|||
|
||||
|
||||
class HasAndBelongsToManyScopingTest< Test::Unit::TestCase
|
||||
fixtures :posts, :categories
|
||||
fixtures :posts, :categories, :categories_posts
|
||||
|
||||
def setup
|
||||
@welcome = Post.find(1)
|
||||
|
@ -85,7 +85,6 @@ class HasAndBelongsToManyScopingTest< Test::Unit::TestCase
|
|||
assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
|
||||
assert_equal 2, @welcome.categories.find_all_by_type('Category').size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ require 'fixtures/developer'
|
|||
require 'fixtures/post'
|
||||
|
||||
class FinderTest < Test::Unit::TestCase
|
||||
fixtures :companies, :topics, :entrants, :developers, :posts
|
||||
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts
|
||||
|
||||
def test_find
|
||||
assert_equal(topics(:first).title, Topic.find(1).title)
|
||||
|
|
|
@ -86,6 +86,7 @@ class FixturesTest < Test::Unit::TestCase
|
|||
secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'")
|
||||
assert_nil(secondRow["author_email_address"])
|
||||
ensure
|
||||
Fixtures.all_loaded_fixtures.delete(topics)
|
||||
ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil
|
||||
end
|
||||
|
||||
|
@ -259,7 +260,7 @@ class MultipleFixturesTest < Test::Unit::TestCase
|
|||
fixtures :developers, :accounts
|
||||
|
||||
def test_fixture_table_names
|
||||
assert_equal([:topics, :developers, :accounts], fixture_table_names)
|
||||
assert_equal %w(topics developers accounts), fixture_table_names
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -269,7 +270,7 @@ class OverlappingFixturesTest < Test::Unit::TestCase
|
|||
fixtures :developers, :accounts
|
||||
|
||||
def test_fixture_table_names
|
||||
assert_equal([:topics, :developers, :accounts], fixture_table_names)
|
||||
assert_equal %w(topics developers accounts), fixture_table_names
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -289,3 +290,42 @@ class ForeignKeyFixturesTest < Test::Unit::TestCase
|
|||
assert true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class FixtureCleanup1Test < Test::Unit::TestCase
|
||||
fixtures :topics
|
||||
|
||||
def test_isolation
|
||||
assert_equal 0, Developer.count
|
||||
assert_equal 2, Topic.count
|
||||
end
|
||||
end
|
||||
|
||||
class FixtureCleanup2Test < Test::Unit::TestCase
|
||||
fixtures :developers
|
||||
|
||||
def test_isolation
|
||||
assert_equal 0, Topic.count
|
||||
assert_equal 10, Developer.count
|
||||
end
|
||||
end
|
||||
|
||||
class FixtureCleanup3Test < FixtureCleanup2Test
|
||||
self.use_transactional_fixtures = false
|
||||
|
||||
def test_dirty_fixture_table_names
|
||||
assert_equal %w(developers), dirty_fixture_table_names
|
||||
assert_equal %w(developers), loaded_fixture_table_names
|
||||
assert_equal %w(developers), fixture_table_names
|
||||
end
|
||||
end
|
||||
|
||||
class FixtureCleanup4Test < FixtureCleanup2Test
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
def test_dirty_fixture_table_names
|
||||
assert_equal [], dirty_fixture_table_names
|
||||
assert_equal %w(developers), loaded_fixture_table_names
|
||||
assert_equal %w(developers), fixture_table_names
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue