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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +104 -0
- data/LICENSE.txt +25 -0
- data/README.md +35 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example/.gitattributes +8 -0
- data/example/.gitignore +24 -0
- data/example/Gemfile +19 -0
- data/example/Gemfile.lock +211 -0
- data/example/README.md +24 -0
- data/example/Rakefile +6 -0
- data/example/app/assets/config/manifest.js +2 -0
- data/example/app/assets/stylesheets/application.css +15 -0
- data/example/app/controllers/application_controller.rb +2 -0
- data/example/app/controllers/boops_controller.rb +10 -0
- data/example/app/helpers/application_helper.rb +2 -0
- data/example/app/models/application_record.rb +3 -0
- data/example/app/models/boop.rb +2 -0
- data/example/app/views/boops/index.html.erb +8 -0
- data/example/app/views/layouts/application.html.erb +15 -0
- data/example/bin/bundle +114 -0
- data/example/bin/rails +4 -0
- data/example/bin/rake +4 -0
- data/example/bin/setup +33 -0
- data/example/config.ru +6 -0
- data/example/config/application.rb +35 -0
- data/example/config/boot.rb +4 -0
- data/example/config/credentials.yml.enc +1 -0
- data/example/config/database.yml +86 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +60 -0
- data/example/config/environments/production.rb +96 -0
- data/example/config/environments/test.rb +49 -0
- data/example/config/initializers/application_controller_renderer.rb +8 -0
- data/example/config/initializers/backtrace_silencers.rb +8 -0
- data/example/config/initializers/content_security_policy.rb +28 -0
- data/example/config/initializers/cookies_serializer.rb +5 -0
- data/example/config/initializers/filter_parameter_logging.rb +6 -0
- data/example/config/initializers/inflections.rb +16 -0
- data/example/config/initializers/mime_types.rb +4 -0
- data/example/config/initializers/permissions_policy.rb +11 -0
- data/example/config/initializers/wrap_parameters.rb +14 -0
- data/example/config/locales/en.yml +33 -0
- data/example/config/puma.rb +43 -0
- data/example/config/routes.rb +5 -0
- data/example/db/migrate/20210417143158_create_boops.rb +7 -0
- data/example/db/schema.rb +21 -0
- data/example/db/seeds.rb +11 -0
- data/example/public/404.html +67 -0
- data/example/public/422.html +67 -0
- data/example/public/500.html +66 -0
- data/example/public/apple-touch-icon-precomposed.png +0 -0
- data/example/public/apple-touch-icon.png +0 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/robots.txt +1 -0
- data/example/test/application_system_test_case.rb +5 -0
- data/example/test/fixtures/boops.yml +5 -0
- data/example/test/integration/basic_boops_test.rb +18 -0
- data/example/test/integration/migrated_boops_test.rb +7 -0
- data/example/test/integration/parallel_boops_with_fixtures_test.rb +11 -0
- data/example/test/integration/parallel_boops_without_fixtures_test.rb +11 -0
- data/example/test/integration/updated_boops_test.rb +7 -0
- data/example/test/test_helper.rb +41 -0
- data/lib/generators/test_data/database_yaml_generator.rb +24 -0
- data/lib/generators/test_data/environment_file_generator.rb +29 -0
- data/lib/generators/test_data/webpacker_yaml_generator.rb +26 -0
- data/lib/test_data.rb +20 -0
- data/lib/test_data/active_support_ext.rb +9 -0
- data/lib/test_data/config.rb +52 -0
- data/lib/test_data/configuration_verification.rb +3 -0
- data/lib/test_data/configurators.rb +11 -0
- data/lib/test_data/configurators/database_yaml.rb +24 -0
- data/lib/test_data/configurators/environment_file.rb +25 -0
- data/lib/test_data/configurators/webpacker_yaml.rb +38 -0
- data/lib/test_data/detects_database_emptiness.rb +17 -0
- data/lib/test_data/dumps_database.rb +49 -0
- data/lib/test_data/error.rb +4 -0
- data/lib/test_data/installs_configuration.rb +9 -0
- data/lib/test_data/loads_database_dumps.rb +45 -0
- data/lib/test_data/railtie.rb +12 -0
- data/lib/test_data/rake.rb +104 -0
- data/lib/test_data/transactional_data_loader.rb +77 -0
- data/lib/test_data/verifies_configuration.rb +14 -0
- data/lib/test_data/verifies_dumps_are_loadable.rb +37 -0
- data/lib/test_data/version.rb +3 -0
- data/script/test +49 -0
- data/test_data.gemspec +27 -0
- 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,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,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
|