test_data 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -5
  3. data/CHANGELOG.md +17 -1
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE.txt +1 -1
  6. data/README.md +102 -102
  7. data/example/Gemfile.lock +1 -1
  8. data/example/config/credentials.yml.enc +1 -2
  9. data/example/spec/rails_helper.rb +1 -1
  10. data/example/spec/requests/boops_spec.rb +1 -5
  11. data/example/spec/requests/rails_fixtures_override_spec.rb +84 -0
  12. data/example/test/integration/better_mode_switching_demo_test.rb +2 -10
  13. data/example/test/integration/load_rollback_truncate_test.rb +40 -45
  14. data/example/test/integration/mode_switching_demo_test.rb +4 -14
  15. data/example/test/integration/parallel_boops_with_fixtures_test.rb +1 -5
  16. data/example/test/integration/parallel_boops_without_fixtures_test.rb +1 -5
  17. data/example/test/integration/rails_fixtures_double_load_test.rb +2 -2
  18. data/example/test/integration/rails_fixtures_override_test.rb +18 -35
  19. data/example/test/integration/transaction_committing_boops_test.rb +1 -10
  20. data/example/test/test_helper.rb +1 -5
  21. data/lib/generators/test_data/environment_file_generator.rb +4 -0
  22. data/lib/generators/test_data/initializer_generator.rb +1 -7
  23. data/lib/test_data.rb +32 -1
  24. data/lib/test_data/config.rb +1 -11
  25. data/lib/test_data/custom_loaders/abstract_base.rb +25 -0
  26. data/lib/test_data/custom_loaders/rails_fixtures.rb +40 -0
  27. data/lib/test_data/inserts_test_data.rb +25 -0
  28. data/lib/test_data/manager.rb +185 -0
  29. data/lib/test_data/truncates_test_data.rb +31 -0
  30. data/lib/test_data/version.rb +1 -1
  31. data/script/test +1 -0
  32. metadata +8 -3
  33. data/lib/test_data/transactional_data_loader.rb +0 -300
@@ -1,3 +1,3 @@
1
1
  module TestData
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/script/test CHANGED
@@ -51,6 +51,7 @@ bin/rails test test/integration/transaction_committing_boops_test.rb
51
51
 
52
52
  # Run a test that prevents Rails fixtures for preloading and then loads them in a transaction
53
53
  bin/rails test test/integration/rails_fixtures_override_test.rb
54
+ bundle exec rspec spec/requests/rails_fixtures_override_spec.rb
54
55
 
55
56
  # Run a test that forgets to prevent Rails fixtures but then tries to load them in a transaction
56
57
  bin/rails test test/integration/rails_fixtures_double_load_test.rb
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.1.0
4
+ version: 0.2.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-06-27 00:00:00.000000000 Z
11
+ date: 2021-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -93,6 +93,7 @@ files:
93
93
  - example/public/robots.txt
94
94
  - example/spec/rails_helper.rb
95
95
  - example/spec/requests/boops_spec.rb
96
+ - example/spec/requests/rails_fixtures_override_spec.rb
96
97
  - example/spec/spec_helper.rb
97
98
  - example/test/application_system_test_case.rb
98
99
  - example/test/factories.rb
@@ -129,18 +130,22 @@ files:
129
130
  - lib/test_data/configurators/initializer.rb
130
131
  - lib/test_data/configurators/secrets_yaml.rb
131
132
  - lib/test_data/configurators/webpacker_yaml.rb
133
+ - lib/test_data/custom_loaders/abstract_base.rb
134
+ - lib/test_data/custom_loaders/rails_fixtures.rb
132
135
  - lib/test_data/detects_database_emptiness.rb
133
136
  - lib/test_data/dumps_database.rb
134
137
  - lib/test_data/error.rb
135
138
  - lib/test_data/generator_support.rb
139
+ - lib/test_data/inserts_test_data.rb
136
140
  - lib/test_data/installs_configuration.rb
137
141
  - lib/test_data/loads_database_dumps.rb
138
142
  - lib/test_data/log.rb
143
+ - lib/test_data/manager.rb
139
144
  - lib/test_data/railtie.rb
140
145
  - lib/test_data/rake.rb
141
146
  - lib/test_data/save_point.rb
142
147
  - lib/test_data/statistics.rb
143
- - lib/test_data/transactional_data_loader.rb
148
+ - lib/test_data/truncates_test_data.rb
144
149
  - lib/test_data/verifies_configuration.rb
145
150
  - lib/test_data/verifies_dumps_are_loadable.rb
146
151
  - lib/test_data/version.rb
@@ -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