2017-07-09 17:41:28 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-05-01 16:08:48 +00:00
|
|
|
require "active_support"
|
2016-08-13 19:34:35 +00:00
|
|
|
require "active_support/testing/autorun"
|
|
|
|
require "active_support/testing/method_call_assertions"
|
2016-08-06 16:26:20 +00:00
|
|
|
require "active_support/testing/stream"
|
2016-08-13 19:34:35 +00:00
|
|
|
require "active_record/fixtures"
|
|
|
|
|
|
|
|
require "cases/validations_repair_helper"
|
2013-07-02 20:08:04 +00:00
|
|
|
|
|
|
|
module ActiveRecord
|
2014-07-16 14:15:16 +00:00
|
|
|
# = Active Record Test Case
|
|
|
|
#
|
|
|
|
# Defines some test assertions to test against SQL queries.
|
|
|
|
class TestCase < ActiveSupport::TestCase #:nodoc:
|
2016-08-13 19:34:35 +00:00
|
|
|
include ActiveSupport::Testing::MethodCallAssertions
|
2015-01-15 06:42:33 +00:00
|
|
|
include ActiveSupport::Testing::Stream
|
2016-08-13 19:34:35 +00:00
|
|
|
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
|
2015-01-15 06:42:33 +00:00
|
|
|
|
2014-07-16 14:15:16 +00:00
|
|
|
def teardown
|
|
|
|
SQLCounter.clear_log
|
|
|
|
end
|
|
|
|
|
2014-03-31 23:18:04 +00:00
|
|
|
def capture_sql
|
2018-08-13 15:51:54 +00:00
|
|
|
ActiveRecord::Base.connection.materialize_transactions
|
2014-03-31 23:18:04 +00:00
|
|
|
SQLCounter.clear_log
|
|
|
|
yield
|
|
|
|
SQLCounter.log_all.dup
|
|
|
|
end
|
|
|
|
|
2013-07-02 20:08:04 +00:00
|
|
|
def assert_sql(*patterns_to_match)
|
2014-04-01 02:53:45 +00:00
|
|
|
capture_sql { yield }
|
2013-07-02 20:08:04 +00:00
|
|
|
ensure
|
|
|
|
failed_patterns = []
|
|
|
|
patterns_to_match.each do |pattern|
|
2016-08-16 07:30:11 +00:00
|
|
|
failed_patterns << pattern unless SQLCounter.log_all.any? { |sql| pattern === sql }
|
2013-07-02 20:08:04 +00:00
|
|
|
end
|
2014-10-27 16:28:53 +00:00
|
|
|
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
|
2013-07-02 20:08:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def assert_queries(num = 1, options = {})
|
|
|
|
ignore_none = options.fetch(:ignore_none) { num == :any }
|
2018-08-13 15:51:54 +00:00
|
|
|
ActiveRecord::Base.connection.materialize_transactions
|
2013-07-02 20:08:04 +00:00
|
|
|
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
|
|
|
|
|
2013-07-30 08:01:00 +00:00
|
|
|
def assert_no_queries(options = {}, &block)
|
|
|
|
options.reverse_merge! ignore_none: true
|
|
|
|
assert_queries(0, options, &block)
|
2013-07-02 20:08:04 +00:00
|
|
|
end
|
|
|
|
|
2016-10-29 03:05:58 +00:00
|
|
|
def assert_column(model, column_name, msg = nil)
|
2013-07-16 12:19:24 +00:00
|
|
|
assert has_column?(model, column_name), msg
|
|
|
|
end
|
|
|
|
|
2016-10-29 03:05:58 +00:00
|
|
|
def assert_no_column(model, column_name, msg = nil)
|
2013-07-16 12:19:24 +00:00
|
|
|
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
|
2013-07-02 20:08:04 +00:00
|
|
|
end
|
|
|
|
|
2015-06-11 12:11:52 +00:00
|
|
|
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
|
|
|
|
|
2013-07-02 20:08:04 +00:00
|
|
|
class SQLCounter
|
|
|
|
class << self
|
|
|
|
attr_accessor :ignored_sql, :log, :log_all
|
|
|
|
def clear_log; self.log = []; self.log_all = []; end
|
|
|
|
end
|
|
|
|
|
2016-08-07 23:05:28 +00:00
|
|
|
clear_log
|
2013-07-02 20:08:04 +00:00
|
|
|
|
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
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/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.
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/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/]
```
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/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-04-30 22:42:59 +00:00
|
|
|
self.ignored_sql = [/^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/]
|
2013-07-02 20:08:04 +00:00
|
|
|
|
|
|
|
attr_reader :ignore
|
|
|
|
|
|
|
|
def initialize(ignore = Regexp.union(self.class.ignored_sql))
|
|
|
|
@ignore = ignore
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(name, start, finish, message_id, values)
|
2016-09-22 13:00:30 +00:00
|
|
|
return if values[:cached]
|
2013-07-02 20:08:04 +00:00
|
|
|
|
2016-09-22 13:00:30 +00:00
|
|
|
sql = values[:sql]
|
2013-07-02 20:08:04 +00:00
|
|
|
self.class.log_all << sql
|
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
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/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.
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/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/]
```
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/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-04-30 22:42:59 +00:00
|
|
|
self.class.log << sql unless values[:name] == "SCHEMA" || ignore.match?(sql)
|
2013-07-02 20:08:04 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-06 16:26:20 +00:00
|
|
|
ActiveSupport::Notifications.subscribe("sql.active_record", SQLCounter.new)
|
2012-02-06 06:09:09 +00:00
|
|
|
end
|