test_data 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: