test_data 0.1.0 → 0.3.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/.github/workflows/ruby.yml +1 -5
- data/.standard.yml +2 -0
- data/CHANGELOG.md +38 -1
- data/Gemfile.lock +15 -15
- data/LICENSE.txt +1 -1
- data/README.md +701 -712
- data/example/.gitignore +1 -4
- data/example/Gemfile.lock +1 -1
- data/example/config/application.rb +3 -0
- data/example/config/credentials.yml.enc +1 -2
- data/example/spec/rails_helper.rb +1 -1
- data/example/spec/requests/boops_spec.rb +1 -5
- data/example/spec/requests/rails_fixtures_override_spec.rb +106 -0
- data/example/test/integration/better_mode_switching_demo_test.rb +2 -10
- data/example/test/integration/fixture_load_count_test.rb +82 -0
- data/example/test/integration/load_rollback_truncate_test.rb +40 -45
- data/example/test/integration/mode_switching_demo_test.rb +4 -14
- data/example/test/integration/parallel_boops_with_fixtures_test.rb +1 -5
- data/example/test/integration/parallel_boops_without_fixtures_test.rb +1 -5
- data/example/test/integration/rails_fixtures_double_load_test.rb +2 -2
- data/example/test/integration/rails_fixtures_override_test.rb +18 -35
- data/example/test/integration/test_data_hooks_test.rb +89 -0
- data/example/test/integration/transaction_committing_boops_test.rb +1 -10
- data/example/test/test_helper.rb +1 -5
- data/lib/generators/test_data/environment_file_generator.rb +4 -0
- data/lib/generators/test_data/initializer_generator.rb +19 -13
- data/lib/test_data/config.rb +30 -12
- data/lib/test_data/custom_loaders/abstract_base.rb +25 -0
- data/lib/test_data/custom_loaders/rails_fixtures.rb +45 -0
- data/lib/test_data/detects_database_existence.rb +19 -0
- data/lib/test_data/determines_databases_associated_dump_time.rb +13 -0
- data/lib/test_data/determines_when_sql_dump_was_made.rb +24 -0
- data/lib/test_data/dumps_database.rb +3 -0
- data/lib/test_data/inserts_test_data.rb +25 -0
- data/lib/test_data/manager.rb +187 -0
- data/lib/test_data/railtie.rb +4 -0
- data/lib/test_data/rake.rb +41 -12
- data/lib/test_data/records_dump_metadata.rb +9 -0
- data/lib/test_data/truncates_test_data.rb +31 -0
- data/lib/test_data/version.rb +1 -1
- data/lib/test_data/warns_if_database_is_newer_than_dump.rb +32 -0
- data/lib/test_data/warns_if_dump_is_newer_than_database.rb +36 -0
- data/lib/test_data.rb +43 -1
- data/script/reset_example_app +1 -0
- data/script/test +54 -6
- metadata +17 -3
- data/lib/test_data/transactional_data_loader.rb +0 -300
@@ -0,0 +1,31 @@
|
|
1
|
+
module TestData
|
2
|
+
class TruncatesTestData
|
3
|
+
def initialize
|
4
|
+
@config = TestData.config
|
5
|
+
@statistics = TestData.statistics
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
connection.disable_referential_integrity do
|
10
|
+
connection.execute("TRUNCATE TABLE #{tables_to_truncate.map { |t| connection.quote_table_name(t) }.join(", ")} #{"CASCADE" unless @config.truncate_these_test_data_tables.present?}")
|
11
|
+
end
|
12
|
+
@statistics.count_truncate!
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def tables_to_truncate
|
18
|
+
if @config.truncate_these_test_data_tables.present?
|
19
|
+
@config.truncate_these_test_data_tables
|
20
|
+
else
|
21
|
+
@tables_to_truncate ||= IO.foreach(@config.data_dump_path).grep(/^INSERT INTO/) { |line|
|
22
|
+
line.match(/^INSERT INTO ([^\s]+)/)&.captures&.first
|
23
|
+
}.compact.uniq
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def connection
|
28
|
+
ActiveRecord::Base.connection
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/test_data/version.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
module TestData
|
2
|
+
class WarnsIfDatabaseIsNewerThanDump
|
3
|
+
def initialize
|
4
|
+
@config = TestData.config
|
5
|
+
@determines_when_sql_dump_was_made = DeterminesWhenSqlDumpWasMade.new
|
6
|
+
@determines_databases_associated_dump_time = DeterminesDatabasesAssociatedDumpTime.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
return unless Rails.env.test_data?
|
11
|
+
sql_dumped_at = @determines_when_sql_dump_was_made.call
|
12
|
+
database_dumped_at = @determines_databases_associated_dump_time.call
|
13
|
+
|
14
|
+
if database_dumped_at.present? &&
|
15
|
+
sql_dumped_at.present? &&
|
16
|
+
database_dumped_at > sql_dumped_at
|
17
|
+
TestData.log.warn <<~MSG
|
18
|
+
Your local test_data database '#{@config.database_name}' is associated
|
19
|
+
with a SQL dump that was NEWER than the current dumps located in
|
20
|
+
'#{File.dirname(@config.data_dump_path)}':
|
21
|
+
|
22
|
+
SQL Dump: #{sql_dumped_at.localtime}
|
23
|
+
Database: #{database_dumped_at.localtime}
|
24
|
+
|
25
|
+
If you're not intentionally resetting your local test_data database to an earlier
|
26
|
+
version, you may want to take a closer look before taking any destructive actions.
|
27
|
+
|
28
|
+
MSG
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TestData
|
2
|
+
class WarnsIfDumpIsNewerThanDatabase
|
3
|
+
def initialize
|
4
|
+
@config = TestData.config
|
5
|
+
@determines_when_sql_dump_was_made = DeterminesWhenSqlDumpWasMade.new
|
6
|
+
@determines_databases_associated_dump_time = DeterminesDatabasesAssociatedDumpTime.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
return unless Rails.env.test_data?
|
11
|
+
sql_dumped_at = @determines_when_sql_dump_was_made.call
|
12
|
+
database_dumped_at = @determines_databases_associated_dump_time.call
|
13
|
+
|
14
|
+
if sql_dumped_at.present? &&
|
15
|
+
database_dumped_at.present? &&
|
16
|
+
sql_dumped_at > database_dumped_at
|
17
|
+
TestData.log.warn <<~MSG
|
18
|
+
The SQL dumps in '#{File.dirname(@config.data_dump_path)}' were created
|
19
|
+
after your local test_data database '#{@config.database_name}' was last dumped.
|
20
|
+
|
21
|
+
SQL Dump: #{sql_dumped_at.localtime}
|
22
|
+
Database: #{database_dumped_at.localtime}
|
23
|
+
|
24
|
+
To avoid potential data loss, you may want to consider dropping '#{@config.database_name}'
|
25
|
+
and loading the SQL dumps before making changes to the test data in this database
|
26
|
+
or performing another dump.
|
27
|
+
|
28
|
+
To do this, kill any processes with RAILS_ENV=test_data and then run:
|
29
|
+
|
30
|
+
$ bin/rake test_data:reinitialize
|
31
|
+
|
32
|
+
MSG
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/test_data.rb
CHANGED
@@ -9,22 +9,64 @@ require_relative "test_data/configurators/cable_yaml"
|
|
9
9
|
require_relative "test_data/configurators/database_yaml"
|
10
10
|
require_relative "test_data/configurators/secrets_yaml"
|
11
11
|
require_relative "test_data/configurators/webpacker_yaml"
|
12
|
+
require_relative "test_data/custom_loaders/abstract_base"
|
13
|
+
require_relative "test_data/custom_loaders/rails_fixtures"
|
12
14
|
require_relative "test_data/detects_database_emptiness"
|
15
|
+
require_relative "test_data/detects_database_existence"
|
16
|
+
require_relative "test_data/determines_when_sql_dump_was_made"
|
17
|
+
require_relative "test_data/determines_databases_associated_dump_time"
|
13
18
|
require_relative "test_data/dumps_database"
|
14
19
|
require_relative "test_data/error"
|
20
|
+
require_relative "test_data/inserts_test_data"
|
15
21
|
require_relative "test_data/installs_configuration"
|
16
22
|
require_relative "test_data/loads_database_dumps"
|
17
23
|
require_relative "test_data/log"
|
24
|
+
require_relative "test_data/manager"
|
18
25
|
require_relative "test_data/railtie"
|
26
|
+
require_relative "test_data/records_dump_metadata"
|
19
27
|
require_relative "test_data/save_point"
|
20
28
|
require_relative "test_data/statistics"
|
21
|
-
require_relative "test_data/
|
29
|
+
require_relative "test_data/truncates_test_data"
|
22
30
|
require_relative "test_data/verifies_configuration"
|
23
31
|
require_relative "test_data/verifies_dumps_are_loadable"
|
24
32
|
require_relative "test_data/version"
|
33
|
+
require_relative "test_data/warns_if_dump_is_newer_than_database"
|
34
|
+
require_relative "test_data/warns_if_database_is_newer_than_dump"
|
25
35
|
require_relative "generators/test_data/environment_file_generator"
|
26
36
|
require_relative "generators/test_data/initializer_generator"
|
27
37
|
require_relative "generators/test_data/cable_yaml_generator"
|
28
38
|
require_relative "generators/test_data/database_yaml_generator"
|
29
39
|
require_relative "generators/test_data/secrets_yaml_generator"
|
30
40
|
require_relative "generators/test_data/webpacker_yaml_generator"
|
41
|
+
|
42
|
+
module TestData
|
43
|
+
def self.uninitialize
|
44
|
+
@manager ||= Manager.new
|
45
|
+
@manager.rollback_to_before_data_load
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.uses_test_data
|
50
|
+
@manager ||= Manager.new
|
51
|
+
@manager.load
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.uses_clean_slate
|
56
|
+
@manager ||= Manager.new
|
57
|
+
@manager.truncate
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.uses_rails_fixtures(test_instance)
|
62
|
+
@rails_fixtures_loader ||= CustomLoaders::RailsFixtures.new
|
63
|
+
@manager ||= Manager.new
|
64
|
+
@manager.load_custom_data(@rails_fixtures_loader, test_instance: test_instance)
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.insert_test_data_dump
|
69
|
+
InsertsTestData.new.call
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
data/script/reset_example_app
CHANGED
data/script/test
CHANGED
@@ -28,11 +28,45 @@ bin/rails test test/integration/better_mode_switching_demo_test.rb
|
|
28
28
|
bin/rails test test/integration/parallel_boops_with_fixtures_test.rb
|
29
29
|
bin/rails test test/integration/parallel_boops_without_fixtures_test.rb
|
30
30
|
|
31
|
+
# Verify the out-of-date database warning system works (step 1)
|
32
|
+
mkdir -p tmp/dump-backup-1
|
33
|
+
cp test/support/test_data/* tmp/dump-backup-1
|
34
|
+
|
31
35
|
# Test using the test_data env to interactively create test data
|
32
36
|
RAILS_ENV=test_data rails runner "5.times { Boop.create! }"
|
33
37
|
bin/rake test_data:dump
|
34
38
|
bin/rails test test/integration/updated_boops_test.rb
|
35
39
|
|
40
|
+
# Verify the out-of-date database warning system works (step 2)
|
41
|
+
mkdir -p tmp/dump-backup-2
|
42
|
+
cp test/support/test_data/* tmp/dump-backup-2
|
43
|
+
cp tmp/dump-backup-1/* test/support/test_data
|
44
|
+
set +e
|
45
|
+
result=`yes "n" | bin/rake test_data:reinitialize 2>&1`
|
46
|
+
set -e
|
47
|
+
if ! echo "$result" | grep -q "\[test_data:warn\] Your local test_data database 'example_test_data' is associated"; then
|
48
|
+
echo "Expected to be warned that the SQL dump was older than the database, but there was no warning."
|
49
|
+
exit 1
|
50
|
+
fi
|
51
|
+
TEST_DATA_CONFIRM=true bin/rake test_data:reinitialize
|
52
|
+
cp tmp/dump-backup-2/* test/support/test_data
|
53
|
+
if ! RAILS_ENV=test_data bin/rails runner "" 2>&1 | grep -q "\[test_data:warn\] The SQL dumps in 'test/support/test_data' were created"; then
|
54
|
+
echo "Expected to be warned that the SQL dump was newer than the database, but there was no warning."
|
55
|
+
exit 1
|
56
|
+
fi
|
57
|
+
set +e
|
58
|
+
result=`yes "n" | bin/rake test_data:reinitialize 2>&1`
|
59
|
+
set -e
|
60
|
+
if echo "$result" | grep -q "\[test_data:warn\] Your local test_data database 'example_test_data' is associated"; then
|
61
|
+
echo "Expected NOT to be warned that the SQL dump was older than the database, but a warning was printed."
|
62
|
+
exit 1
|
63
|
+
fi
|
64
|
+
TEST_DATA_CONFIRM=true bin/rake test_data:reinitialize
|
65
|
+
if RAILS_ENV=test_data bin/rails runner "" 2>&1 | grep -q "\[test_data:warn\]"; then
|
66
|
+
echo "Expected NOT to be warned that the SQL dump was newer than the database, but a warning was printed."
|
67
|
+
exit 1
|
68
|
+
fi
|
69
|
+
|
36
70
|
# Test a migration being added and run and an out-of-date dump being loaded
|
37
71
|
cp ../test/fixtures/20210418220133_add_beep_to_boops.rb db/migrate
|
38
72
|
cp ../test/fixtures/20210624180810_create_pants.rb db/migrate
|
@@ -51,6 +85,8 @@ bin/rails test test/integration/transaction_committing_boops_test.rb
|
|
51
85
|
|
52
86
|
# Run a test that prevents Rails fixtures for preloading and then loads them in a transaction
|
53
87
|
bin/rails test test/integration/rails_fixtures_override_test.rb
|
88
|
+
bundle exec rspec spec/requests/rails_fixtures_override_spec.rb
|
89
|
+
bin/rails test test/integration/fixture_load_count_test.rb
|
54
90
|
|
55
91
|
# Run a test that forgets to prevent Rails fixtures but then tries to load them in a transaction
|
56
92
|
bin/rails test test/integration/rails_fixtures_double_load_test.rb
|
@@ -82,18 +118,30 @@ cp ../test/fixtures/20210423190737_add_foreign_keys.rb db/migrate/
|
|
82
118
|
cp ../test/fixtures/boop_with_other_boops.rb app/models/boop.rb
|
83
119
|
RAILS_ENV=test_data bin/rake db:migrate
|
84
120
|
bin/rake test_data:dump
|
85
|
-
bin/rake db:migrate
|
86
|
-
bin/rake db:test:prepare
|
121
|
+
bin/rake db:migrate db:test:prepare
|
87
122
|
bin/rails test test/integration/boops_that_boop_boops_test.rb
|
88
123
|
|
89
124
|
# Make sure it loads cleanly again
|
90
125
|
bin/rake test_data:drop_database
|
91
|
-
|
92
|
-
echo "Running test_data:load after adding FK constraints led to errors"
|
93
|
-
exit 1
|
94
|
-
fi
|
126
|
+
bin/rake test_data:load
|
95
127
|
bin/rails test test/integration/boops_that_boop_boops_test.rb
|
96
128
|
|
129
|
+
# Test all the after hooks!
|
130
|
+
cp ../test/fixtures/20210729130542_add_materialized_meta_boop_view.rb db/migrate/
|
131
|
+
cp ../test/fixtures/meta_boop.rb app/models/meta_boop.rb
|
132
|
+
# Gsub config file to switch to structure.sql b/c materialized view
|
133
|
+
ruby -e '
|
134
|
+
path = "config/application.rb"
|
135
|
+
IO.write(path, File.open(path) { |f|
|
136
|
+
f.read.gsub("# config.active_record.schema_format = :sql", "config.active_record.schema_format = :sql")
|
137
|
+
})
|
138
|
+
'
|
139
|
+
rm db/schema.rb
|
140
|
+
bin/rake db:migrate db:test:prepare
|
141
|
+
RAILS_ENV=test_data bin/rake db:migrate
|
142
|
+
bin/rake test_data:dump
|
143
|
+
bin/rails test test/integration/test_data_hooks_test.rb
|
144
|
+
|
97
145
|
# Cleanup
|
98
146
|
cd ..
|
99
147
|
./script/reset_example_app
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test_data
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -33,6 +33,7 @@ extra_rdoc_files: []
|
|
33
33
|
files:
|
34
34
|
- ".github/workflows/ruby.yml"
|
35
35
|
- ".gitignore"
|
36
|
+
- ".standard.yml"
|
36
37
|
- CHANGELOG.md
|
37
38
|
- Gemfile
|
38
39
|
- Gemfile.lock
|
@@ -93,6 +94,7 @@ files:
|
|
93
94
|
- example/public/robots.txt
|
94
95
|
- example/spec/rails_helper.rb
|
95
96
|
- example/spec/requests/boops_spec.rb
|
97
|
+
- example/spec/requests/rails_fixtures_override_spec.rb
|
96
98
|
- example/spec/spec_helper.rb
|
97
99
|
- example/test/application_system_test_case.rb
|
98
100
|
- example/test/factories.rb
|
@@ -101,6 +103,7 @@ files:
|
|
101
103
|
- example/test/integration/better_mode_switching_demo_test.rb
|
102
104
|
- example/test/integration/boops_that_boop_boops_test.rb
|
103
105
|
- example/test/integration/dont_dump_tables_test.rb
|
106
|
+
- example/test/integration/fixture_load_count_test.rb
|
104
107
|
- example/test/integration/load_rollback_truncate_test.rb
|
105
108
|
- example/test/integration/migrated_boops_test.rb
|
106
109
|
- example/test/integration/mode_switching_demo_test.rb
|
@@ -108,6 +111,7 @@ files:
|
|
108
111
|
- example/test/integration/parallel_boops_without_fixtures_test.rb
|
109
112
|
- example/test/integration/rails_fixtures_double_load_test.rb
|
110
113
|
- example/test/integration/rails_fixtures_override_test.rb
|
114
|
+
- example/test/integration/test_data_hooks_test.rb
|
111
115
|
- example/test/integration/transaction_committing_boops_test.rb
|
112
116
|
- example/test/integration/updated_boops_test.rb
|
113
117
|
- example/test/test_helper.rb
|
@@ -129,21 +133,31 @@ files:
|
|
129
133
|
- lib/test_data/configurators/initializer.rb
|
130
134
|
- lib/test_data/configurators/secrets_yaml.rb
|
131
135
|
- lib/test_data/configurators/webpacker_yaml.rb
|
136
|
+
- lib/test_data/custom_loaders/abstract_base.rb
|
137
|
+
- lib/test_data/custom_loaders/rails_fixtures.rb
|
132
138
|
- lib/test_data/detects_database_emptiness.rb
|
139
|
+
- lib/test_data/detects_database_existence.rb
|
140
|
+
- lib/test_data/determines_databases_associated_dump_time.rb
|
141
|
+
- lib/test_data/determines_when_sql_dump_was_made.rb
|
133
142
|
- lib/test_data/dumps_database.rb
|
134
143
|
- lib/test_data/error.rb
|
135
144
|
- lib/test_data/generator_support.rb
|
145
|
+
- lib/test_data/inserts_test_data.rb
|
136
146
|
- lib/test_data/installs_configuration.rb
|
137
147
|
- lib/test_data/loads_database_dumps.rb
|
138
148
|
- lib/test_data/log.rb
|
149
|
+
- lib/test_data/manager.rb
|
139
150
|
- lib/test_data/railtie.rb
|
140
151
|
- lib/test_data/rake.rb
|
152
|
+
- lib/test_data/records_dump_metadata.rb
|
141
153
|
- lib/test_data/save_point.rb
|
142
154
|
- lib/test_data/statistics.rb
|
143
|
-
- lib/test_data/
|
155
|
+
- lib/test_data/truncates_test_data.rb
|
144
156
|
- lib/test_data/verifies_configuration.rb
|
145
157
|
- lib/test_data/verifies_dumps_are_loadable.rb
|
146
158
|
- lib/test_data/version.rb
|
159
|
+
- lib/test_data/warns_if_database_is_newer_than_dump.rb
|
160
|
+
- lib/test_data/warns_if_dump_is_newer_than_database.rb
|
147
161
|
- script/reset_example_app
|
148
162
|
- script/test
|
149
163
|
- test_data.gemspec
|
@@ -1,300 +0,0 @@
|
|
1
|
-
module TestData
|
2
|
-
def self.load
|
3
|
-
@data_loader ||= TestData.config.use_transactional_data_loader ? TransactionalDataLoader.new : CommittingDataLoader.new
|
4
|
-
@data_loader.load
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.ensure_we_dont_mix_transactional_and_non_transactional_data_loaders!(use_transactions)
|
8
|
-
unless @data_loader.nil? || use_transactions == @data_loader.transactional?
|
9
|
-
raise Error.new("There is already a #{@data_loader.transactional? ? "transactional" : "non-transactional"} data loader in use, and test_data does not support mixing both types of loaders in a single process.")
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.rollback(save_point_name = :after_data_load)
|
14
|
-
unless TestData.config.use_transactional_data_loader
|
15
|
-
raise Error.new("TestData.rollback requires config.use_transactional_data_loader = true")
|
16
|
-
end
|
17
|
-
@data_loader ||= TransactionalDataLoader.new
|
18
|
-
case save_point_name
|
19
|
-
when :before_data_load
|
20
|
-
@data_loader.rollback_to_before_data_load
|
21
|
-
when :after_data_load
|
22
|
-
@data_loader.rollback_to_after_data_load
|
23
|
-
when :after_data_truncate
|
24
|
-
@data_loader.rollback_to_after_data_truncate
|
25
|
-
when :after_load_rails_fixtures
|
26
|
-
@data_loader.rollback_to_after_load_rails_fixtures
|
27
|
-
else
|
28
|
-
raise Error.new("No known save point named '#{save_point_name}'. Valid values are: [:before_data_load, :after_data_load, :after_data_truncate, :after_load_rails_fixtures]")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.truncate
|
33
|
-
@data_loader ||= TestData.config.use_transactional_data_loader ? TransactionalDataLoader.new : CommittingDataLoader.new
|
34
|
-
@data_loader.truncate
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.load_rails_fixtures(test_instance)
|
38
|
-
if !test_instance.respond_to?(:setup_fixtures)
|
39
|
-
raise Error.new("'TestData.load_rails_fixtures' must be passed a test instance that has had ActiveRecord::TestFixtures mixed-in (e.g. `TestData.load_rails_fixtures(self)` in an ActiveSupport::TestCase `setup` block), but the provided argument does not respond to 'setup_fixtures'")
|
40
|
-
elsif !test_instance.respond_to?(:__test_data_gem_setup_fixtures)
|
41
|
-
raise Error.new("'TestData.load_rails_fixtures' depends on Rails' default fixture-loading behavior being disabled by calling 'TestData.prevent_rails_fixtures_from_loading_automatically!' as early as possible (e.g. near the top of your test_helper.rb), but it looks like it was never called.")
|
42
|
-
elsif !TestData.config.use_transactional_data_loader
|
43
|
-
raise Error.new("'TestData.load_rails_fixtures' requires config.use_transactional_data_loader = true")
|
44
|
-
end
|
45
|
-
@data_loader ||= TransactionalDataLoader.new
|
46
|
-
@data_loader.load_rails_fixtures(test_instance)
|
47
|
-
end
|
48
|
-
|
49
|
-
class AbstractDataLoader
|
50
|
-
def initialize
|
51
|
-
@config = TestData.config
|
52
|
-
@statistics = TestData.statistics
|
53
|
-
end
|
54
|
-
|
55
|
-
protected
|
56
|
-
|
57
|
-
def execute_data_load
|
58
|
-
search_path = execute("show search_path").first["search_path"]
|
59
|
-
connection.disable_referential_integrity do
|
60
|
-
execute(File.read(@config.data_dump_full_path))
|
61
|
-
end
|
62
|
-
execute <<~SQL
|
63
|
-
select pg_catalog.set_config('search_path', '#{search_path}', false)
|
64
|
-
SQL
|
65
|
-
@statistics.count_load!
|
66
|
-
end
|
67
|
-
|
68
|
-
def execute_data_truncate
|
69
|
-
connection.disable_referential_integrity do
|
70
|
-
execute("TRUNCATE TABLE #{tables_to_truncate.map { |t| connection.quote_table_name(t) }.join(", ")} #{"CASCADE" unless @config.truncate_these_test_data_tables.present?}")
|
71
|
-
end
|
72
|
-
@statistics.count_truncate!
|
73
|
-
end
|
74
|
-
|
75
|
-
def execute_load_rails_fixtures(test_instance)
|
76
|
-
test_instance.pre_loaded_fixtures = false
|
77
|
-
test_instance.use_transactional_tests = false
|
78
|
-
test_instance.__test_data_gem_setup_fixtures
|
79
|
-
@already_loaded_rails_fixtures[test_instance.class] = test_instance.instance_variable_get(:@loaded_fixtures)
|
80
|
-
@statistics.count_load_rails_fixtures!
|
81
|
-
end
|
82
|
-
|
83
|
-
def tables_to_truncate
|
84
|
-
if @config.truncate_these_test_data_tables.present?
|
85
|
-
@config.truncate_these_test_data_tables
|
86
|
-
else
|
87
|
-
@tables_to_truncate ||= IO.foreach(@config.data_dump_path).grep(/^INSERT INTO/) { |line|
|
88
|
-
line.match(/^INSERT INTO ([^\s]+)/)&.captures&.first
|
89
|
-
}.compact.uniq
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def connection
|
94
|
-
ActiveRecord::Base.connection
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def execute(sql)
|
100
|
-
connection.execute(sql)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
class CommittingDataLoader < AbstractDataLoader
|
105
|
-
def transactional?
|
106
|
-
false
|
107
|
-
end
|
108
|
-
|
109
|
-
def load
|
110
|
-
execute_data_load
|
111
|
-
end
|
112
|
-
|
113
|
-
def truncate
|
114
|
-
execute_data_truncate
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
class TransactionalDataLoader < AbstractDataLoader
|
119
|
-
def initialize
|
120
|
-
super
|
121
|
-
@save_points = []
|
122
|
-
@already_loaded_rails_fixtures = {}
|
123
|
-
end
|
124
|
-
|
125
|
-
def transactional?
|
126
|
-
true
|
127
|
-
end
|
128
|
-
|
129
|
-
def load
|
130
|
-
ensure_after_load_save_point_is_active_if_data_is_loaded!
|
131
|
-
return rollback_to_after_data_load if save_point_active?(:after_data_load)
|
132
|
-
|
133
|
-
create_save_point(:before_data_load)
|
134
|
-
execute_data_load
|
135
|
-
record_ar_internal_metadata_that_test_data_is_loaded
|
136
|
-
create_save_point(:after_data_load)
|
137
|
-
end
|
138
|
-
|
139
|
-
def rollback_to_before_data_load
|
140
|
-
if save_point_active?(:before_data_load)
|
141
|
-
rollback_save_point(:before_data_load)
|
142
|
-
# No need to recreate the save point -- TestData.load will if called
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def rollback_to_after_data_load
|
147
|
-
if save_point_active?(:after_data_load)
|
148
|
-
rollback_save_point(:after_data_load)
|
149
|
-
create_save_point(:after_data_load)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def rollback_to_after_data_truncate
|
154
|
-
if save_point_active?(:after_data_truncate)
|
155
|
-
rollback_save_point(:after_data_truncate)
|
156
|
-
create_save_point(:after_data_truncate)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def rollback_to_after_load_rails_fixtures
|
161
|
-
if save_point_active?(:after_load_rails_fixtures)
|
162
|
-
rollback_save_point(:after_load_rails_fixtures)
|
163
|
-
create_save_point(:after_load_rails_fixtures)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def truncate
|
168
|
-
ensure_after_load_save_point_is_active_if_data_is_loaded!
|
169
|
-
ensure_after_truncate_save_point_is_active_if_data_is_truncated!
|
170
|
-
return rollback_to_after_data_truncate if save_point_active?(:after_data_truncate)
|
171
|
-
|
172
|
-
if save_point_active?(:after_data_load)
|
173
|
-
# If a test that uses the test data runs before a test that starts by
|
174
|
-
# calling truncate, tables in the database that would NOT be truncated
|
175
|
-
# may have been changed. To avoid this category of test pollution, start
|
176
|
-
# the truncation by rolling back to the known clean point
|
177
|
-
rollback_to_after_data_load
|
178
|
-
else
|
179
|
-
# Seems silly loading data when the user asked us to truncate, but
|
180
|
-
# it's important that the state of the transaction stack matches the
|
181
|
-
# mental model we advertise, because any _other_ test in their suite
|
182
|
-
# should expect that the existence of :after_data_truncate save point
|
183
|
-
# implies that it's safe to rollback to the :after_data_load save
|
184
|
-
# point; since tests run in random order, it's likely to happen
|
185
|
-
TestData.log.debug("TestData.truncate was called, but data was not loaded. Loading data before truncate to preserve the transaction save point ordering")
|
186
|
-
load
|
187
|
-
end
|
188
|
-
|
189
|
-
execute_data_truncate
|
190
|
-
record_ar_internal_metadata_that_test_data_is_truncated
|
191
|
-
create_save_point(:after_data_truncate)
|
192
|
-
end
|
193
|
-
|
194
|
-
# logic is beat-for-beat the same as #truncate just one step deeper down
|
195
|
-
# this rabbit hole…
|
196
|
-
def load_rails_fixtures(test_instance)
|
197
|
-
ensure_after_load_save_point_is_active_if_data_is_loaded!
|
198
|
-
ensure_after_truncate_save_point_is_active_if_data_is_truncated!
|
199
|
-
ensure_after_load_rails_fixtures_save_point_is_active_if_fixtures_are_loaded!
|
200
|
-
reset_rails_fixture_caches(test_instance)
|
201
|
-
return rollback_to_after_load_rails_fixtures if save_point_active?(:after_load_rails_fixtures) && @already_loaded_rails_fixtures[test_instance.class].present?
|
202
|
-
|
203
|
-
if save_point_active?(:after_data_truncate)
|
204
|
-
rollback_to_after_data_truncate
|
205
|
-
else
|
206
|
-
truncate
|
207
|
-
end
|
208
|
-
|
209
|
-
execute_load_rails_fixtures(test_instance)
|
210
|
-
record_ar_internal_metadata_that_rails_fixtures_are_loaded
|
211
|
-
create_save_point(:after_load_rails_fixtures)
|
212
|
-
end
|
213
|
-
|
214
|
-
private
|
215
|
-
|
216
|
-
def ensure_after_load_save_point_is_active_if_data_is_loaded!
|
217
|
-
if !save_point_active?(:after_data_load) && ar_internal_metadata_shows_test_data_is_loaded?
|
218
|
-
TestData.log.debug "Test data appears to be loaded, but the :after_data_load save point was rolled back (and not by this gem). Recreating the :after_data_load save point"
|
219
|
-
create_save_point(:after_data_load)
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
def record_ar_internal_metadata_that_test_data_is_loaded
|
224
|
-
if ar_internal_metadata_shows_test_data_is_loaded?
|
225
|
-
TestData.log.warn "Attempted to record that test data is loaded in ar_internal_metadata, but record already existed. Perhaps a previous test run committed your test data?"
|
226
|
-
else
|
227
|
-
ActiveRecord::InternalMetadata.create!(key: "test_data:loaded", value: "true")
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def ar_internal_metadata_shows_test_data_is_loaded?
|
232
|
-
ActiveRecord::InternalMetadata.find_by(key: "test_data:loaded")&.value == "true"
|
233
|
-
end
|
234
|
-
|
235
|
-
def ensure_after_truncate_save_point_is_active_if_data_is_truncated!
|
236
|
-
if !save_point_active?(:after_data_truncate) && ar_internal_metadata_shows_test_data_is_truncated?
|
237
|
-
TestData.log.debug "Test data appears to be loaded, but the :after_data_truncate save point was rolled back (and not by this gem). Recreating the :after_data_truncate save point"
|
238
|
-
create_save_point(:after_data_truncate)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def record_ar_internal_metadata_that_test_data_is_truncated
|
243
|
-
if ar_internal_metadata_shows_test_data_is_truncated?
|
244
|
-
TestData.log.warn "Attempted to record that test data is truncated in ar_internal_metadata, but record already existed. Perhaps a previous test run committed the truncation of your test data?"
|
245
|
-
else
|
246
|
-
ActiveRecord::InternalMetadata.create!(key: "test_data:truncated", value: "true")
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
def ar_internal_metadata_shows_test_data_is_truncated?
|
251
|
-
ActiveRecord::InternalMetadata.find_by(key: "test_data:truncated")&.value == "true"
|
252
|
-
end
|
253
|
-
|
254
|
-
def ensure_after_load_rails_fixtures_save_point_is_active_if_fixtures_are_loaded!
|
255
|
-
if !save_point_active?(:after_load_rails_fixtures) && ar_internal_metadata_shows_rails_fixtures_are_loaded?
|
256
|
-
TestData.log.debug "Rails Fixtures appears to have been loaded by test_data, but the :after_load_rails_fixtures save point was rolled back (and not by this gem). Recreating the :after_load_rails_fixtures save point"
|
257
|
-
create_save_point(:after_load_rails_fixtures)
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def record_ar_internal_metadata_that_rails_fixtures_are_loaded
|
262
|
-
if ar_internal_metadata_shows_rails_fixtures_are_loaded?
|
263
|
-
TestData.log.warn "Attempted to record that test_data had loaded your Rails fixtures in ar_internal_metadata, but record already existed. Perhaps a previous test run committed the loading of your Rails fixtures?"
|
264
|
-
else
|
265
|
-
ActiveRecord::InternalMetadata.create!(key: "test_data:rails_fixtures_loaded", value: "true")
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def ar_internal_metadata_shows_rails_fixtures_are_loaded?
|
270
|
-
ActiveRecord::InternalMetadata.find_by(key: "test_data:rails_fixtures_loaded")&.value == "true"
|
271
|
-
end
|
272
|
-
|
273
|
-
def reset_rails_fixture_caches(test_instance)
|
274
|
-
ActiveRecord::FixtureSet.reset_cache
|
275
|
-
test_instance.instance_variable_set(:@loaded_fixtures, @already_loaded_rails_fixtures[test_instance.class])
|
276
|
-
test_instance.instance_variable_set(:@fixture_cache, {})
|
277
|
-
end
|
278
|
-
|
279
|
-
def save_point_active?(name)
|
280
|
-
purge_closed_save_points!
|
281
|
-
!!@save_points.find { |sp| sp.name == name }&.active?
|
282
|
-
end
|
283
|
-
|
284
|
-
def create_save_point(name)
|
285
|
-
raise Error.new("Could not create test_data savepoint '#{name}', because it was already active!") if save_point_active?(name)
|
286
|
-
@save_points << SavePoint.new(name)
|
287
|
-
end
|
288
|
-
|
289
|
-
def rollback_save_point(name)
|
290
|
-
@save_points.find { |sp| sp.name == name }&.rollback!
|
291
|
-
purge_closed_save_points!
|
292
|
-
end
|
293
|
-
|
294
|
-
def purge_closed_save_points!
|
295
|
-
@save_points = @save_points.select { |save_point|
|
296
|
-
save_point.active?
|
297
|
-
}
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|