test_data 0.0.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/CHANGELOG.md +2 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +104 -0
  6. data/LICENSE.txt +25 -0
  7. data/README.md +35 -0
  8. data/Rakefile +11 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/example/.gitattributes +8 -0
  12. data/example/.gitignore +24 -0
  13. data/example/Gemfile +19 -0
  14. data/example/Gemfile.lock +211 -0
  15. data/example/README.md +24 -0
  16. data/example/Rakefile +6 -0
  17. data/example/app/assets/config/manifest.js +2 -0
  18. data/example/app/assets/stylesheets/application.css +15 -0
  19. data/example/app/controllers/application_controller.rb +2 -0
  20. data/example/app/controllers/boops_controller.rb +10 -0
  21. data/example/app/helpers/application_helper.rb +2 -0
  22. data/example/app/models/application_record.rb +3 -0
  23. data/example/app/models/boop.rb +2 -0
  24. data/example/app/views/boops/index.html.erb +8 -0
  25. data/example/app/views/layouts/application.html.erb +15 -0
  26. data/example/bin/bundle +114 -0
  27. data/example/bin/rails +4 -0
  28. data/example/bin/rake +4 -0
  29. data/example/bin/setup +33 -0
  30. data/example/config.ru +6 -0
  31. data/example/config/application.rb +35 -0
  32. data/example/config/boot.rb +4 -0
  33. data/example/config/credentials.yml.enc +1 -0
  34. data/example/config/database.yml +86 -0
  35. data/example/config/environment.rb +5 -0
  36. data/example/config/environments/development.rb +60 -0
  37. data/example/config/environments/production.rb +96 -0
  38. data/example/config/environments/test.rb +49 -0
  39. data/example/config/initializers/application_controller_renderer.rb +8 -0
  40. data/example/config/initializers/backtrace_silencers.rb +8 -0
  41. data/example/config/initializers/content_security_policy.rb +28 -0
  42. data/example/config/initializers/cookies_serializer.rb +5 -0
  43. data/example/config/initializers/filter_parameter_logging.rb +6 -0
  44. data/example/config/initializers/inflections.rb +16 -0
  45. data/example/config/initializers/mime_types.rb +4 -0
  46. data/example/config/initializers/permissions_policy.rb +11 -0
  47. data/example/config/initializers/wrap_parameters.rb +14 -0
  48. data/example/config/locales/en.yml +33 -0
  49. data/example/config/puma.rb +43 -0
  50. data/example/config/routes.rb +5 -0
  51. data/example/db/migrate/20210417143158_create_boops.rb +7 -0
  52. data/example/db/schema.rb +21 -0
  53. data/example/db/seeds.rb +11 -0
  54. data/example/public/404.html +67 -0
  55. data/example/public/422.html +67 -0
  56. data/example/public/500.html +66 -0
  57. data/example/public/apple-touch-icon-precomposed.png +0 -0
  58. data/example/public/apple-touch-icon.png +0 -0
  59. data/example/public/favicon.ico +0 -0
  60. data/example/public/robots.txt +1 -0
  61. data/example/test/application_system_test_case.rb +5 -0
  62. data/example/test/fixtures/boops.yml +5 -0
  63. data/example/test/integration/basic_boops_test.rb +18 -0
  64. data/example/test/integration/migrated_boops_test.rb +7 -0
  65. data/example/test/integration/parallel_boops_with_fixtures_test.rb +11 -0
  66. data/example/test/integration/parallel_boops_without_fixtures_test.rb +11 -0
  67. data/example/test/integration/updated_boops_test.rb +7 -0
  68. data/example/test/test_helper.rb +41 -0
  69. data/lib/generators/test_data/database_yaml_generator.rb +24 -0
  70. data/lib/generators/test_data/environment_file_generator.rb +29 -0
  71. data/lib/generators/test_data/webpacker_yaml_generator.rb +26 -0
  72. data/lib/test_data.rb +20 -0
  73. data/lib/test_data/active_support_ext.rb +9 -0
  74. data/lib/test_data/config.rb +52 -0
  75. data/lib/test_data/configuration_verification.rb +3 -0
  76. data/lib/test_data/configurators.rb +11 -0
  77. data/lib/test_data/configurators/database_yaml.rb +24 -0
  78. data/lib/test_data/configurators/environment_file.rb +25 -0
  79. data/lib/test_data/configurators/webpacker_yaml.rb +38 -0
  80. data/lib/test_data/detects_database_emptiness.rb +17 -0
  81. data/lib/test_data/dumps_database.rb +49 -0
  82. data/lib/test_data/error.rb +4 -0
  83. data/lib/test_data/installs_configuration.rb +9 -0
  84. data/lib/test_data/loads_database_dumps.rb +45 -0
  85. data/lib/test_data/railtie.rb +12 -0
  86. data/lib/test_data/rake.rb +104 -0
  87. data/lib/test_data/transactional_data_loader.rb +77 -0
  88. data/lib/test_data/verifies_configuration.rb +14 -0
  89. data/lib/test_data/verifies_dumps_are_loadable.rb +37 -0
  90. data/lib/test_data/version.rb +3 -0
  91. data/script/test +49 -0
  92. data/test_data.gemspec +27 -0
  93. metadata +150 -0
@@ -0,0 +1,24 @@
1
+ module TestData
2
+ module Configurators
3
+ class DatabaseYaml
4
+ def initialize
5
+ @generator = DatabaseYamlGenerator.new
6
+ @config = TestData.config
7
+ end
8
+
9
+ def verify
10
+ if @config.database_yaml.key?("test_data")
11
+ ConfigurationVerification.new(looks_good?: true)
12
+ else
13
+ ConfigurationVerification.new(problems: [
14
+ "'#{@config.database_yaml_path}' does not contain a 'test_data' section"
15
+ ])
16
+ end
17
+ end
18
+
19
+ def configure
20
+ @generator.call
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module TestData
2
+ module Configurators
3
+ class EnvironmentFile
4
+ def initialize
5
+ @generator = EnvironmentFileGenerator.new
6
+ @config = TestData.config
7
+ end
8
+
9
+ def verify
10
+ pathname = Pathname.new("#{@config.pwd}/config/environments/test_data.rb")
11
+ if pathname.readable?
12
+ ConfigurationVerification.new(looks_good?: true)
13
+ else
14
+ ConfigurationVerification.new(problems: [
15
+ "'#{pathname}' is not readable"
16
+ ])
17
+ end
18
+ end
19
+
20
+ def configure
21
+ @generator.call
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ module TestData
2
+ module Configurators
3
+ class WebpackerYaml
4
+ def initialize
5
+ @generator = WebpackerYamlGenerator.new
6
+ @config = TestData.config
7
+ end
8
+
9
+ def verify
10
+ pathname = Pathname.new("#{@config.pwd}/config/webpacker.yml")
11
+ return ConfigurationVerification.new(looks_good?: true) unless pathname.readable?
12
+ yaml = load_yaml(pathname)
13
+ if yaml.nil?
14
+ ConfigurationVerification.new(problems: [
15
+ "'#{pathname}' is not valid YAML"
16
+ ])
17
+ elsif !yaml.key?("test_data")
18
+ ConfigurationVerification.new(problems: [
19
+ "'#{pathname}' does not contain a 'test_data' section"
20
+ ])
21
+ else
22
+ ConfigurationVerification.new(looks_good?: true)
23
+ end
24
+ end
25
+
26
+ def configure
27
+ @generator.call
28
+ end
29
+
30
+ private
31
+
32
+ def load_yaml(pathname)
33
+ YAML.load_file(pathname)
34
+ rescue
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ module TestData
2
+ class DetectsDatabaseEmptiness
3
+ def initialize
4
+ @config = TestData.config
5
+ end
6
+
7
+ def empty?
8
+ result = ActiveRecord::Base.connection.execute <<~SQL
9
+ select not exists (
10
+ select from information_schema.tables
11
+ where table_name = 'ar_internal_metadata'
12
+ ) as empty
13
+ SQL
14
+ result.first["empty"]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ require "pathname"
2
+ require "fileutils"
3
+
4
+ module TestData
5
+ class DumpsDatabase
6
+ def initialize
7
+ @config = TestData.config
8
+ end
9
+
10
+ def call
11
+ dump(
12
+ type: :schema,
13
+ database_name: @config.database_name,
14
+ relative_path: @config.schema_dump_path,
15
+ full_path: @config.schema_dump_full_path
16
+ )
17
+
18
+ dump(
19
+ type: :data,
20
+ name: "test data",
21
+ database_name: @config.database_name,
22
+ relative_path: @config.data_dump_path,
23
+ full_path: @config.data_dump_full_path,
24
+ flags: @config.non_test_data_tables.map { |t| "-T #{t}" }.join(" ")
25
+ )
26
+
27
+ dump(
28
+ type: :data,
29
+ name: "non-test data",
30
+ database_name: @config.database_name,
31
+ relative_path: @config.non_test_data_dump_path,
32
+ full_path: @config.non_test_data_dump_full_path,
33
+ flags: @config.non_test_data_tables.map { |t| "-t #{t}" }.join(" ")
34
+ )
35
+ end
36
+
37
+ private
38
+
39
+ def dump(type:, database_name:, relative_path:, full_path:, name: type, flags: "")
40
+ dump_pathname = Pathname.new(full_path)
41
+ FileUtils.mkdir_p(File.dirname(dump_pathname))
42
+ if system "pg_dump #{database_name} --no-tablespaces --no-owner --inserts --#{type}-only #{flags} -f #{dump_pathname}"
43
+ puts "Dumped database '#{database_name}' #{name} to '#{relative_path}'"
44
+ else
45
+ raise "Failed while attempting to dump '#{database_name}' #{name} to '#{relative_path}'"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ module TestData
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module TestData
2
+ class InstallsConfiguration
3
+ def call
4
+ Configurators.all.each do |configurator|
5
+ configurator.configure
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ require "pathname"
2
+ require "fileutils"
3
+
4
+ module TestData
5
+ class LoadsDatabaseDumps
6
+ def initialize
7
+ @config = TestData.config
8
+ end
9
+
10
+ def call
11
+ load_dump(
12
+ name: "schema",
13
+ database_name: @config.database_name,
14
+ relative_path: @config.schema_dump_path,
15
+ full_path: @config.schema_dump_full_path
16
+ )
17
+
18
+ load_dump(
19
+ name: "test data",
20
+ database_name: @config.database_name,
21
+ relative_path: @config.data_dump_path,
22
+ full_path: @config.data_dump_full_path
23
+ )
24
+
25
+ load_dump(
26
+ name: "non-test data",
27
+ database_name: @config.database_name,
28
+ relative_path: @config.non_test_data_dump_path,
29
+ full_path: @config.non_test_data_dump_full_path
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def load_dump(name:, database_name:, relative_path:, full_path:)
36
+ dump_pathname = Pathname.new(full_path)
37
+ FileUtils.mkdir_p(File.dirname(dump_pathname))
38
+ if system "psql -q -d #{database_name} < #{dump_pathname}"
39
+ puts "Loaded #{name} from '#{relative_path}' into database '#{database_name}' "
40
+ else
41
+ raise "Failed while attempting to load #{name} from '#{relative_path}' into database '#{database_name}'"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ require "rails/railtie"
2
+ require "pathname"
3
+
4
+ module TestData
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :test_data
7
+
8
+ rake_tasks do
9
+ load Pathname.new(__dir__).join("rake.rb")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,104 @@
1
+ def wrong_env?
2
+ Rails.env != "test_data"
3
+ end
4
+
5
+ def run_in_test_data_env(task_name)
6
+ command = "RAILS_ENV=test_data #{Rails.root}/bin/rake #{task_name}"
7
+ unless system(command)
8
+ fail "An error ocurred when running: #{command}"
9
+ end
10
+ end
11
+
12
+ def create_database_or_else_blow_up_if_its_not_empty!
13
+ unless TestData::DetectsDatabaseEmptiness.new.empty?
14
+ raise TestData::Error.new("Database '#{TestData.config.database_name}' already exists and is not empty. To re-initialize it, drop it first (e.g. `rake test_data:drop_database`)")
15
+ end
16
+ rescue TestData::Error => e
17
+ raise e
18
+ rescue
19
+ # Only (anticipated) cause for raise here is DB did not exist
20
+ Rake::Task["test_data:create_database"].invoke
21
+ end
22
+
23
+ desc "Verifies test_data environment looks good"
24
+ task "test_data:verify_config" do
25
+ config = TestData::VerifiesConfiguration.new.call
26
+ unless config.looks_good?
27
+ puts "The test_data gem is not configured correctly. Try 'rake test_data:install'?\n"
28
+ config.problems.each do |problem|
29
+ puts " - #{problem}"
30
+ end
31
+ fail
32
+ end
33
+ end
34
+
35
+ desc "Install default configuration files and snippets"
36
+ task "test_data:configure" do
37
+ TestData::InstallsConfiguration.new.call
38
+ end
39
+
40
+ desc "Initialize test_data's interactive database"
41
+ task "test_data:initialize" => ["test_data:verify_config", :environment] do
42
+ next run_in_test_data_env("test_data:initialize") if wrong_env?
43
+
44
+ create_database_or_else_blow_up_if_its_not_empty!
45
+ if TestData::VerifiesDumpsAreLoadable.new.call(quiet: true)
46
+ Rake::Task["test_data:load"].invoke
47
+ else
48
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord::Base.schema_format, ENV["SCHEMA"], "test_data")
49
+ ActiveRecord::Tasks::DatabaseTasks.load_seed
50
+ end
51
+ end
52
+
53
+ desc "Initialize test_data Rails environment & configure database"
54
+ task "test_data:install" => ["test_data:configure", "test_data:initialize"]
55
+
56
+ desc "Dumps the interactive test_data database"
57
+ task "test_data:dump" => "test_data:verify_config" do
58
+ TestData::DumpsDatabase.new.call
59
+ end
60
+
61
+ desc "Dumps the interactive test_data database"
62
+ task "test_data:load" => ["test_data:verify_config", :environment] do
63
+ next run_in_test_data_env("test_data:load") if wrong_env?
64
+
65
+ create_database_or_else_blow_up_if_its_not_empty!
66
+
67
+ unless TestData::VerifiesDumpsAreLoadable.new.call
68
+ fail "Cannot load schema & data into database '#{TestData.config.database_name}'"
69
+ end
70
+
71
+ TestData::LoadsDatabaseDumps.new.call
72
+
73
+ if ActiveRecord::Base.connection.migration_context.needs_migration?
74
+ warn "Warning: 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
+ end
76
+ end
77
+
78
+ desc "Creates the test_data interactive database"
79
+ task "test_data:create_database" => :environment do
80
+ if TestData::Configurators::DatabaseYaml.new.verify.looks_good?
81
+ ActiveRecord::Tasks::DatabaseTasks.create_current("test_data")
82
+ else
83
+ warn "Warning: 'test_data' section not defined in config/database.yml - Skipping"
84
+ end
85
+ end
86
+
87
+ # Add the test_data env to Rails' default rake db:create task
88
+ Rake::Task["db:create"].enhance do |task|
89
+ Rake::Task["test_data:create_database"].invoke
90
+ end
91
+
92
+ desc "Drops the test_data interactive database"
93
+ task "test_data:drop_database" => :environment do
94
+ if TestData::Configurators::DatabaseYaml.new.verify.looks_good?
95
+ ActiveRecord::Tasks::DatabaseTasks.drop_current("test_data")
96
+ else
97
+ warn "Warning: 'test_data' section not defined in config/database.yml - Skipping"
98
+ end
99
+ end
100
+
101
+ # Add the test_data env to Rails' default rake db:create task
102
+ Rake::Task["db:drop"].enhance do |task|
103
+ Rake::Task["test_data:drop_database"].invoke
104
+ end
@@ -0,0 +1,77 @@
1
+ require "fileutils"
2
+
3
+ module TestData
4
+ def self.load_data_dump
5
+ @transactional_data_loader ||= TransactionalDataLoader.new
6
+ @transactional_data_loader.load_data_dump
7
+ end
8
+
9
+ def self.rollback(to: :after_data_load)
10
+ raise Error.new("rollback called before load_data_dump") unless @transactional_data_loader.present?
11
+ @transactional_data_loader.rollback(to: to)
12
+ end
13
+
14
+ class TransactionalDataLoader
15
+ SavePoint = Struct.new(:name, :transaction, keyword_init: true)
16
+
17
+ def initialize
18
+ @config = TestData.config
19
+ @save_points = []
20
+ @dump_count = 0
21
+ end
22
+
23
+ def load_data_dump
24
+ create_save_point(:before_data_load) unless save_point?(:before_data_load)
25
+ unless save_point?(:after_data_load)
26
+ execute_data_dump
27
+ @dump_count += 1
28
+ create_save_point(:after_data_load)
29
+ end
30
+ end
31
+
32
+ def rollback(to:)
33
+ return unless save_point?(to)
34
+ rollback_save_point(to)
35
+ end
36
+
37
+ private
38
+
39
+ def execute_data_dump
40
+ search_path = execute("show search_path").first["search_path"]
41
+ execute(File.read(@config.data_dump_full_path))
42
+ execute <<~SQL
43
+ select pg_catalog.set_config('search_path', '#{search_path}', false)
44
+ SQL
45
+ end
46
+
47
+ def save_point?(name)
48
+ purge_closed_save_points!
49
+ @save_points.any? { |sp| sp.name == name }
50
+ end
51
+
52
+ def create_save_point(name)
53
+ save_point = SavePoint.new(
54
+ name: name,
55
+ transaction: ActiveRecord::Base.connection.begin_transaction(joinable: false, _lazy: false)
56
+ )
57
+ @save_points << save_point
58
+ end
59
+
60
+ def rollback_save_point(name)
61
+ if (save_point = @save_points.find { |sp| sp.name == name }) && save_point.transaction.open?
62
+ save_point.transaction.rollback
63
+ end
64
+ purge_closed_save_points!
65
+ end
66
+
67
+ def purge_closed_save_points!
68
+ @save_points = @save_points.select { |save_point|
69
+ save_point.transaction.open?
70
+ }
71
+ end
72
+
73
+ def execute(sql)
74
+ ActiveRecord::Base.connection.execute(sql)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,14 @@
1
+ module TestData
2
+ class VerifiesConfiguration
3
+ def call
4
+ problems = Configurators.all.flat_map { |configurator|
5
+ configurator.verify.problems
6
+ }.compact
7
+
8
+ ConfigurationVerification.new(
9
+ looks_good?: problems.none?,
10
+ problems: problems
11
+ )
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ module TestData
2
+ class VerifiesDumpsAreLoadable
3
+ def initialize
4
+ @config = TestData.config
5
+ @detects_database_emptiness = DetectsDatabaseEmptiness.new
6
+ end
7
+
8
+ def call(quiet: false)
9
+ schema_dump_looks_good = Pathname.new(@config.schema_dump_full_path).readable?
10
+ if !quiet && !schema_dump_looks_good
11
+ warn "Warning: Database schema dump '#{@config.schema_dump_path}' not readable"
12
+ end
13
+
14
+ data_dump_looks_good = Pathname.new(@config.data_dump_full_path).readable?
15
+ if !quiet && !data_dump_looks_good
16
+ warn "Warning: Database data dump '#{@config.data_dump_path}' not readable"
17
+ end
18
+
19
+ non_test_data_dump_looks_good = Pathname.new(@config.non_test_data_dump_full_path).readable?
20
+ if !quiet && !non_test_data_dump_looks_good
21
+ warn "Warning: Database non-test data dump '#{@config.non_test_data_dump_path}' not readable"
22
+ end
23
+
24
+ database_empty = @detects_database_emptiness.empty?
25
+ unless quiet || database_empty
26
+ warn "Warning: Database '#{@config.database_name}' is not empty"
27
+ end
28
+
29
+ [
30
+ schema_dump_looks_good,
31
+ data_dump_looks_good,
32
+ non_test_data_dump_looks_good,
33
+ database_empty
34
+ ].all?
35
+ end
36
+ end
37
+ end