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.
- checksums.yaml +4 -4
 - data/.github/workflows/ruby.yml +41 -0
 - data/.standard.yml +2 -0
 - data/CHANGELOG.md +43 -0
 - data/Gemfile.lock +17 -15
 - data/LICENSE.txt +1 -6
 - data/README.md +1232 -17
 - data/example/.gitignore +1 -4
 - data/example/Gemfile +3 -0
 - data/example/Gemfile.lock +100 -71
 - data/example/README.md +2 -22
 - data/example/config/application.rb +3 -0
 - data/example/config/credentials.yml.enc +1 -1
 - data/example/config/database.yml +2 -0
 - data/example/spec/rails_helper.rb +64 -0
 - data/example/spec/requests/boops_spec.rb +17 -0
 - data/example/spec/requests/rails_fixtures_override_spec.rb +106 -0
 - data/example/spec/spec_helper.rb +94 -0
 - data/example/test/factories.rb +4 -0
 - data/example/test/integration/better_mode_switching_demo_test.rb +41 -0
 - data/example/test/integration/boops_that_boop_boops_test.rb +17 -0
 - data/example/test/integration/dont_dump_tables_test.rb +7 -0
 - data/example/test/integration/load_rollback_truncate_test.rb +190 -0
 - data/example/test/integration/mode_switching_demo_test.rb +38 -0
 - data/example/test/integration/parallel_boops_with_fixtures_test.rb +10 -0
 - data/example/test/integration/parallel_boops_without_fixtures_test.rb +9 -0
 - data/example/test/integration/rails_fixtures_double_load_test.rb +10 -0
 - data/example/test/integration/rails_fixtures_override_test.rb +110 -0
 - data/example/test/integration/test_data_hooks_test.rb +89 -0
 - data/example/test/integration/transaction_committing_boops_test.rb +27 -0
 - data/example/test/test_helper.rb +4 -31
 - data/lib/generators/test_data/cable_yaml_generator.rb +18 -0
 - data/lib/generators/test_data/database_yaml_generator.rb +3 -4
 - data/lib/generators/test_data/environment_file_generator.rb +7 -14
 - data/lib/generators/test_data/initializer_generator.rb +51 -0
 - data/lib/generators/test_data/secrets_yaml_generator.rb +19 -0
 - data/lib/generators/test_data/webpacker_yaml_generator.rb +4 -3
 - data/lib/test_data.rb +42 -1
 - data/lib/test_data/active_record_ext.rb +11 -0
 - data/lib/test_data/config.rb +57 -4
 - data/lib/test_data/configurators.rb +3 -0
 - data/lib/test_data/configurators/cable_yaml.rb +25 -0
 - data/lib/test_data/configurators/environment_file.rb +3 -2
 - data/lib/test_data/configurators/initializer.rb +26 -0
 - data/lib/test_data/configurators/secrets_yaml.rb +25 -0
 - data/lib/test_data/configurators/webpacker_yaml.rb +4 -3
 - data/lib/test_data/custom_loaders/abstract_base.rb +25 -0
 - data/lib/test_data/custom_loaders/rails_fixtures.rb +42 -0
 - data/lib/test_data/dumps_database.rb +55 -5
 - data/lib/test_data/generator_support.rb +3 -0
 - data/lib/test_data/inserts_test_data.rb +25 -0
 - data/lib/test_data/loads_database_dumps.rb +8 -8
 - data/lib/test_data/log.rb +76 -0
 - data/lib/test_data/manager.rb +187 -0
 - data/lib/test_data/rake.rb +20 -9
 - data/lib/test_data/save_point.rb +34 -0
 - data/lib/test_data/statistics.rb +31 -0
 - data/lib/test_data/truncates_test_data.rb +31 -0
 - data/lib/test_data/verifies_dumps_are_loadable.rb +4 -4
 - data/lib/test_data/version.rb +1 -1
 - data/script/reset_example_app +18 -0
 - data/script/test +78 -13
 - data/test_data.gemspec +1 -1
 - metadata +36 -4
 - data/lib/test_data/transactional_data_loader.rb +0 -77
 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module TestData
         
     | 
| 
      
 2 
     | 
    
         
            +
              class InsertsTestData
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 4 
     | 
    
         
            +
                  @config = TestData.config
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @statistics = TestData.statistics
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def call
         
     | 
| 
      
 9 
     | 
    
         
            +
                  search_path = connection.execute("show search_path").first["search_path"]
         
     | 
| 
      
 10 
     | 
    
         
            +
                  connection.disable_referential_integrity do
         
     | 
| 
      
 11 
     | 
    
         
            +
                    connection.execute(File.read(@config.data_dump_full_path))
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  connection.execute <<~SQL
         
     | 
| 
      
 14 
     | 
    
         
            +
                    select pg_catalog.set_config('search_path', '#{search_path}', false)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  SQL
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @statistics.count_load!
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                private
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def connection
         
     | 
| 
      
 22 
     | 
    
         
            +
                  ActiveRecord::Base.connection
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -16,17 +16,17 @@ module TestData 
     | 
|
| 
       16 
16 
     | 
    
         
             
                  )
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                  load_dump(
         
     | 
| 
       19 
     | 
    
         
            -
                    name: "test data",
         
     | 
| 
      
 19 
     | 
    
         
            +
                    name: "non-test data",
         
     | 
| 
       20 
20 
     | 
    
         
             
                    database_name: @config.database_name,
         
     | 
| 
       21 
     | 
    
         
            -
                    relative_path: @config. 
     | 
| 
       22 
     | 
    
         
            -
                    full_path: @config. 
     | 
| 
      
 21 
     | 
    
         
            +
                    relative_path: @config.non_test_data_dump_path,
         
     | 
| 
      
 22 
     | 
    
         
            +
                    full_path: @config.non_test_data_dump_full_path
         
     | 
| 
       23 
23 
     | 
    
         
             
                  )
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
                  load_dump(
         
     | 
| 
       26 
     | 
    
         
            -
                    name: " 
     | 
| 
      
 26 
     | 
    
         
            +
                    name: "test data",
         
     | 
| 
       27 
27 
     | 
    
         
             
                    database_name: @config.database_name,
         
     | 
| 
       28 
     | 
    
         
            -
                    relative_path: @config. 
     | 
| 
       29 
     | 
    
         
            -
                    full_path: @config. 
     | 
| 
      
 28 
     | 
    
         
            +
                    relative_path: @config.data_dump_path,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    full_path: @config.data_dump_full_path
         
     | 
| 
       30 
30 
     | 
    
         
             
                  )
         
     | 
| 
       31 
31 
     | 
    
         
             
                end
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
         @@ -36,9 +36,9 @@ module TestData 
     | 
|
| 
       36 
36 
     | 
    
         
             
                  dump_pathname = Pathname.new(full_path)
         
     | 
| 
       37 
37 
     | 
    
         
             
                  FileUtils.mkdir_p(File.dirname(dump_pathname))
         
     | 
| 
       38 
38 
     | 
    
         
             
                  if system "psql -q -d #{database_name} < #{dump_pathname}"
         
     | 
| 
       39 
     | 
    
         
            -
                     
     | 
| 
      
 39 
     | 
    
         
            +
                    TestData.log.info "Loaded #{name} from '#{relative_path}' into database '#{database_name}' "
         
     | 
| 
       40 
40 
     | 
    
         
             
                  else
         
     | 
| 
       41 
     | 
    
         
            -
                    raise "Failed while attempting to load #{name} from '#{relative_path}' into database '#{database_name}'"
         
     | 
| 
      
 41 
     | 
    
         
            +
                    raise Error.new("Failed while attempting to load #{name} from '#{relative_path}' into database '#{database_name}'")
         
     | 
| 
       42 
42 
     | 
    
         
             
                  end
         
     | 
| 
       43 
43 
     | 
    
         
             
                end
         
     | 
| 
       44 
44 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,76 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module TestData
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.log
         
     | 
| 
      
 3 
     | 
    
         
            +
                @log ||= Log.new
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              class Log
         
     | 
| 
      
 7 
     | 
    
         
            +
                LEVELS = [:debug, :info, :warn, :error, :quiet]
         
     | 
| 
      
 8 
     | 
    
         
            +
                DEFAULT_WRITER = ->(message, level) do
         
     | 
| 
      
 9 
     | 
    
         
            +
                  output = "[test_data:#{level}] #{message}"
         
     | 
| 
      
 10 
     | 
    
         
            +
                  if [:warn, :error].include?(level)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    warn output
         
     | 
| 
      
 12 
     | 
    
         
            +
                  else
         
     | 
| 
      
 13 
     | 
    
         
            +
                    puts output
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
                PLAIN_WRITER = ->(message, level) do
         
     | 
| 
      
 17 
     | 
    
         
            +
                  if [:warn, :error].include?(level)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    warn message
         
     | 
| 
      
 19 
     | 
    
         
            +
                  else
         
     | 
| 
      
 20 
     | 
    
         
            +
                    puts message
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                attr_reader :level, :writer
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 27 
     | 
    
         
            +
                  reset
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                LEVELS[0...4].each do |level|
         
     | 
| 
      
 31 
     | 
    
         
            +
                  define_method level.to_s do |message|
         
     | 
| 
      
 32 
     | 
    
         
            +
                    next unless message.strip.present?
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    @writer.call(message, level) if enabled?(level)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 39 
     | 
    
         
            +
                  self.level = ENV["TEST_DATA_LOG_LEVEL"]&.to_sym || :info
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @writer = DEFAULT_WRITER
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def level=(level)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  if LEVELS.include?(level)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @level = level
         
     | 
| 
      
 46 
     | 
    
         
            +
                  else
         
     | 
| 
      
 47 
     | 
    
         
            +
                    raise Error.new("Not a valid level")
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def writer=(writer)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  if writer.respond_to?(:call)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    @writer = writer
         
     | 
| 
      
 54 
     | 
    
         
            +
                  else
         
     | 
| 
      
 55 
     | 
    
         
            +
                    raise Error.new("Log writer must be callable")
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def with_writer(writer, &blk)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  og_writer = self.writer
         
     | 
| 
      
 61 
     | 
    
         
            +
                  self.writer = writer
         
     | 
| 
      
 62 
     | 
    
         
            +
                  blk.call
         
     | 
| 
      
 63 
     | 
    
         
            +
                  self.writer = og_writer
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def with_plain_writer(&blk)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  with_writer(PLAIN_WRITER, &blk)
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                private
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def enabled?(level)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  LEVELS.index(level) >= LEVELS.index(@level)
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,187 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module TestData
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Manager
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 4 
     | 
    
         
            +
                  @inserts_test_data = InsertsTestData.new
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @truncates_test_data = TruncatesTestData.new
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @config = TestData.config
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @statistics = TestData.statistics
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @save_points = []
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def load
         
     | 
| 
      
 12 
     | 
    
         
            +
                  ensure_after_load_save_point_is_active_if_data_is_loaded!
         
     | 
| 
      
 13 
     | 
    
         
            +
                  return rollback_to_after_data_load if save_point_active?(:after_data_load)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  create_save_point(:before_data_load)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @inserts_test_data.call
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @config.after_test_data_load_hook.call
         
     | 
| 
      
 18 
     | 
    
         
            +
                  record_ar_internal_metadata_that_test_data_is_loaded
         
     | 
| 
      
 19 
     | 
    
         
            +
                  create_save_point(:after_data_load)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def truncate
         
     | 
| 
      
 23 
     | 
    
         
            +
                  ensure_after_load_save_point_is_active_if_data_is_loaded!
         
     | 
| 
      
 24 
     | 
    
         
            +
                  ensure_after_truncate_save_point_is_active_if_data_is_truncated!
         
     | 
| 
      
 25 
     | 
    
         
            +
                  return rollback_to_after_data_truncate if save_point_active?(:after_data_truncate)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  if save_point_active?(:after_data_load)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    # If a test that uses the test data runs before a test that starts by
         
     | 
| 
      
 29 
     | 
    
         
            +
                    # calling truncate, tables in the database that would NOT be truncated
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # may have been changed. To avoid this category of test pollution, start
         
     | 
| 
      
 31 
     | 
    
         
            +
                    # the truncation by rolling back to the known clean point
         
     | 
| 
      
 32 
     | 
    
         
            +
                    rollback_to_after_data_load
         
     | 
| 
      
 33 
     | 
    
         
            +
                  else
         
     | 
| 
      
 34 
     | 
    
         
            +
                    # Seems silly loading data when the user asked us to truncate, but
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # it's important that the state of the transaction stack matches the
         
     | 
| 
      
 36 
     | 
    
         
            +
                    # mental model we advertise, because any _other_ test in their suite
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # should expect that the existence of :after_data_truncate save point
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # implies that it's safe to rollback to the :after_data_load save
         
     | 
| 
      
 39 
     | 
    
         
            +
                    # point; since tests run in random order, it's likely to happen
         
     | 
| 
      
 40 
     | 
    
         
            +
                    TestData.log.debug("TestData.uses_clean_slate was called, but data was not loaded. Loading data before truncate to preserve the transaction save point ordering")
         
     | 
| 
      
 41 
     | 
    
         
            +
                    load
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  @truncates_test_data.call
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @config.after_test_data_truncate_hook.call
         
     | 
| 
      
 46 
     | 
    
         
            +
                  record_ar_internal_metadata_that_test_data_is_truncated
         
     | 
| 
      
 47 
     | 
    
         
            +
                  create_save_point(:after_data_truncate)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def load_custom_data(loader, **options)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  loader.validate!(**options)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  snapshot_name = "user_#{loader.name}".to_sym
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  ensure_after_load_save_point_is_active_if_data_is_loaded!
         
     | 
| 
      
 55 
     | 
    
         
            +
                  ensure_after_truncate_save_point_is_active_if_data_is_truncated!
         
     | 
| 
      
 56 
     | 
    
         
            +
                  ensure_custom_save_point_is_active_if_memo_exists!(snapshot_name)
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  loader.load_requested(**options)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  if save_point_active?(snapshot_name) && loader.loaded?(**options)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    return rollback_to_custom_savepoint(snapshot_name)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  if save_point_active?(:after_data_truncate)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    rollback_to_after_data_truncate
         
     | 
| 
      
 65 
     | 
    
         
            +
                  else
         
     | 
| 
      
 66 
     | 
    
         
            +
                    truncate
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  loader.load(**options)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  record_ar_internal_metadata_of_custom_save_point(snapshot_name)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  create_save_point(snapshot_name)
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def rollback_to_before_data_load
         
     | 
| 
      
 75 
     | 
    
         
            +
                  if save_point_active?(:before_data_load)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    rollback_save_point(:before_data_load)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    # No need to recreate the save point
         
     | 
| 
      
 78 
     | 
    
         
            +
                    # (TestData.uses_test_data will if called)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                private
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def rollback_to_after_data_load
         
     | 
| 
      
 85 
     | 
    
         
            +
                  if save_point_active?(:after_data_load)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    rollback_save_point(:after_data_load)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    create_save_point(:after_data_load)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                def rollback_to_after_data_truncate
         
     | 
| 
      
 92 
     | 
    
         
            +
                  if save_point_active?(:after_data_truncate)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    rollback_save_point(:after_data_truncate)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    create_save_point(:after_data_truncate)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                def rollback_to_custom_savepoint(name)
         
     | 
| 
      
 99 
     | 
    
         
            +
                  if save_point_active?(name)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    rollback_save_point(name)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    create_save_point(name)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  end
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                def connection
         
     | 
| 
      
 106 
     | 
    
         
            +
                  ActiveRecord::Base.connection
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                def ensure_after_load_save_point_is_active_if_data_is_loaded!
         
     | 
| 
      
 110 
     | 
    
         
            +
                  if !save_point_active?(:after_data_load) && ar_internal_metadata_shows_test_data_is_loaded?
         
     | 
| 
      
 111 
     | 
    
         
            +
                    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"
         
     | 
| 
      
 112 
     | 
    
         
            +
                    create_save_point(:after_data_load)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def record_ar_internal_metadata_that_test_data_is_loaded
         
     | 
| 
      
 117 
     | 
    
         
            +
                  if ar_internal_metadata_shows_test_data_is_loaded?
         
     | 
| 
      
 118 
     | 
    
         
            +
                    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?"
         
     | 
| 
      
 119 
     | 
    
         
            +
                  else
         
     | 
| 
      
 120 
     | 
    
         
            +
                    ActiveRecord::InternalMetadata.create!(key: "test_data:loaded", value: "true")
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                def ar_internal_metadata_shows_test_data_is_loaded?
         
     | 
| 
      
 125 
     | 
    
         
            +
                  ActiveRecord::InternalMetadata.find_by(key: "test_data:loaded")&.value == "true"
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                def ensure_after_truncate_save_point_is_active_if_data_is_truncated!
         
     | 
| 
      
 129 
     | 
    
         
            +
                  if !save_point_active?(:after_data_truncate) && ar_internal_metadata_shows_test_data_is_truncated?
         
     | 
| 
      
 130 
     | 
    
         
            +
                    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"
         
     | 
| 
      
 131 
     | 
    
         
            +
                    create_save_point(:after_data_truncate)
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
                end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                def record_ar_internal_metadata_that_test_data_is_truncated
         
     | 
| 
      
 136 
     | 
    
         
            +
                  if ar_internal_metadata_shows_test_data_is_truncated?
         
     | 
| 
      
 137 
     | 
    
         
            +
                    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?"
         
     | 
| 
      
 138 
     | 
    
         
            +
                  else
         
     | 
| 
      
 139 
     | 
    
         
            +
                    ActiveRecord::InternalMetadata.create!(key: "test_data:truncated", value: "true")
         
     | 
| 
      
 140 
     | 
    
         
            +
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                def ar_internal_metadata_shows_test_data_is_truncated?
         
     | 
| 
      
 144 
     | 
    
         
            +
                  ActiveRecord::InternalMetadata.find_by(key: "test_data:truncated")&.value == "true"
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                def ensure_custom_save_point_is_active_if_memo_exists!(name)
         
     | 
| 
      
 148 
     | 
    
         
            +
                  if !save_point_active?(name) && ar_internal_metadata_shows_custom_operation_was_persisted?(name)
         
     | 
| 
      
 149 
     | 
    
         
            +
                    TestData.log.debug "#{name} appears to have been loaded by test_data, but the #{name} save point was rolled back (and not by this gem). Recreating the #{name} save point"
         
     | 
| 
      
 150 
     | 
    
         
            +
                    create_save_point(name)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                def ar_internal_metadata_shows_custom_operation_was_persisted?(name)
         
     | 
| 
      
 155 
     | 
    
         
            +
                  ActiveRecord::InternalMetadata.find_by(key: "test_data:#{name}")&.value == "true"
         
     | 
| 
      
 156 
     | 
    
         
            +
                end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                def record_ar_internal_metadata_of_custom_save_point(name)
         
     | 
| 
      
 159 
     | 
    
         
            +
                  if ar_internal_metadata_shows_custom_operation_was_persisted?(name)
         
     | 
| 
      
 160 
     | 
    
         
            +
                    TestData.log.warn "Attempted to record that test_data had loaded #{name} in ar_internal_metadata, but record already existed. Perhaps a previous test run committed it?"
         
     | 
| 
      
 161 
     | 
    
         
            +
                  else
         
     | 
| 
      
 162 
     | 
    
         
            +
                    ActiveRecord::InternalMetadata.create!(key: "test_data:#{name}", value: "true")
         
     | 
| 
      
 163 
     | 
    
         
            +
                  end
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                def save_point_active?(name)
         
     | 
| 
      
 167 
     | 
    
         
            +
                  purge_closed_save_points!
         
     | 
| 
      
 168 
     | 
    
         
            +
                  !!@save_points.find { |sp| sp.name == name }&.active?
         
     | 
| 
      
 169 
     | 
    
         
            +
                end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                def create_save_point(name)
         
     | 
| 
      
 172 
     | 
    
         
            +
                  raise Error.new("Could not create test_data savepoint '#{name}', because it was already active!") if save_point_active?(name)
         
     | 
| 
      
 173 
     | 
    
         
            +
                  @save_points << SavePoint.new(name)
         
     | 
| 
      
 174 
     | 
    
         
            +
                end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                def rollback_save_point(name)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  @save_points.find { |sp| sp.name == name }&.rollback!
         
     | 
| 
      
 178 
     | 
    
         
            +
                  purge_closed_save_points!
         
     | 
| 
      
 179 
     | 
    
         
            +
                end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                def purge_closed_save_points!
         
     | 
| 
      
 182 
     | 
    
         
            +
                  @save_points = @save_points.select { |save_point|
         
     | 
| 
      
 183 
     | 
    
         
            +
                    save_point.active?
         
     | 
| 
      
 184 
     | 
    
         
            +
                  }
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
              end
         
     | 
| 
      
 187 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/test_data/rake.rb
    CHANGED
    
    | 
         @@ -22,13 +22,15 @@ end 
     | 
|
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            desc "Verifies test_data environment looks good"
         
     | 
| 
       24 
24 
     | 
    
         
             
            task "test_data:verify_config" do
         
     | 
| 
       25 
     | 
    
         
            -
               
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                 
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
                   
     | 
| 
      
 25 
     | 
    
         
            +
              TestData.log.with_plain_writer do
         
     | 
| 
      
 26 
     | 
    
         
            +
                config = TestData::VerifiesConfiguration.new.call
         
     | 
| 
      
 27 
     | 
    
         
            +
                unless config.looks_good?
         
     | 
| 
      
 28 
     | 
    
         
            +
                  TestData.log.warn "\nThe test_data gem is not configured correctly. Try running: rake test_data:configure\n\n"
         
     | 
| 
      
 29 
     | 
    
         
            +
                  config.problems.each do |problem|
         
     | 
| 
      
 30 
     | 
    
         
            +
                    TestData.log.warn "  - #{problem}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  fail
         
     | 
| 
       30 
33 
     | 
    
         
             
                end
         
     | 
| 
       31 
     | 
    
         
            -
                fail
         
     | 
| 
       32 
34 
     | 
    
         
             
              end
         
     | 
| 
       33 
35 
     | 
    
         
             
            end
         
     | 
| 
       34 
36 
     | 
    
         | 
| 
         @@ -48,17 +50,26 @@ task "test_data:initialize" => ["test_data:verify_config", :environment] do 
     | 
|
| 
       48 
50 
     | 
    
         
             
                ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord::Base.schema_format, ENV["SCHEMA"], "test_data")
         
     | 
| 
       49 
51 
     | 
    
         
             
                ActiveRecord::Tasks::DatabaseTasks.load_seed
         
     | 
| 
       50 
52 
     | 
    
         
             
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
              TestData.log.info <<~MSG
         
     | 
| 
      
 55 
     | 
    
         
            +
                Your test_data environment and database are ready for use! You can now run your server (or any command) to create some test data like so:
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  $ RAILS_ENV=test_data bin/rails server
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
              MSG
         
     | 
| 
       51 
60 
     | 
    
         
             
            end
         
     | 
| 
       52 
61 
     | 
    
         | 
| 
       53 
62 
     | 
    
         
             
            desc "Initialize test_data Rails environment & configure database"
         
     | 
| 
       54 
63 
     | 
    
         
             
            task "test_data:install" => ["test_data:configure", "test_data:initialize"]
         
     | 
| 
       55 
64 
     | 
    
         | 
| 
       56 
65 
     | 
    
         
             
            desc "Dumps the interactive test_data database"
         
     | 
| 
       57 
     | 
    
         
            -
            task "test_data:dump" => "test_data:verify_config" do
         
     | 
| 
      
 66 
     | 
    
         
            +
            task "test_data:dump" => ["test_data:verify_config", :environment] do
         
     | 
| 
      
 67 
     | 
    
         
            +
              next run_in_test_data_env("test_data:dump") if wrong_env?
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
       58 
69 
     | 
    
         
             
              TestData::DumpsDatabase.new.call
         
     | 
| 
       59 
70 
     | 
    
         
             
            end
         
     | 
| 
       60 
71 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
            desc " 
     | 
| 
      
 72 
     | 
    
         
            +
            desc "Loads the schema and data SQL dumps into the test_data database"
         
     | 
| 
       62 
73 
     | 
    
         
             
            task "test_data:load" => ["test_data:verify_config", :environment] do
         
     | 
| 
       63 
74 
     | 
    
         
             
              next run_in_test_data_env("test_data:load") if wrong_env?
         
     | 
| 
       64 
75 
     | 
    
         | 
| 
         @@ -71,7 +82,7 @@ task "test_data:load" => ["test_data:verify_config", :environment] do 
     | 
|
| 
       71 
82 
     | 
    
         
             
              TestData::LoadsDatabaseDumps.new.call
         
     | 
| 
       72 
83 
     | 
    
         | 
| 
       73 
84 
     | 
    
         
             
              if ActiveRecord::Base.connection.migration_context.needs_migration?
         
     | 
| 
       74 
     | 
    
         
            -
                warn " 
     | 
| 
      
 85 
     | 
    
         
            +
                TestData.log.warn "There are pending migrations for database '#{TestData.config.database_name}'. To run them, run:\n\n  $ RAILS_ENV=test_data bin/rake db:migrate\n\n"
         
     | 
| 
       75 
86 
     | 
    
         
             
              end
         
     | 
| 
       76 
87 
     | 
    
         
             
            end
         
     | 
| 
       77 
88 
     | 
    
         | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module TestData
         
     | 
| 
      
 2 
     | 
    
         
            +
              class SavePoint
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :name, :transaction
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(name)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @transaction = connection.begin_transaction(joinable: false, _lazy: false)
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def active?
         
     | 
| 
      
 11 
     | 
    
         
            +
                  !@transaction.state.finalized?
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def rollback!
         
     | 
| 
      
 15 
     | 
    
         
            +
                  warn_if_not_rollbackable!
         
     | 
| 
      
 16 
     | 
    
         
            +
                  while active?
         
     | 
| 
      
 17 
     | 
    
         
            +
                    connection.rollback_transaction
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                private
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def connection
         
     | 
| 
      
 24 
     | 
    
         
            +
                  ActiveRecord::Base.connection
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def warn_if_not_rollbackable!
         
     | 
| 
      
 28 
     | 
    
         
            +
                  return if active?
         
     | 
| 
      
 29 
     | 
    
         
            +
                  TestData.log.warn(
         
     | 
| 
      
 30 
     | 
    
         
            +
                    "Attempted to roll back transaction save point '#{name}', but its state was #{@transaction.state}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  )
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module TestData
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.statistics
         
     | 
| 
      
 3 
     | 
    
         
            +
                @statistics ||= Statistics.new
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              class Statistics
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :load_count, :truncate_count, :load_rails_fixtures_count
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 10 
     | 
    
         
            +
                  reset
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def count_load!
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @load_count += 1
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def count_truncate!
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @truncate_count += 1
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def count_load_rails_fixtures!
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @load_rails_fixtures_count += 1
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @load_count = 0
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @truncate_count = 0
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @load_rails_fixtures_count = 0
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     |