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