active_record_doctor 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +29 -0
- data/lib/active_record_doctor.rb +16 -12
- data/lib/active_record_doctor/detectors.rb +13 -0
- data/lib/active_record_doctor/detectors/base.rb +64 -0
- data/lib/active_record_doctor/{tasks → detectors}/extraneous_indexes.rb +11 -7
- data/lib/active_record_doctor/{tasks → detectors}/incorrect_boolean_presence_validation.rb +9 -6
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +71 -0
- data/lib/active_record_doctor/{tasks → detectors}/missing_foreign_keys.rb +13 -10
- data/lib/active_record_doctor/{tasks → detectors}/missing_non_null_constraint.rb +11 -7
- data/lib/active_record_doctor/{tasks → detectors}/missing_presence_validation.rb +11 -8
- data/lib/active_record_doctor/{tasks → detectors}/missing_unique_indexes.rb +8 -4
- data/lib/active_record_doctor/{tasks → detectors}/undefined_table_references.rb +11 -12
- data/lib/active_record_doctor/{tasks → detectors}/unindexed_deleted_at.rb +12 -6
- data/lib/active_record_doctor/{tasks → detectors}/unindexed_foreign_keys.rb +13 -10
- data/lib/active_record_doctor/printers.rb +3 -1
- data/lib/active_record_doctor/printers/io_printer.rb +63 -35
- data/lib/active_record_doctor/railtie.rb +2 -0
- data/lib/active_record_doctor/task.rb +28 -0
- data/lib/active_record_doctor/version.rb +3 -1
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +15 -11
- data/lib/tasks/active_record_doctor.rake +25 -25
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +67 -0
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +36 -0
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +117 -0
- data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +24 -0
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +102 -0
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +107 -0
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +114 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +44 -0
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +67 -0
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +26 -0
- data/test/active_record_doctor/printers/io_printer_test.rb +14 -9
- data/test/model_factory.rb +78 -0
- data/test/setup.rb +69 -40
- metadata +70 -64
- data/lib/active_record_doctor/tasks.rb +0 -10
- data/lib/active_record_doctor/tasks/base.rb +0 -86
- data/test/active_record_doctor/tasks/extraneous_indexes_test.rb +0 -77
- data/test/active_record_doctor/tasks/incorrect_boolean_presence_validation_test.rb +0 -38
- data/test/active_record_doctor/tasks/missing_foreign_keys_test.rb +0 -23
- data/test/active_record_doctor/tasks/missing_non_null_constraint_test.rb +0 -113
- data/test/active_record_doctor/tasks/missing_presence_validation_test.rb +0 -115
- data/test/active_record_doctor/tasks/missing_unique_indexes_test.rb +0 -126
- data/test/active_record_doctor/tasks/undefined_table_references_test.rb +0 -47
- data/test/active_record_doctor/tasks/unindexed_deleted_at_test.rb +0 -59
- data/test/active_record_doctor/tasks/unindexed_foreign_keys_test.rb +0 -23
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveRecordDoctor::Detectors::MissingUniqueIndexesTest < Minitest::Test
|
4
|
+
def test_missing_unique_index
|
5
|
+
create_table(:users) do |t|
|
6
|
+
t.string :email
|
7
|
+
t.index :email
|
8
|
+
end.create_model do
|
9
|
+
validates :email, uniqueness: true
|
10
|
+
end
|
11
|
+
|
12
|
+
assert_problems(<<OUTPUT)
|
13
|
+
The following indexes should be created to back model-level uniqueness validations:
|
14
|
+
users: email
|
15
|
+
OUTPUT
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_present_unique_index
|
19
|
+
create_table(:users) do |t|
|
20
|
+
t.string :email
|
21
|
+
t.index :email, unique: true
|
22
|
+
end.create_model do
|
23
|
+
validates :email, uniqueness: true
|
24
|
+
end
|
25
|
+
|
26
|
+
refute_problems
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_missing_unique_index_with_scope
|
30
|
+
create_table(:users) do |t|
|
31
|
+
t.string :email
|
32
|
+
t.integer :company_id
|
33
|
+
t.integer :department_id
|
34
|
+
t.index [:company_id, :department_id, :email]
|
35
|
+
end.create_model do
|
36
|
+
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_problems(<<OUTPUT)
|
40
|
+
The following indexes should be created to back model-level uniqueness validations:
|
41
|
+
users: company_id, department_id, email
|
42
|
+
OUTPUT
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_present_unique_index_with_scope
|
46
|
+
create_table(:users) do |t|
|
47
|
+
t.string :email
|
48
|
+
t.integer :company_id
|
49
|
+
t.integer :department_id
|
50
|
+
t.index [:company_id, :department_id, :email], unique: true
|
51
|
+
end.create_model do
|
52
|
+
validates :email, uniqueness: { scope: [:company_id, :department_id] }
|
53
|
+
end
|
54
|
+
|
55
|
+
refute_problems
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_column_order_is_ignored
|
59
|
+
create_table(:users) do |t|
|
60
|
+
t.string :email
|
61
|
+
t.integer :organization_id
|
62
|
+
|
63
|
+
t.index [:email, :organization_id], unique: true
|
64
|
+
end.create_model do
|
65
|
+
validates :email, uniqueness: { scope: :organization_id }
|
66
|
+
end
|
67
|
+
|
68
|
+
refute_problems
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_conditions_is_skipped
|
72
|
+
assert_skipped(conditions: -> { where.not(email: nil) })
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_case_insensitive_is_skipped
|
76
|
+
assert_skipped(case_sensitive: false)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_if_is_skipped
|
80
|
+
assert_skipped(if: ->(_model) { true })
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_unless_is_skipped
|
84
|
+
assert_skipped(unless: ->(_model) { true })
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_skips_validator_without_attributes
|
88
|
+
create_table(:users) do |t|
|
89
|
+
t.string :email
|
90
|
+
t.index :email
|
91
|
+
end.create_model do
|
92
|
+
validates_with DummyValidator
|
93
|
+
end
|
94
|
+
|
95
|
+
refute_problems
|
96
|
+
end
|
97
|
+
|
98
|
+
class DummyValidator < ActiveModel::Validator
|
99
|
+
def validate(record)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def assert_skipped(options)
|
106
|
+
create_table(:users) do |t|
|
107
|
+
t.string :email
|
108
|
+
end.create_model do
|
109
|
+
validates :email, uniqueness: options
|
110
|
+
end
|
111
|
+
|
112
|
+
refute_problems
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveRecordDoctor::Detectors::UndefinedTableReferencesTest < Minitest::Test
|
4
|
+
def test_table_exists
|
5
|
+
create_table(:users) do
|
6
|
+
end.create_model do
|
7
|
+
end
|
8
|
+
|
9
|
+
refute_problems
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_table_does_not_exist_when_views_supported
|
13
|
+
create_model(:users)
|
14
|
+
|
15
|
+
if mysql? && ActiveRecord::VERSION::STRING < "5.0"
|
16
|
+
assert_problems(<<OUTPUT)
|
17
|
+
WARNING: Models backed by database views are supported only in Rails 5+ OR
|
18
|
+
Rails 4.2 + PostgreSQL. It seems this is NOT your setup. Therefore, such models
|
19
|
+
will be erroneously reported below as not having their underlying tables/views.
|
20
|
+
Consider upgrading Rails or disabling this task temporarily.
|
21
|
+
The following models reference undefined tables:
|
22
|
+
ModelFactory::Models::User (the table users is undefined)
|
23
|
+
OUTPUT
|
24
|
+
else
|
25
|
+
assert_problems(<<OUTPUT)
|
26
|
+
The following models reference undefined tables:
|
27
|
+
ModelFactory::Models::User (the table users is undefined)
|
28
|
+
OUTPUT
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_view_instead_of_table
|
33
|
+
# We replace the underlying table with a view. The view doesn't have to be
|
34
|
+
# backed by an actual table - it can simply return a predefined tuple.
|
35
|
+
ActiveRecord::Base.connection.execute("CREATE VIEW users AS SELECT 1")
|
36
|
+
create_model(:users)
|
37
|
+
|
38
|
+
begin
|
39
|
+
refute_problems
|
40
|
+
ensure
|
41
|
+
ActiveRecord::Base.connection.execute("DROP VIEW users")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveRecordDoctor::Detectors::UnindexedDeletedAtTest < Minitest::Test
|
4
|
+
def test_indexed_deleted_at_is_not_reported
|
5
|
+
skip("MySQL doesn't support partial indexes") if mysql?
|
6
|
+
|
7
|
+
create_table(:users) do |t|
|
8
|
+
t.string :first_name
|
9
|
+
t.string :last_name
|
10
|
+
t.datetime :deleted_at
|
11
|
+
t.index [:first_name, :last_name],
|
12
|
+
name: "index_profiles_on_first_name_and_last_name",
|
13
|
+
where: "deleted_at IS NULL"
|
14
|
+
end
|
15
|
+
|
16
|
+
refute_problems
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_unindexed_deleted_at_is_reported
|
20
|
+
skip("MySQL doesn't support partial indexes") if mysql?
|
21
|
+
|
22
|
+
create_table(:users) do |t|
|
23
|
+
t.string :first_name
|
24
|
+
t.string :last_name
|
25
|
+
t.datetime :deleted_at
|
26
|
+
t.index [:first_name, :last_name],
|
27
|
+
name: "index_profiles_on_first_name_and_last_name"
|
28
|
+
end
|
29
|
+
|
30
|
+
assert_problems(<<OUTPUT)
|
31
|
+
The following indexes should include `deleted_at IS NULL`:
|
32
|
+
index_profiles_on_first_name_and_last_name
|
33
|
+
OUTPUT
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_indexed_discarded_at_is_not_reported
|
37
|
+
skip("MySQL doesn't support partial indexes") if mysql?
|
38
|
+
|
39
|
+
create_table(:users) do |t|
|
40
|
+
t.string :first_name
|
41
|
+
t.string :last_name
|
42
|
+
t.datetime :discarded_at
|
43
|
+
t.index [:first_name, :last_name],
|
44
|
+
name: "index_profiles_on_first_name_and_last_name",
|
45
|
+
where: "discarded_at IS NULL"
|
46
|
+
end
|
47
|
+
|
48
|
+
refute_problems
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_unindexed_discarded_at_is_reported
|
52
|
+
skip("MySQL doesn't support partial indexes") if mysql?
|
53
|
+
|
54
|
+
create_table(:users) do |t|
|
55
|
+
t.string :first_name
|
56
|
+
t.string :last_name
|
57
|
+
t.datetime :discarded_at
|
58
|
+
t.index [:first_name, :last_name],
|
59
|
+
name: "index_profiles_on_first_name_and_last_name"
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_problems(<<OUTPUT)
|
63
|
+
The following indexes should include `deleted_at IS NULL`:
|
64
|
+
index_profiles_on_first_name_and_last_name
|
65
|
+
OUTPUT
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ActiveRecordDoctor::Detectors::UnindexedForeignKeysTest < Minitest::Test
|
4
|
+
def test_unindexed_foreign_key_is_reported
|
5
|
+
skip("MySQL always indexes foreign keys") if mysql?
|
6
|
+
|
7
|
+
create_table(:companies)
|
8
|
+
create_table(:users) do |t|
|
9
|
+
t.references :company, foreign_key: true, index: false
|
10
|
+
end
|
11
|
+
|
12
|
+
assert_problems(<<OUTPUT)
|
13
|
+
The following foreign keys should be indexed for performance reasons:
|
14
|
+
users company_id
|
15
|
+
OUTPUT
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_indexed_foreign_key_is_not_reported
|
19
|
+
create_table(:companies)
|
20
|
+
create_table(:users) do |t|
|
21
|
+
t.references :company, foreign_key: true, index: true
|
22
|
+
end
|
23
|
+
|
24
|
+
refute_problems
|
25
|
+
end
|
26
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Load all detectors
|
2
4
|
class ActiveRecordDoctor::Printers::IOPrinterTest < Minitest::Test
|
3
|
-
def
|
4
|
-
ActiveRecordDoctor::
|
5
|
-
name =
|
5
|
+
def test_all_detectors_have_printers
|
6
|
+
ActiveRecordDoctor::Detectors::Base.subclasses.each do |detector_class|
|
7
|
+
name = detector_class.name.demodulize.underscore.to_sym
|
6
8
|
|
7
9
|
assert(
|
8
10
|
ActiveRecordDoctor::Printers::IOPrinter.method_defined?(name),
|
@@ -12,17 +14,20 @@ class ActiveRecordDoctor::Printers::IOPrinterTest < Minitest::Test
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def test_unindexed_foreign_keys
|
15
|
-
|
16
|
-
account group_id
|
17
|
-
|
18
|
-
|
17
|
+
# rubocop:disable Layout/LineLength
|
18
|
+
assert_equal(<<OUTPUT, unindexed_foreign_keys({ "users" => ["profile_id", "account_id"], "account" => ["group_id"] }))
|
19
|
+
The following foreign keys should be indexed for performance reasons:
|
20
|
+
account group_id
|
21
|
+
users account_id profile_id
|
22
|
+
OUTPUT
|
23
|
+
# rubocop:enable Layout/LineLength
|
19
24
|
end
|
20
25
|
|
21
26
|
private
|
22
27
|
|
23
28
|
def unindexed_foreign_keys(argument)
|
24
29
|
io = StringIO.new
|
25
|
-
ActiveRecordDoctor::Printers::IOPrinter.new(io).unindexed_foreign_keys(argument)
|
30
|
+
ActiveRecordDoctor::Printers::IOPrinter.new(io).unindexed_foreign_keys(argument, {})
|
26
31
|
io.string
|
27
32
|
end
|
28
33
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModelFactory
|
4
|
+
def self.cleanup
|
5
|
+
delete_models
|
6
|
+
drop_all_tables
|
7
|
+
GC.start
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.drop_all_tables
|
11
|
+
connection = ActiveRecord::Base.connection
|
12
|
+
loop do
|
13
|
+
before = connection.tables.size
|
14
|
+
break if before.zero?
|
15
|
+
|
16
|
+
attempt_drop_all_tables(connection)
|
17
|
+
after = connection.tables.size
|
18
|
+
|
19
|
+
if before == after
|
20
|
+
raise("cannot delete temporary tables - most likely due to failing constraints")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.attempt_drop_all_tables(connection)
|
26
|
+
connection.tables.each do |table_name|
|
27
|
+
ActiveRecord::Migration.suppress_messages do
|
28
|
+
begin
|
29
|
+
connection.drop_table(table_name, force: :cascade)
|
30
|
+
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::StatementInvalid
|
31
|
+
# The table cannot be dropped due to foreign key constraints so
|
32
|
+
# we'll try to drop it on another attempt.
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.delete_models
|
39
|
+
Models.empty
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.create_table(table_name, &block)
|
43
|
+
table_name = table_name.to_sym
|
44
|
+
ActiveRecord::Migration.suppress_messages do
|
45
|
+
ActiveRecord::Migration.create_table(table_name, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return a proxy object allowing the caller to chain #create_model
|
49
|
+
# right after creating a table so that it can be followed by the model
|
50
|
+
# definition.
|
51
|
+
ModelDefinitionProxy.new(table_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_model(table_name, &block)
|
55
|
+
table_name = table_name.to_sym
|
56
|
+
klass = Class.new(ActiveRecord::Base, &block)
|
57
|
+
klass_name = table_name.to_s.classify
|
58
|
+
Models.const_set(klass_name, klass)
|
59
|
+
end
|
60
|
+
|
61
|
+
class ModelDefinitionProxy
|
62
|
+
def initialize(table_name)
|
63
|
+
@table_name = table_name
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_model(&block)
|
67
|
+
ModelFactory.create_model(@table_name, &block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Models
|
72
|
+
def self.empty
|
73
|
+
constants.each do |name|
|
74
|
+
remove_const(name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/test/setup.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Configure Active Record.
|
2
4
|
|
3
5
|
# We must import "uri" explicitly as otherwsie URI won't be accessible in
|
@@ -7,15 +9,30 @@ require "uri"
|
|
7
9
|
require "active_record"
|
8
10
|
|
9
11
|
# Connect to the database defined in the URL.
|
10
|
-
|
12
|
+
case ENV["DATABASE"]
|
13
|
+
when "postgresql"
|
14
|
+
require "pg"
|
15
|
+
DEFAULT_DATABASE_URL = "postgres:///active_record_doctor_test"
|
16
|
+
when "mysql"
|
17
|
+
require "mysql2"
|
18
|
+
DEFAULT_DATABASE_URL = "mysql2:///active_record_doctor_test"
|
19
|
+
when nil
|
20
|
+
# rubocop:disable Style/StderrPuts
|
21
|
+
$stderr.puts(<<ERROR)
|
22
|
+
The DATABASE environment variable is not set. It must be set before running the
|
23
|
+
test suite. Valid values are "mysql" and "postgresql".
|
24
|
+
ERROR
|
25
|
+
# rubocop:enable Style/StderrPuts
|
26
|
+
exit(1)
|
27
|
+
else raise("unrecognized database #{ENV['DATABASE']}")
|
28
|
+
end
|
29
|
+
ActiveRecord::Base.establish_connection(ENV.fetch("DATABASE_URL", DEFAULT_DATABASE_URL))
|
11
30
|
|
12
31
|
# We need to call #connection to enfore Active Record to actually establish
|
13
32
|
# the connection.
|
14
33
|
ActiveRecord::Base.connection
|
15
34
|
|
16
|
-
|
17
|
-
|
18
|
-
# We need to mock Rails because some tasks depend on .eager_load! This must
|
35
|
+
# We need to mock Rails because some detectors depend on .eager_load! This must
|
19
36
|
# happen AFTER loading active_record_doctor as otherwise it'd attempt to
|
20
37
|
# install a Railtie.
|
21
38
|
module Rails
|
@@ -29,61 +46,75 @@ module Rails
|
|
29
46
|
end
|
30
47
|
end
|
31
48
|
|
32
|
-
|
33
|
-
|
34
49
|
# Load Active Record Doctor.
|
35
50
|
require "active_record_doctor"
|
36
51
|
|
37
|
-
|
38
|
-
|
39
52
|
# Configure the test suite.
|
40
53
|
require "minitest"
|
41
54
|
require "minitest/autorun"
|
42
55
|
require "minitest/fork_executor"
|
43
56
|
|
57
|
+
require_relative "model_factory"
|
44
58
|
|
59
|
+
# Prepare the test class.
|
60
|
+
class Minitest::Test
|
61
|
+
def setup
|
62
|
+
# Ensure all remnants of previous test runs, most likely in form of tables,
|
63
|
+
# are removed.
|
64
|
+
ModelFactory.cleanup
|
65
|
+
end
|
45
66
|
|
46
|
-
|
47
|
-
|
48
|
-
|
67
|
+
def teardown
|
68
|
+
ModelFactory.cleanup
|
69
|
+
end
|
49
70
|
|
50
|
-
|
51
|
-
# creation which fails if foreign key constraints are present.
|
52
|
-
class Temping
|
53
|
-
class << self
|
54
|
-
alias_method :old_teardown, :teardown
|
71
|
+
private
|
55
72
|
|
56
|
-
|
57
|
-
|
58
|
-
|
73
|
+
def postgresql?
|
74
|
+
ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
75
|
+
end
|
59
76
|
|
60
|
-
|
61
|
-
|
62
|
-
# returned as valid models which will break tests.
|
63
|
-
ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants).clear
|
64
|
-
end
|
77
|
+
def mysql?
|
78
|
+
ActiveRecord::Base.connection.adapter_name == "Mysql2"
|
65
79
|
end
|
66
|
-
end
|
67
80
|
|
81
|
+
def create_table(*args, &block)
|
82
|
+
ModelFactory.create_table(*args, &block)
|
83
|
+
end
|
68
84
|
|
85
|
+
def create_model(*args, &block)
|
86
|
+
ModelFactory.create_model(*args, &block)
|
87
|
+
end
|
69
88
|
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
# Remove temporary databases created by the current test case.
|
74
|
-
Temping.teardown
|
89
|
+
# Return the detector class under test.
|
90
|
+
def detector_class
|
91
|
+
self.class.name.sub(/Test$/, "").constantize
|
75
92
|
end
|
76
93
|
|
77
|
-
|
94
|
+
# Run the appropriate detector. The detector name is inferred from the test class.
|
95
|
+
def run_detector
|
96
|
+
detector_class.run.first
|
97
|
+
end
|
78
98
|
|
79
|
-
# Run the appropriate task. The task name is inferred from the test class.
|
80
99
|
def run_task
|
81
|
-
|
100
|
+
output = StringIO.new
|
101
|
+
printer = ActiveRecordDoctor::Printers::IOPrinter.new(output)
|
102
|
+
success = ActiveRecordDoctor::Task.new(detector_class, printer).run
|
103
|
+
[success, output.string]
|
82
104
|
end
|
83
105
|
|
84
|
-
|
85
|
-
|
86
|
-
|
106
|
+
def assert_problems(expected_output)
|
107
|
+
success, actual_output = run_task
|
108
|
+
|
109
|
+
assert_equal(expected_output, actual_output)
|
110
|
+
refute(success)
|
111
|
+
end
|
112
|
+
|
113
|
+
def refute_problems
|
114
|
+
success, actual_output = run_task
|
115
|
+
|
116
|
+
assert_equal("", actual_output)
|
117
|
+
assert(success)
|
87
118
|
end
|
88
119
|
end
|
89
120
|
|
@@ -91,7 +122,5 @@ end
|
|
91
122
|
# to be shown.
|
92
123
|
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
|
93
124
|
|
94
|
-
#
|
95
|
-
|
96
|
-
# be a problem with Rails caching those classes aggressively.
|
97
|
-
# Minitest.parallel_executor = Minitest::ForkExecutor.new
|
125
|
+
# Uncomment in case there's test case interference.
|
126
|
+
Minitest.parallel_executor = Minitest::ForkExecutor.new
|