test_data 0.0.2 → 0.1.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile.lock +15 -15
  4. data/LICENSE.txt +1 -6
  5. data/README.md +652 -276
  6. data/example/Gemfile.lock +73 -73
  7. data/example/test/integration/better_mode_switching_demo_test.rb +4 -0
  8. data/example/test/integration/parallel_boops_with_fixtures_test.rb +2 -2
  9. data/example/test/integration/parallel_boops_without_fixtures_test.rb +2 -2
  10. data/example/test/integration/rails_fixtures_double_load_test.rb +10 -0
  11. data/example/test/integration/rails_fixtures_override_test.rb +127 -0
  12. data/example/test/integration/transaction_committing_boops_test.rb +14 -3
  13. data/example/test/test_helper.rb +2 -2
  14. data/lib/generators/test_data/cable_yaml_generator.rb +18 -0
  15. data/lib/generators/test_data/database_yaml_generator.rb +2 -3
  16. data/lib/generators/test_data/environment_file_generator.rb +3 -0
  17. data/lib/generators/test_data/initializer_generator.rb +7 -0
  18. data/lib/generators/test_data/secrets_yaml_generator.rb +19 -0
  19. data/lib/generators/test_data/webpacker_yaml_generator.rb +3 -2
  20. data/lib/test_data.rb +5 -0
  21. data/lib/test_data/active_record_ext.rb +11 -0
  22. data/lib/test_data/config.rb +14 -2
  23. data/lib/test_data/configurators.rb +2 -0
  24. data/lib/test_data/configurators/cable_yaml.rb +25 -0
  25. data/lib/test_data/configurators/environment_file.rb +3 -2
  26. data/lib/test_data/configurators/initializer.rb +3 -2
  27. data/lib/test_data/configurators/secrets_yaml.rb +25 -0
  28. data/lib/test_data/configurators/webpacker_yaml.rb +4 -3
  29. data/lib/test_data/dumps_database.rb +24 -1
  30. data/lib/test_data/generator_support.rb +3 -0
  31. data/lib/test_data/loads_database_dumps.rb +1 -1
  32. data/lib/test_data/log.rb +19 -1
  33. data/lib/test_data/rake.rb +16 -6
  34. data/lib/test_data/statistics.rb +6 -1
  35. data/lib/test_data/transactional_data_loader.rb +156 -46
  36. data/lib/test_data/version.rb +1 -1
  37. data/script/test +11 -0
  38. data/test_data.gemspec +1 -1
  39. metadata +11 -3
@@ -4,7 +4,7 @@ module TestData
4
4
  end
5
5
 
6
6
  class Statistics
7
- attr_reader :load_count, :truncate_count
7
+ attr_reader :load_count, :truncate_count, :load_rails_fixtures_count
8
8
 
9
9
  def initialize
10
10
  reset
@@ -18,9 +18,14 @@ module TestData
18
18
  @truncate_count += 1
19
19
  end
20
20
 
21
+ def count_load_rails_fixtures!
22
+ @load_rails_fixtures_count += 1
23
+ end
24
+
21
25
  def reset
22
26
  @load_count = 0
23
27
  @truncate_count = 0
28
+ @load_rails_fixtures_count = 0
24
29
  end
25
30
  end
26
31
  end
@@ -1,37 +1,132 @@
1
1
  module TestData
2
- def self.load(transactions: true)
3
- @transactional_data_loader ||= TransactionalDataLoader.new
4
- @transactional_data_loader.load(transactions: transactions)
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
5
11
  end
6
12
 
7
13
  def self.rollback(save_point_name = :after_data_load)
8
- @transactional_data_loader ||= TransactionalDataLoader.new
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
9
18
  case save_point_name
10
19
  when :before_data_load
11
- @transactional_data_loader.rollback_to_before_data_load
20
+ @data_loader.rollback_to_before_data_load
12
21
  when :after_data_load
13
- @transactional_data_loader.rollback_to_after_data_load
22
+ @data_loader.rollback_to_after_data_load
14
23
  when :after_data_truncate
15
- @transactional_data_loader.rollback_to_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
16
27
  else
17
- raise Error.new("No known save point named '#{save_point_name}'. Valid values are: [:before_data_load, :after_data_load, :after_data_truncate]")
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]")
18
29
  end
19
30
  end
20
31
 
21
- def self.truncate(transactions: true)
22
- @transactional_data_loader ||= TransactionalDataLoader.new
23
- @transactional_data_loader.truncate(transactions: transactions)
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)
24
47
  end
25
48
 
26
- class TransactionalDataLoader
49
+ class AbstractDataLoader
27
50
  def initialize
28
51
  @config = TestData.config
29
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
30
121
  @save_points = []
122
+ @already_loaded_rails_fixtures = {}
123
+ end
124
+
125
+ def transactional?
126
+ true
31
127
  end
32
128
 
33
- def load(transactions: true)
34
- return execute_data_load unless transactions
129
+ def load
35
130
  ensure_after_load_save_point_is_active_if_data_is_loaded!
36
131
  return rollback_to_after_data_load if save_point_active?(:after_data_load)
37
132
 
@@ -62,8 +157,14 @@ module TestData
62
157
  end
63
158
  end
64
159
 
65
- def truncate(transactions: true)
66
- return execute_data_truncate unless transactions
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
67
168
  ensure_after_load_save_point_is_active_if_data_is_loaded!
68
169
  ensure_after_truncate_save_point_is_active_if_data_is_truncated!
69
170
  return rollback_to_after_data_truncate if save_point_active?(:after_data_truncate)
@@ -81,8 +182,8 @@ module TestData
81
182
  # should expect that the existence of :after_data_truncate save point
82
183
  # implies that it's safe to rollback to the :after_data_load save
83
184
  # point; since tests run in random order, it's likely to happen
84
- TestData.log.debug("TestData.truncate was called, but data was not loaded. Loading data before truncate to preserve the documents transaction save point ordering")
85
- load(transactions: true)
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
86
187
  end
87
188
 
88
189
  execute_data_truncate
@@ -90,6 +191,26 @@ module TestData
90
191
  create_save_point(:after_data_truncate)
91
192
  end
92
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
+
93
214
  private
94
215
 
95
216
  def ensure_after_load_save_point_is_active_if_data_is_loaded!
@@ -130,32 +251,29 @@ module TestData
130
251
  ActiveRecord::InternalMetadata.find_by(key: "test_data:truncated")&.value == "true"
131
252
  end
132
253
 
133
- def execute_data_load
134
- search_path = execute("show search_path").first["search_path"]
135
- connection.disable_referential_integrity do
136
- execute(File.read(@config.data_dump_full_path))
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)
137
258
  end
138
- execute <<~SQL
139
- select pg_catalog.set_config('search_path', '#{search_path}', false)
140
- SQL
141
- @statistics.count_load!
142
259
  end
143
260
 
144
- def execute_data_truncate
145
- connection.disable_referential_integrity do
146
- execute("TRUNCATE TABLE #{tables_to_truncate.map { |t| connection.quote_table_name(t) }.join(", ")}")
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")
147
266
  end
148
- @statistics.count_truncate!
149
267
  end
150
268
 
151
- def tables_to_truncate
152
- if @config.truncate_these_test_data_tables.present?
153
- @config.truncate_these_test_data_tables
154
- else
155
- @tables_to_truncate ||= IO.foreach(@config.data_dump_path).grep(/^INSERT INTO/) { |line|
156
- line.match(/^INSERT INTO ([^\s]+)/)&.captures&.first
157
- }.compact.uniq
158
- end
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, {})
159
277
  end
160
278
 
161
279
  def save_point_active?(name)
@@ -178,13 +296,5 @@ module TestData
178
296
  save_point.active?
179
297
  }
180
298
  end
181
-
182
- def execute(sql)
183
- connection.execute(sql)
184
- end
185
-
186
- def connection
187
- ActiveRecord::Base.connection
188
- end
189
299
  end
190
300
  end
@@ -1,3 +1,3 @@
1
1
  module TestData
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/script/test CHANGED
@@ -35,6 +35,9 @@ bin/rails test test/integration/updated_boops_test.rb
35
35
 
36
36
  # Test a migration being added and run and an out-of-date dump being loaded
37
37
  cp ../test/fixtures/20210418220133_add_beep_to_boops.rb db/migrate
38
+ cp ../test/fixtures/20210624180810_create_pants.rb db/migrate
39
+ cp ../test/fixtures/pant.rb app/models
40
+ cp ../test/fixtures/pants.yml test/fixtures
38
41
  bin/rake db:migrate
39
42
  bin/rake db:test:prepare
40
43
  bin/rake test_data:drop_database
@@ -46,6 +49,12 @@ bin/rails test test/integration/migrated_boops_test.rb
46
49
  # Run a test that commits test data thru to the database
47
50
  bin/rails test test/integration/transaction_committing_boops_test.rb
48
51
 
52
+ # Run a test that prevents Rails fixtures for preloading and then loads them in a transaction
53
+ bin/rails test test/integration/rails_fixtures_override_test.rb
54
+
55
+ # Run a test that forgets to prevent Rails fixtures but then tries to load them in a transaction
56
+ bin/rails test test/integration/rails_fixtures_double_load_test.rb
57
+
49
58
  # Add a second migration, this time without wiping the test_data db and with a table we want to ignore
50
59
  cp ../test/fixtures/20210423114916_add_table_we_want_to_ignore.rb db/migrate
51
60
  cp ../test/fixtures/chatty_audit_log.rb app/models
@@ -88,3 +97,5 @@ bin/rails test test/integration/boops_that_boop_boops_test.rb
88
97
  # Cleanup
89
98
  cd ..
90
99
  ./script/reset_example_app
100
+
101
+ echo "You win!"
data/test_data.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
 
13
13
  spec.metadata["homepage_uri"] = spec.homepage
14
14
  spec.metadata["source_code_uri"] = spec.homepage
15
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
15
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
16
16
 
17
17
  # Specify which files should be added to the gem when it is released.
18
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
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.0.2
4
+ version: 0.1.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-04-30 00:00:00.000000000 Z
11
+ date: 2021-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -106,25 +106,33 @@ files:
106
106
  - example/test/integration/mode_switching_demo_test.rb
107
107
  - example/test/integration/parallel_boops_with_fixtures_test.rb
108
108
  - example/test/integration/parallel_boops_without_fixtures_test.rb
109
+ - example/test/integration/rails_fixtures_double_load_test.rb
110
+ - example/test/integration/rails_fixtures_override_test.rb
109
111
  - example/test/integration/transaction_committing_boops_test.rb
110
112
  - example/test/integration/updated_boops_test.rb
111
113
  - example/test/test_helper.rb
114
+ - lib/generators/test_data/cable_yaml_generator.rb
112
115
  - lib/generators/test_data/database_yaml_generator.rb
113
116
  - lib/generators/test_data/environment_file_generator.rb
114
117
  - lib/generators/test_data/initializer_generator.rb
118
+ - lib/generators/test_data/secrets_yaml_generator.rb
115
119
  - lib/generators/test_data/webpacker_yaml_generator.rb
116
120
  - lib/test_data.rb
121
+ - lib/test_data/active_record_ext.rb
117
122
  - lib/test_data/active_support_ext.rb
118
123
  - lib/test_data/config.rb
119
124
  - lib/test_data/configuration_verification.rb
120
125
  - lib/test_data/configurators.rb
126
+ - lib/test_data/configurators/cable_yaml.rb
121
127
  - lib/test_data/configurators/database_yaml.rb
122
128
  - lib/test_data/configurators/environment_file.rb
123
129
  - lib/test_data/configurators/initializer.rb
130
+ - lib/test_data/configurators/secrets_yaml.rb
124
131
  - lib/test_data/configurators/webpacker_yaml.rb
125
132
  - lib/test_data/detects_database_emptiness.rb
126
133
  - lib/test_data/dumps_database.rb
127
134
  - lib/test_data/error.rb
135
+ - lib/test_data/generator_support.rb
128
136
  - lib/test_data/installs_configuration.rb
129
137
  - lib/test_data/loads_database_dumps.rb
130
138
  - lib/test_data/log.rb
@@ -144,7 +152,7 @@ licenses: []
144
152
  metadata:
145
153
  homepage_uri: https://github.com/testdouble/test_data
146
154
  source_code_uri: https://github.com/testdouble/test_data
147
- changelog_uri: https://github.com/testdouble/test_data/blob/master/CHANGELOG.md
155
+ changelog_uri: https://github.com/testdouble/test_data/blob/main/CHANGELOG.md
148
156
  post_install_message:
149
157
  rdoc_options: []
150
158
  require_paths: