mirror of https://github.com/rails/rails
275 lines
9.5 KiB
Ruby
275 lines
9.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "active_support/testing/strict_warnings"
|
|
require "active_support"
|
|
require "active_support/testing/autorun"
|
|
require "active_support/testing/method_call_assertions"
|
|
require "active_support/testing/stream"
|
|
require "active_record/testing/query_assertions"
|
|
require "active_record/fixtures"
|
|
|
|
require "cases/validations_repair_helper"
|
|
require_relative "../support/config"
|
|
require_relative "../support/connection"
|
|
require_relative "../support/adapter_helper"
|
|
require_relative "../support/load_schema_helper"
|
|
|
|
module ActiveRecord
|
|
# = Active Record Test Case
|
|
#
|
|
# Defines some test assertions to test against SQL queries.
|
|
class TestCase < ActiveSupport::TestCase # :nodoc:
|
|
include ActiveSupport::Testing::MethodCallAssertions
|
|
include ActiveSupport::Testing::Stream
|
|
include ActiveRecord::Assertions::QueryAssertions
|
|
include ActiveRecord::TestFixtures
|
|
include ActiveRecord::ValidationsRepairHelper
|
|
include AdapterHelper
|
|
extend AdapterHelper
|
|
include LoadSchemaHelper
|
|
extend LoadSchemaHelper
|
|
|
|
self.fixture_paths = [FIXTURES_ROOT]
|
|
self.use_instantiated_fixtures = false
|
|
self.use_transactional_tests = true
|
|
|
|
def create_fixtures(*fixture_set_names, &block)
|
|
ActiveRecord::FixtureSet.create_fixtures(ActiveRecord::TestCase.fixture_paths, fixture_set_names, fixture_class_names, &block)
|
|
end
|
|
|
|
def capture_sql(include_schema: false)
|
|
counter = SQLCounter.new
|
|
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
|
yield
|
|
if include_schema
|
|
counter.log_all
|
|
else
|
|
counter.log
|
|
end
|
|
end
|
|
end
|
|
|
|
# Redefine existing assertion method to explicitly not materialize transactions.
|
|
def assert_queries_match(match, count: nil, include_schema: false, &block)
|
|
counter = SQLCounter.new
|
|
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
|
result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
|
|
queries = include_schema ? counter.log_all : counter.log
|
|
matched_queries = queries.select { |query| match === query }
|
|
|
|
if count
|
|
assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
|
else
|
|
assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
|
end
|
|
|
|
result
|
|
end
|
|
end
|
|
|
|
def assert_column(model, column_name, msg = nil)
|
|
model.reset_column_information
|
|
assert_includes model.column_names, column_name.to_s, msg
|
|
end
|
|
|
|
def assert_no_column(model, column_name, msg = nil)
|
|
model.reset_column_information
|
|
assert_not_includes model.column_names, column_name.to_s, msg
|
|
end
|
|
|
|
def with_has_many_inversing(model = ActiveRecord::Base)
|
|
old = model.has_many_inversing
|
|
model.has_many_inversing = true
|
|
yield
|
|
ensure
|
|
model.has_many_inversing = old
|
|
if model != ActiveRecord::Base && !old
|
|
model.singleton_class.remove_method(:has_many_inversing) # reset the class_attribute
|
|
end
|
|
end
|
|
|
|
def with_automatic_scope_inversing(*reflections)
|
|
old = reflections.map { |reflection| reflection.klass.automatic_scope_inversing }
|
|
|
|
reflections.each do |reflection|
|
|
reflection.klass.automatic_scope_inversing = true
|
|
reflection.remove_instance_variable(:@inverse_name) if reflection.instance_variable_defined?(:@inverse_name)
|
|
reflection.remove_instance_variable(:@inverse_of) if reflection.instance_variable_defined?(:@inverse_of)
|
|
end
|
|
|
|
yield
|
|
ensure
|
|
reflections.each_with_index do |reflection, i|
|
|
reflection.klass.automatic_scope_inversing = old[i]
|
|
reflection.remove_instance_variable(:@inverse_name) if reflection.instance_variable_defined?(:@inverse_name)
|
|
reflection.remove_instance_variable(:@inverse_of) if reflection.instance_variable_defined?(:@inverse_of)
|
|
end
|
|
end
|
|
|
|
def with_db_warnings_action(action, warnings_to_ignore = [])
|
|
original_db_warnings_ignore = ActiveRecord.db_warnings_ignore
|
|
|
|
ActiveRecord.db_warnings_action = action
|
|
ActiveRecord.db_warnings_ignore = warnings_to_ignore
|
|
|
|
ActiveRecord::Base.connection.disconnect! # Disconnect from the db so that we reconfigure the connection
|
|
|
|
yield
|
|
ensure
|
|
ActiveRecord.db_warnings_action = @original_db_warnings_action
|
|
ActiveRecord.db_warnings_ignore = original_db_warnings_ignore
|
|
ActiveRecord::Base.connection.disconnect!
|
|
end
|
|
|
|
def reset_callbacks(klass, kind)
|
|
old_callbacks = {}
|
|
old_callbacks[klass] = klass.send("_#{kind}_callbacks").dup
|
|
klass.subclasses.each do |subclass|
|
|
old_callbacks[subclass] = subclass.send("_#{kind}_callbacks").dup
|
|
end
|
|
yield
|
|
ensure
|
|
klass.send("_#{kind}_callbacks=", old_callbacks[klass])
|
|
klass.subclasses.each do |subclass|
|
|
subclass.send("_#{kind}_callbacks=", old_callbacks[subclass])
|
|
end
|
|
end
|
|
|
|
def with_postgresql_datetime_type(type)
|
|
adapter = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
adapter.remove_instance_variable(:@native_database_types) if adapter.instance_variable_defined?(:@native_database_types)
|
|
datetime_type_was = adapter.datetime_type
|
|
adapter.datetime_type = type
|
|
yield
|
|
ensure
|
|
adapter = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
adapter.datetime_type = datetime_type_was
|
|
adapter.remove_instance_variable(:@native_database_types) if adapter.instance_variable_defined?(:@native_database_types)
|
|
end
|
|
|
|
def with_env_tz(new_tz = "US/Eastern")
|
|
old_tz, ENV["TZ"] = ENV["TZ"], new_tz
|
|
yield
|
|
ensure
|
|
old_tz ? ENV["TZ"] = old_tz : ENV.delete("TZ")
|
|
end
|
|
|
|
def with_timezone_config(cfg)
|
|
verify_default_timezone_config
|
|
|
|
old_default_zone = ActiveRecord.default_timezone
|
|
old_awareness = ActiveRecord::Base.time_zone_aware_attributes
|
|
old_aware_types = ActiveRecord::Base.time_zone_aware_types
|
|
old_zone = Time.zone
|
|
|
|
if cfg.has_key?(:default)
|
|
ActiveRecord.default_timezone = cfg[:default]
|
|
end
|
|
if cfg.has_key?(:aware_attributes)
|
|
ActiveRecord::Base.time_zone_aware_attributes = cfg[:aware_attributes]
|
|
end
|
|
if cfg.has_key?(:aware_types)
|
|
ActiveRecord::Base.time_zone_aware_types = cfg[:aware_types]
|
|
end
|
|
if cfg.has_key?(:zone)
|
|
Time.zone = cfg[:zone]
|
|
end
|
|
yield
|
|
ensure
|
|
ActiveRecord.default_timezone = old_default_zone
|
|
ActiveRecord::Base.time_zone_aware_attributes = old_awareness
|
|
ActiveRecord::Base.time_zone_aware_types = old_aware_types
|
|
Time.zone = old_zone
|
|
end
|
|
|
|
# This method makes sure that tests don't leak global state related to time zones.
|
|
EXPECTED_ZONE = nil
|
|
EXPECTED_DEFAULT_TIMEZONE = :utc
|
|
EXPECTED_AWARE_TYPES = [:datetime, :time]
|
|
EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES = false
|
|
def verify_default_timezone_config
|
|
if Time.zone != EXPECTED_ZONE
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `Time.zone` was leaked.
|
|
Expected: #{EXPECTED_ZONE}
|
|
Got: #{Time.zone}
|
|
MSG
|
|
end
|
|
if ActiveRecord.default_timezone != EXPECTED_DEFAULT_TIMEZONE
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `ActiveRecord.default_timezone` was leaked.
|
|
Expected: #{EXPECTED_DEFAULT_TIMEZONE}
|
|
Got: #{ActiveRecord.default_timezone}
|
|
MSG
|
|
end
|
|
if ActiveRecord::Base.time_zone_aware_attributes != EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `ActiveRecord::Base.time_zone_aware_attributes` was leaked.
|
|
Expected: #{EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES}
|
|
Got: #{ActiveRecord::Base.time_zone_aware_attributes}
|
|
MSG
|
|
end
|
|
if ActiveRecord::Base.time_zone_aware_types != EXPECTED_AWARE_TYPES
|
|
$stderr.puts <<-MSG
|
|
\n#{self}
|
|
Global state `ActiveRecord::Base.time_zone_aware_types` was leaked.
|
|
Expected: #{EXPECTED_AWARE_TYPES}
|
|
Got: #{ActiveRecord::Base.time_zone_aware_types}
|
|
MSG
|
|
end
|
|
end
|
|
|
|
def clean_up_connection_handler
|
|
handler = ActiveRecord::Base.connection_handler
|
|
handler.instance_variable_get(:@connection_name_to_pool_manager).each do |owner, pool_manager|
|
|
pool_manager.role_names.each do |role_name|
|
|
next if role_name == ActiveRecord::Base.default_role &&
|
|
# TODO: Remove this helper when `remove_connection` for different shards is fixed.
|
|
# See https://github.com/rails/rails/pull/49382.
|
|
["ActiveRecord::Base", "ARUnit2Model", "Contact", "ContactSti"].include?(owner)
|
|
pool_manager.remove_role(role_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Connect to the database
|
|
ARTest.connect
|
|
# Load database schema
|
|
load_schema
|
|
end
|
|
|
|
class PostgreSQLTestCase < TestCase
|
|
def self.run(*args)
|
|
super if current_adapter?(:PostgreSQLAdapter)
|
|
end
|
|
end
|
|
|
|
class AbstractMysqlTestCase < TestCase
|
|
def self.run(*args)
|
|
super if current_adapter?(:Mysql2Adapter) || current_adapter?(:TrilogyAdapter)
|
|
end
|
|
end
|
|
|
|
class Mysql2TestCase < TestCase
|
|
def self.run(*args)
|
|
super if current_adapter?(:Mysql2Adapter)
|
|
end
|
|
end
|
|
|
|
class TrilogyTestCase < TestCase
|
|
def self.run(*args)
|
|
super if current_adapter?(:TrilogyAdapter)
|
|
end
|
|
end
|
|
|
|
|
|
class SQLite3TestCase < TestCase
|
|
def self.run(*args)
|
|
super if current_adapter?(:SQLite3Adapter)
|
|
end
|
|
end
|
|
end
|