test_data 0.0.1 → 0.2.1

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +41 -0
  3. data/.standard.yml +2 -0
  4. data/CHANGELOG.md +43 -0
  5. data/Gemfile.lock +17 -15
  6. data/LICENSE.txt +1 -6
  7. data/README.md +1232 -17
  8. data/example/.gitignore +1 -4
  9. data/example/Gemfile +3 -0
  10. data/example/Gemfile.lock +100 -71
  11. data/example/README.md +2 -22
  12. data/example/config/application.rb +3 -0
  13. data/example/config/credentials.yml.enc +1 -1
  14. data/example/config/database.yml +2 -0
  15. data/example/spec/rails_helper.rb +64 -0
  16. data/example/spec/requests/boops_spec.rb +17 -0
  17. data/example/spec/requests/rails_fixtures_override_spec.rb +106 -0
  18. data/example/spec/spec_helper.rb +94 -0
  19. data/example/test/factories.rb +4 -0
  20. data/example/test/integration/better_mode_switching_demo_test.rb +41 -0
  21. data/example/test/integration/boops_that_boop_boops_test.rb +17 -0
  22. data/example/test/integration/dont_dump_tables_test.rb +7 -0
  23. data/example/test/integration/load_rollback_truncate_test.rb +190 -0
  24. data/example/test/integration/mode_switching_demo_test.rb +38 -0
  25. data/example/test/integration/parallel_boops_with_fixtures_test.rb +10 -0
  26. data/example/test/integration/parallel_boops_without_fixtures_test.rb +9 -0
  27. data/example/test/integration/rails_fixtures_double_load_test.rb +10 -0
  28. data/example/test/integration/rails_fixtures_override_test.rb +110 -0
  29. data/example/test/integration/test_data_hooks_test.rb +89 -0
  30. data/example/test/integration/transaction_committing_boops_test.rb +27 -0
  31. data/example/test/test_helper.rb +4 -31
  32. data/lib/generators/test_data/cable_yaml_generator.rb +18 -0
  33. data/lib/generators/test_data/database_yaml_generator.rb +3 -4
  34. data/lib/generators/test_data/environment_file_generator.rb +7 -14
  35. data/lib/generators/test_data/initializer_generator.rb +51 -0
  36. data/lib/generators/test_data/secrets_yaml_generator.rb +19 -0
  37. data/lib/generators/test_data/webpacker_yaml_generator.rb +4 -3
  38. data/lib/test_data.rb +42 -1
  39. data/lib/test_data/active_record_ext.rb +11 -0
  40. data/lib/test_data/config.rb +57 -4
  41. data/lib/test_data/configurators.rb +3 -0
  42. data/lib/test_data/configurators/cable_yaml.rb +25 -0
  43. data/lib/test_data/configurators/environment_file.rb +3 -2
  44. data/lib/test_data/configurators/initializer.rb +26 -0
  45. data/lib/test_data/configurators/secrets_yaml.rb +25 -0
  46. data/lib/test_data/configurators/webpacker_yaml.rb +4 -3
  47. data/lib/test_data/custom_loaders/abstract_base.rb +25 -0
  48. data/lib/test_data/custom_loaders/rails_fixtures.rb +42 -0
  49. data/lib/test_data/dumps_database.rb +55 -5
  50. data/lib/test_data/generator_support.rb +3 -0
  51. data/lib/test_data/inserts_test_data.rb +25 -0
  52. data/lib/test_data/loads_database_dumps.rb +8 -8
  53. data/lib/test_data/log.rb +76 -0
  54. data/lib/test_data/manager.rb +187 -0
  55. data/lib/test_data/rake.rb +20 -9
  56. data/lib/test_data/save_point.rb +34 -0
  57. data/lib/test_data/statistics.rb +31 -0
  58. data/lib/test_data/truncates_test_data.rb +31 -0
  59. data/lib/test_data/verifies_dumps_are_loadable.rb +4 -4
  60. data/lib/test_data/version.rb +1 -1
  61. data/script/reset_example_app +18 -0
  62. data/script/test +78 -13
  63. data/test_data.gemspec +1 -1
  64. metadata +36 -4
  65. data/lib/test_data/transactional_data_loader.rb +0 -77
@@ -0,0 +1,94 @@
1
+ # This file was generated by the `rails generate rspec:install` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+ RSpec.configure do |config|
17
+ # rspec-expectations config goes here. You can use an alternate
18
+ # assertion/expectation library such as wrong or the stdlib/minitest
19
+ # assertions if you prefer.
20
+ config.expect_with :rspec do |expectations|
21
+ # This option will default to `true` in RSpec 4. It makes the `description`
22
+ # and `failure_message` of custom matchers include text for helper methods
23
+ # defined using `chain`, e.g.:
24
+ # be_bigger_than(2).and_smaller_than(4).description
25
+ # # => "be bigger than 2 and smaller than 4"
26
+ # ...rather than:
27
+ # # => "be bigger than 2"
28
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29
+ end
30
+
31
+ # rspec-mocks config goes here. You can use an alternate test double
32
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
33
+ config.mock_with :rspec do |mocks|
34
+ # Prevents you from mocking or stubbing a method that does not exist on
35
+ # a real object. This is generally recommended, and will default to
36
+ # `true` in RSpec 4.
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
41
+ # have no way to turn it off -- the option exists only for backwards
42
+ # compatibility in RSpec 3). It causes shared context metadata to be
43
+ # inherited by the metadata hash of host groups and examples, rather than
44
+ # triggering implicit auto-inclusion in groups with matching metadata.
45
+ config.shared_context_metadata_behavior = :apply_to_host_groups
46
+
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
+ # # This allows you to limit a spec run to individual examples or groups
50
+ # # you care about by tagging them with `:focus` metadata. When nothing
51
+ # # is tagged with `:focus`, all examples get run. RSpec also provides
52
+ # # aliases for `it`, `describe`, and `context` that include `:focus`
53
+ # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
54
+ # config.filter_run_when_matching :focus
55
+ #
56
+ # # Allows RSpec to persist some state between runs in order to support
57
+ # # the `--only-failures` and `--next-failure` CLI options. We recommend
58
+ # # you configure your source control system to ignore this file.
59
+ # config.example_status_persistence_file_path = "spec/examples.txt"
60
+ #
61
+ # # Limits the available syntax to the non-monkey patched syntax that is
62
+ # # recommended. For more details, see:
63
+ # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
64
+ # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
65
+ # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
66
+ # config.disable_monkey_patching!
67
+ #
68
+ # # Many RSpec users commonly either run the entire suite or an individual
69
+ # # file, and it's useful to allow more verbose output when running an
70
+ # # individual spec file.
71
+ # if config.files_to_run.one?
72
+ # # Use the documentation formatter for detailed output,
73
+ # # unless a formatter has already been configured
74
+ # # (e.g. via a command-line flag).
75
+ # config.default_formatter = "doc"
76
+ # end
77
+ #
78
+ # # Print the 10 slowest examples and example groups at the
79
+ # # end of the spec run, to help surface which specs are running
80
+ # # particularly slow.
81
+ # config.profile_examples = 10
82
+ #
83
+ # # Run specs in random order to surface order dependencies. If you find an
84
+ # # order dependency and want to debug it, you can fix the order by providing
85
+ # # the seed, which is printed after each run.
86
+ # # --seed 1234
87
+ # config.order = :random
88
+ #
89
+ # # Seed global randomization in this process using the `--seed` CLI option.
90
+ # # Setting this allows you to use `--seed` to deterministically reproduce
91
+ # # test failures related to randomization by passing the same `--seed` value
92
+ # # as the one that triggered the failure.
93
+ # Kernel.srand config.seed
94
+ end
@@ -0,0 +1,4 @@
1
+ FactoryBot.define do
2
+ factory :boop do
3
+ end
4
+ end
@@ -0,0 +1,41 @@
1
+ require "test_helper"
2
+
3
+ class ActiveSupport::TestCase
4
+ def self.test_data_mode(mode)
5
+ case mode
6
+ when :factory_bot
7
+ require "factory_bot_rails"
8
+ include FactoryBot::Syntax::Methods
9
+
10
+ setup do
11
+ TestData.uses_clean_slate
12
+ end
13
+ when :test_data
14
+ setup do
15
+ TestData.uses_test_data
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ class SomeFactoryUsingTest < ActiveSupport::TestCase
22
+ test_data_mode :factory_bot
23
+
24
+ def test_boops
25
+ create(:boop)
26
+
27
+ assert_equal 1, Boop.count
28
+ end
29
+ end
30
+
31
+ class SomeTestDataUsingTest < ActionDispatch::IntegrationTest
32
+ test_data_mode :test_data
33
+
34
+ def test_boops
35
+ assert_equal 10, Boop.count
36
+ end
37
+
38
+ def test_factory_bot_method_is_not_on_this_class
39
+ assert_raises(NameError) { method(:create) }
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ require "test_helper"
2
+
3
+ class BoopsThatBoopBoopsTest < SerializedNonTransactionalTestCase
4
+ def test_each_of_the_boops_has_a_boop
5
+ assert_equal 15, Boop.count
6
+
7
+ Boop.find_each do |boop|
8
+ assert_kind_of Boop, boop.other_boop
9
+ end
10
+ end
11
+
12
+ def test_it_wont_let_you_assign_a_nonsensical_boop
13
+ assert_raise {
14
+ Boop.last.update!(other_boop_id: 2138012)
15
+ }
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require "test_helper"
2
+
3
+ class DontDumpTablesTest < SerializedNonTransactionalTestCase
4
+ def test_dump_includes_zero_chatty_audit_logs
5
+ assert_equal 0, ChattyAuditLog.count
6
+ end
7
+ end
@@ -0,0 +1,190 @@
1
+ require "test_helper"
2
+
3
+ class LoadRollbackTruncateTest < ActiveSupport::TestCase
4
+ LogMessage = Struct.new(:message, :level, keyword_init: true)
5
+
6
+ def setup
7
+ @last_log = nil
8
+ TestData.log.level = :debug
9
+ TestData.log.writer = ->(message, level) {
10
+ @last_log = LogMessage.new(message: message, level: level)
11
+ }
12
+ end
13
+
14
+ def teardown
15
+ TestData.log.reset
16
+ TestData.statistics.reset
17
+ end
18
+
19
+ def test_loads_data_then_truncates_then_rolls_back_etc
20
+ # Check default state
21
+ assert_equal 0, Boop.count
22
+
23
+ # Now load the dump
24
+ TestData.uses_test_data
25
+ assert_equal 15, Boop.count
26
+ Boop.create!
27
+ assert_equal 16, Boop.count
28
+
29
+ # Next, truncate the boops
30
+ TestData.uses_clean_slate
31
+ assert_equal 0, Boop.count
32
+ Boop.create!
33
+ assert_equal 1, Boop.count
34
+
35
+ # Now roll back to _just after truncate_
36
+ TestData.uses_clean_slate
37
+ assert_equal 0, Boop.count
38
+ Boop.create!
39
+ assert_equal 1, Boop.count
40
+
41
+ # Verify default rollback works after truncate
42
+ TestData.uses_test_data
43
+ assert_equal 15, Boop.count
44
+
45
+ # Verify touching non-test-data tables will also be first rollbacked when truncate is called
46
+ TestData.uninitialize
47
+ good = ChattyAuditLog.create!(message: "I do belong here, because now we're at the start, prior to test_data's purview")
48
+ TestData.uses_test_data
49
+ bad = ChattyAuditLog.create!(message: "I won't belong here after truncate because I'm data that the truncate-calling test wouldn't expect")
50
+ assert_equal 2, ChattyAuditLog.count
51
+
52
+ TestData.uses_clean_slate
53
+
54
+ assert_equal 1, ChattyAuditLog.count
55
+ refute_nil ChattyAuditLog.find_by(id: good.id)
56
+ assert_nil ChattyAuditLog.find_by(id: bad.id)
57
+
58
+ # Warn but load anyway if rolled back to the start and then truncated
59
+ TestData.uninitialize
60
+ TestData.uses_clean_slate
61
+ assert_equal :debug, @last_log.level
62
+ assert_match "TestData.uses_clean_slate was called, but data was not loaded. Loading data", @last_log.message
63
+ assert_equal 0, Boop.count
64
+ TestData.uses_test_data
65
+ assert_equal 15, Boop.count
66
+
67
+ # Chaos: try rolling back outside the gem (one level of extraneous rollback) and verify load recovers
68
+ TestData.uninitialize
69
+ TestData.statistics.reset
70
+ assert_equal 0, TestData.statistics.load_count
71
+ TestData.uses_test_data
72
+ assert_equal 1, TestData.statistics.load_count
73
+ TestData.uses_test_data # Smart enough to not load again
74
+ assert_equal 1, TestData.statistics.load_count
75
+ ActiveRecord::Base.connection.rollback_transaction # Someone might do this!
76
+ TestData.uses_test_data # Still smart enough to not do this
77
+ assert_equal 1, TestData.statistics.load_count
78
+ TestData.uses_test_data # after load savepoint should have been healed with subsequent load call
79
+ assert_equal 15, Boop.count
80
+
81
+ # Chaos: try rolling back outside the gem (one level of extraneous rollback) and verify truncate recovers
82
+ TestData.uninitialize
83
+ TestData.statistics.reset
84
+ assert_equal 0, TestData.statistics.truncate_count
85
+ TestData.uses_test_data
86
+ TestData.uses_clean_slate
87
+ assert_equal 1, TestData.statistics.truncate_count
88
+ TestData.uses_clean_slate
89
+ assert_equal 1, TestData.statistics.truncate_count
90
+ ActiveRecord::Base.connection.rollback_transaction # Someone might do this!
91
+ TestData.uses_clean_slate # Will recover, not take the bait
92
+ assert_equal 1, TestData.statistics.truncate_count
93
+ TestData.uses_clean_slate # after truncate savepoint should have been healed with subsequent truncate call
94
+ assert_equal 0, Boop.count
95
+ TestData.uses_test_data
96
+ assert_equal 15, Boop.count
97
+
98
+ # Chaos: load data then call rollback two times and ensure we're still in a good spot
99
+ TestData.uninitialize
100
+ TestData.statistics.reset
101
+ TestData.uses_test_data
102
+ assert_equal 15, Boop.count
103
+ 2.times do # Two rollbacks means we're back at before_data_load
104
+ ActiveRecord::Base.connection.rollback_transaction
105
+ end
106
+ assert_equal 0, Boop.count
107
+ TestData.uses_test_data # It should successfully load again a second time
108
+ assert_equal 15, Boop.count
109
+ assert_equal 2, TestData.statistics.load_count
110
+
111
+ # Chaos: truncate data then call rollback two times and ensure we're still in a good spot
112
+ TestData.uninitialize
113
+ TestData.statistics.reset
114
+ TestData.uses_clean_slate # will warn-and-load and then truncate
115
+ assert_equal 0, Boop.count
116
+ 2.times do # Two rollbacks means data is loaded but after_data_load savepoint has been lost
117
+ ActiveRecord::Base.connection.rollback_transaction
118
+ end
119
+ assert_equal 15, Boop.count
120
+ assert_equal 1, TestData.statistics.load_count
121
+ assert_equal 1, TestData.statistics.truncate_count
122
+ TestData.uses_clean_slate # should restore the lost after_data_load savepoint and re-truncate
123
+ 3.times do # Three rollbacks means we are at before_data_load again
124
+ ActiveRecord::Base.connection.rollback_transaction
125
+ end
126
+ assert_equal 0, Boop.count
127
+ TestData.uses_test_data
128
+ assert_equal 15, Boop.count
129
+ TestData.uses_clean_slate
130
+ assert_equal 0, Boop.count
131
+ assert_equal 3, TestData.statistics.truncate_count
132
+ assert_equal 2, TestData.statistics.load_count
133
+ end
134
+
135
+ def test_suite_runs_different_tests_in_whatever_order
136
+ # Imagine a test-datay test runs
137
+ test_data_using_test = -> do
138
+ TestData.uses_test_data # before each
139
+ Boop.create!
140
+ assert_equal 16, Boop.count
141
+ end
142
+
143
+ test_data_avoiding_test = -> do
144
+ TestData.uses_clean_slate # before each
145
+ Boop.create!
146
+ assert_equal 1, Boop.count
147
+ end
148
+
149
+ # Run the tests separately:
150
+ 3.times { test_data_using_test.call }
151
+ 3.times { test_data_avoiding_test.call }
152
+
153
+ # Mix and match the tests:
154
+ test_data_using_test.call
155
+ test_data_avoiding_test.call
156
+ test_data_using_test.call
157
+ test_data_avoiding_test.call
158
+ test_data_using_test.call
159
+ test_data_avoiding_test.call
160
+ end
161
+
162
+ def test_calling_truncate_multiple_times_will_return_you_to_truncated_state
163
+ # In the interest of behaving similarly to .load, rollback in case the
164
+ # previous test doesn't have an after_each as you might hope/expect
165
+ 3.times do
166
+ TestData.uses_clean_slate
167
+ Boop.create!
168
+ assert_equal 1, Boop.count
169
+ end
170
+ end
171
+
172
+ def test_other_rollbacks_mess_with_transaction_state_will_debug_you
173
+ TestData.uninitialize
174
+ TestData.statistics.reset
175
+ TestData.uses_test_data
176
+ ActiveRecord::Base.connection.rollback_transaction # data loaded, after_data_load save point destroyed
177
+ TestData.uses_test_data
178
+ assert_equal :debug, @last_log.level # debug only b/c rails fixtures will do this on every after_each if enabled
179
+ assert_match "Recreating the :after_data_load save point", @last_log.message
180
+ assert_equal 1, TestData.statistics.load_count
181
+
182
+ TestData.uses_clean_slate
183
+ ActiveRecord::Base.connection.rollback_transaction # data loaded, after_data_truncate save point destroyed
184
+ TestData.uses_clean_slate
185
+ assert_equal :debug, @last_log.level # debug only b/c rails fixtures will do this on every after_each if enabled
186
+ assert_match "Recreating the :after_data_truncate save point", @last_log.message
187
+ assert_equal 1, TestData.statistics.load_count
188
+ assert_equal 1, TestData.statistics.truncate_count
189
+ end
190
+ end
@@ -0,0 +1,38 @@
1
+ require "test_helper"
2
+
3
+ class ModeSwitchingTestCase < ActiveSupport::TestCase
4
+ self.use_transactional_tests = false
5
+
6
+ def self.test_data_mode(mode)
7
+ if mode == :factory_bot
8
+ require "factory_bot_rails"
9
+ include FactoryBot::Syntax::Methods
10
+
11
+ setup do
12
+ TestData.uses_clean_slate
13
+ end
14
+ elsif mode == :test_data
15
+ setup do
16
+ TestData.uses_test_data
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ class FactoryModeTest < ModeSwitchingTestCase
23
+ test_data_mode :factory_bot
24
+
25
+ def test_boops
26
+ create(:boop)
27
+
28
+ assert_equal 1, Boop.count
29
+ end
30
+ end
31
+
32
+ class TestDataModeTest < ModeSwitchingTestCase
33
+ test_data_mode :test_data
34
+
35
+ def test_boops
36
+ assert_equal 10, Boop.count
37
+ end
38
+ end
@@ -1,5 +1,15 @@
1
1
  require "test_helper"
2
2
 
3
+ class ParallelizedTransactionalFixturefullTestCase < ActiveSupport::TestCase
4
+ parallelize(workers: :number_of_processors)
5
+ self.use_transactional_tests = true
6
+ fixtures :all
7
+
8
+ setup do
9
+ TestData.uses_test_data
10
+ end
11
+ end
12
+
3
13
  class ParallelBoopsWithFixturesTest < ParallelizedTransactionalFixturefullTestCase
4
14
  100.times do |i|
5
15
  test "that boops don't change ##{i}" do
@@ -1,5 +1,14 @@
1
1
  require "test_helper"
2
2
 
3
+ class ParallelizedNonTransactionalFixturelessTestCase < ActiveSupport::TestCase
4
+ parallelize(workers: :number_of_processors)
5
+ self.use_transactional_tests = false
6
+
7
+ setup do
8
+ TestData.uses_test_data
9
+ end
10
+ end
11
+
3
12
  class ParallelBoopsWithoutFixturesTest < ParallelizedNonTransactionalFixturelessTestCase
4
13
  100.times do |i|
5
14
  test "that boops don't change ##{i}" do
@@ -0,0 +1,10 @@
1
+ require "test_helper"
2
+
3
+ class FixturesUsingTest < ActiveSupport::TestCase
4
+ def test_tries_to_load_rails_fixtures_with_test_data
5
+ error = assert_raises(TestData::Error) do
6
+ TestData.uses_rails_fixtures(self)
7
+ end
8
+ assert_match "'TestData.uses_rails_fixtures(self)' 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", error.message
9
+ end
10
+ end