1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/test/cases/test_case.rb
Yasuo Honda 6030110f56 Remove database specific sql statements from SQLCounter
Every database executes different type of sql statement to get metadata then `ActiveRecord::TestCase` ignores these database specific sql statements to make `assert_queries` or `assert_no_queries` work consistently.

Connection adapter already labels these statement by setting "SCHEMA" argument, this pull request makes use of "SCHEMA" argument to ignore metadata queries.

Here are the details of these changes:

* PostgresqlConnectionTest

Each of PostgresqlConnectionTest modified just executes corresponding methods

fef174f5c5/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb (L182-L195)

```ruby
        # Returns the current database encoding format.
        def encoding
          query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
        end

        # Returns the current database collation.
        def collation
          query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
        end

        # Returns the current database ctype.
        def ctype
          query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
        end
```

* BulkAlterTableMigrationsTest

mysql2 adapter executes `SHOW KEYS FROM ...` to see if there is an index already created as below. I think the main concerns of these tests are how each database adapter creates or drops indexes then ignoring  `SHOW KEYS FROM` statement makes sense.

fef174f5c5/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb (L11)

```ruby
          execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
```

* Temporary change not included in this commit to show which statements executed

```diff
$ git diff
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 8e8ed494d9..df05f9bd16 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -854,7 +854,7 @@ def test_adding_indexes

       classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
       expected_query_count = {
-        "Mysql2Adapter"     => 3, # Adding an index fires a query every time to check if an index already exists or not
+        "Mysql2Adapter"     => 1, # Adding an index fires a query every time to check if an index already exists or not
         "PostgreSQLAdapter" => 2,
       }.fetch(classname) {
         raise "need an expected query count for #{classname}"
@@ -886,7 +886,7 @@ def test_removing_index

       classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
       expected_query_count = {
-        "Mysql2Adapter"     => 3, # Adding an index fires a query every time to check if an index already exists or not
+        "Mysql2Adapter"     => 1, # Adding an index fires a query every time to check if an index already exists or not
         "PostgreSQLAdapter" => 2,
       }.fetch(classname) {
         raise "need an expected query count for #{classname}"
$
```

* Executed these modified tests

```ruby
$ ARCONN=mysql2 bin/test test/cases/migration_test.rb -n /index/
Using mysql2
Run options: -n /index/ --seed 8462

F

Failure:
BulkAlterTableMigrationsTest#test_adding_indexes [/home/yahonda/git/rails/activerecord/test/cases/migration_test.rb:863]:
3 instead of 1 queries were executed.
Queries:
SHOW KEYS FROM `delete_me`
SHOW KEYS FROM `delete_me`
ALTER TABLE `delete_me` ADD UNIQUE INDEX `awesome_username_index`  (`username`), ADD  INDEX `index_delete_me_on_name_and_age`  (`name`, `age`).
Expected: 1
  Actual: 3

bin/test test/cases/migration_test.rb:848

F

Failure:
BulkAlterTableMigrationsTest#test_removing_index [/home/yahonda/git/rails/activerecord/test/cases/migration_test.rb:895]:
3 instead of 1 queries were executed.
Queries:
SHOW KEYS FROM `delete_me`
SHOW KEYS FROM `delete_me`
ALTER TABLE `delete_me` DROP INDEX `index_delete_me_on_name`, ADD UNIQUE INDEX `new_name_index`  (`name`).
Expected: 1
  Actual: 3

bin/test test/cases/migration_test.rb:879

..

Finished in 0.379245s, 10.5473 runs/s, 7.9105 assertions/s.
4 runs, 3 assertions, 2 failures, 0 errors, 0 skips
$
```

* ActiveRecord::ConnectionAdapters::Savepoints

Left `self.ignored_sql` to ignore savepoint related statements because these SQL statements are not related "SCHEMA"

```
self.ignored_sql = [/^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/]
```

fef174f5c5/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb (L10-L20)

```ruby
      def create_savepoint(name = current_savepoint_name)
        execute("SAVEPOINT #{name}")
      end

      def exec_rollback_to_savepoint(name = current_savepoint_name)
        execute("ROLLBACK TO SAVEPOINT #{name}")
      end

      def release_savepoint(name = current_savepoint_name)
        execute("RELEASE SAVEPOINT #{name}")
      end
```
2019-05-01 13:30:11 +00:00

128 lines
3.7 KiB
Ruby

# frozen_string_literal: true
require "active_support"
require "active_support/testing/autorun"
require "active_support/testing/method_call_assertions"
require "active_support/testing/stream"
require "active_record/fixtures"
require "cases/validations_repair_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::TestFixtures
include ActiveRecord::ValidationsRepairHelper
self.fixture_path = 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_path, fixture_set_names, fixture_class_names, &block)
end
def teardown
SQLCounter.clear_log
end
def capture_sql
ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
yield
SQLCounter.log_all.dup
end
def assert_sql(*patterns_to_match)
capture_sql { yield }
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
failed_patterns << pattern unless SQLCounter.log_all.any? { |sql| pattern === sql }
end
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1, options = {})
ignore_none = options.fetch(:ignore_none) { num == :any }
ActiveRecord::Base.connection.materialize_transactions
SQLCounter.clear_log
x = yield
the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
if num == :any
assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
else
mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
assert_equal num, the_log.size, mesg
end
x
end
def assert_no_queries(options = {}, &block)
options.reverse_merge! ignore_none: true
assert_queries(0, options, &block)
end
def assert_column(model, column_name, msg = nil)
assert has_column?(model, column_name), msg
end
def assert_no_column(model, column_name, msg = nil)
assert_not has_column?(model, column_name), msg
end
def has_column?(model, column_name)
model.reset_column_information
model.column_names.include?(column_name.to_s)
end
end
class PostgreSQLTestCase < TestCase
def self.run(*args)
super if current_adapter?(:PostgreSQLAdapter)
end
end
class Mysql2TestCase < TestCase
def self.run(*args)
super if current_adapter?(:Mysql2Adapter)
end
end
class SQLite3TestCase < TestCase
def self.run(*args)
super if current_adapter?(:SQLite3Adapter)
end
end
class SQLCounter
class << self
attr_accessor :ignored_sql, :log, :log_all
def clear_log; self.log = []; self.log_all = []; end
end
clear_log
self.ignored_sql = [/^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/]
attr_reader :ignore
def initialize(ignore = Regexp.union(self.class.ignored_sql))
@ignore = ignore
end
def call(name, start, finish, message_id, values)
return if values[:cached]
sql = values[:sql]
self.class.log_all << sql
self.class.log << sql unless values[:name] == "SCHEMA" || ignore.match?(sql)
end
end
ActiveSupport::Notifications.subscribe("sql.active_record", SQLCounter.new)
end