forked from fixin.me/fixin.me
Compare commits
2 Commits
16af631fae
...
feature/ex
| Author | SHA1 | Date | |
|---|---|---|---|
| badb4b336c | |||
| 3e0f27b357 |
@@ -48,3 +48,24 @@ production:
|
|||||||
#test:
|
#test:
|
||||||
# <<: *default
|
# <<: *default
|
||||||
# database: fixinme_test
|
# database: fixinme_test
|
||||||
|
|
||||||
|
# Multi-database testing
|
||||||
|
# ----------------------
|
||||||
|
# Any key starting with "test" is treated as a test database.
|
||||||
|
# When more than one is present, EVERY test task (rails test, rails test:models,
|
||||||
|
# rails test:system, …) automatically runs against all of them.
|
||||||
|
#
|
||||||
|
# The adapter gem must be available:
|
||||||
|
# bundle config --local with "mysql sqlite" # mysql + sqlite
|
||||||
|
# bundle config --local with "mysql pg" # mysql + postgresql
|
||||||
|
#
|
||||||
|
#test_sqlite:
|
||||||
|
# adapter: sqlite3
|
||||||
|
# database: db/fixinme_test.sqlite3
|
||||||
|
#
|
||||||
|
#test_pg:
|
||||||
|
# adapter: postgresql
|
||||||
|
# database: fixinme_test
|
||||||
|
# username: fixinme
|
||||||
|
# password: Some-password1%
|
||||||
|
# host: localhost
|
||||||
|
|||||||
@@ -58,7 +58,4 @@ Rails.application.configure do
|
|||||||
# config.action_view.annotate_rendered_view_with_filenames = true
|
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||||
|
|
||||||
config.log_level = :info
|
config.log_level = :info
|
||||||
|
|
||||||
# Allow Capybara's dynamic test server host (127.0.0.1:<random_port>)
|
|
||||||
config.hosts << '127.0.0.1'
|
|
||||||
end
|
end
|
||||||
|
|||||||
164
lib/tasks/test_databases.rake
Normal file
164
lib/tasks/test_databases.rake
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
require 'uri'
|
||||||
|
|
||||||
|
# Multi-database test runner
|
||||||
|
# ==========================
|
||||||
|
# When database.yml contains more than one `test*` configuration, every
|
||||||
|
# standard test task (test, test:models, test:system, …) is automatically
|
||||||
|
# rewritten to run the full suite against EACH configured database in turn.
|
||||||
|
#
|
||||||
|
# Convention — any top-level key that starts with "test" and holds a Hash:
|
||||||
|
#
|
||||||
|
# test: ← required primary database
|
||||||
|
# adapter: mysql2
|
||||||
|
# database: fixinme_test
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# test_sqlite: ← optional additional databases
|
||||||
|
# adapter: sqlite3
|
||||||
|
# database: db/fixinme_test.sqlite3
|
||||||
|
#
|
||||||
|
# test_pg:
|
||||||
|
# adapter: postgresql
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# A single-database setup is unchanged: every task behaves exactly as before.
|
||||||
|
#
|
||||||
|
# Database configuration is read via Rails.application.config.database_configuration
|
||||||
|
# so ERB interpolation and all Rails path overrides are respected automatically.
|
||||||
|
# Each subprocess receives DATABASE_URL built from the selected config, which
|
||||||
|
# Rails merges on top of the test: entry from database.yml — no temporary files needed.
|
||||||
|
|
||||||
|
module MultiDbTests
|
||||||
|
ADAPTER_GEMS = {
|
||||||
|
'mysql2' => 'mysql2',
|
||||||
|
'sqlite3' => 'sqlite3',
|
||||||
|
'postgresql' => 'pg',
|
||||||
|
'pg' => 'pg',
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
ADAPTER_BUNDLE_GROUPS = {
|
||||||
|
'mysql2' => 'mysql',
|
||||||
|
'sqlite3' => 'sqlite',
|
||||||
|
'postgresql' => 'postgresql',
|
||||||
|
'pg' => 'postgresql',
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# Rake task names generated by railties/lib/rails/test_unit/testing.rake
|
||||||
|
# that use run_from_rake — these are the ones we rewrite.
|
||||||
|
WRAPPED_TASKS = (
|
||||||
|
['test'] +
|
||||||
|
Rails::TestUnit::Runner::TEST_FOLDERS.map { |f| "test:#{f}" } +
|
||||||
|
%w[test:all test:system test:generators test:units test:functionals]
|
||||||
|
).freeze
|
||||||
|
|
||||||
|
class << self
|
||||||
|
# Returns {name => config_hash} for every key starting with "test".
|
||||||
|
def test_configs
|
||||||
|
@test_configs ||= Rails.application.config.database_configuration
|
||||||
|
.select { |k, v| k.to_s.start_with?('test') && v.is_a?(Hash) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run rails +task_name+ for every configured test database.
|
||||||
|
# Called from the rewritten rake task actions.
|
||||||
|
def run(task_name)
|
||||||
|
cfgs = test_configs
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
cfgs.each do |db_name, config|
|
||||||
|
adapter = config['adapter'].to_s
|
||||||
|
puts "\n#{'─' * 64}"
|
||||||
|
puts " #{task_name} · #{db_name} (#{adapter})"
|
||||||
|
puts '─' * 64
|
||||||
|
|
||||||
|
unless adapter_available?(adapter)
|
||||||
|
warn " SKIPPED — '#{adapter}' gem not in bundle.\n" \
|
||||||
|
" Run: bundle config --local with \"#{current_with} #{adapter_group(adapter)}\""
|
||||||
|
results[db_name] = :skipped
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
env = { 'DATABASE_URL' => config_to_url(config) }
|
||||||
|
|
||||||
|
if system(env, 'bundle exec rails db:test:prepare')
|
||||||
|
results[db_name] = system(env, 'rails', task_name) ? :pass : :fail
|
||||||
|
else
|
||||||
|
results[db_name] = :prepare_failed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print_summary(cfgs, results)
|
||||||
|
|
||||||
|
failed = results.count { |_, s| [:fail, :prepare_failed].include?(s) }
|
||||||
|
exit(false) if failed > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Build a DATABASE_URL string from a database.yml config hash.
|
||||||
|
# Rails merges DATABASE_URL on top of the test: entry, so adapter and
|
||||||
|
# connection details from the URL take precedence over the YAML file.
|
||||||
|
def config_to_url(config)
|
||||||
|
adapter = config['adapter'].to_s
|
||||||
|
database = config['database'].to_s
|
||||||
|
|
||||||
|
return "sqlite3:#{database}" if adapter == 'sqlite3'
|
||||||
|
|
||||||
|
user = config['username']
|
||||||
|
pass = config['password']
|
||||||
|
host = config['host'] || 'localhost'
|
||||||
|
port = config['port']
|
||||||
|
socket = config['socket']
|
||||||
|
|
||||||
|
userinfo = user ? "#{URI.encode_www_form_component(user)}:#{URI.encode_www_form_component(pass.to_s)}@" : ''
|
||||||
|
hostport = port ? "#{host}:#{port}" : host
|
||||||
|
url = "#{adapter}://#{userinfo}#{hostport}/#{database}"
|
||||||
|
url += "?socket=#{URI.encode_www_form_component(socket)}" if socket
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
|
def adapter_available?(adapter)
|
||||||
|
require ADAPTER_GEMS.fetch(adapter, adapter)
|
||||||
|
true
|
||||||
|
rescue LoadError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def adapter_group(adapter)
|
||||||
|
ADAPTER_BUNDLE_GROUPS.fetch(adapter, adapter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_with
|
||||||
|
(Bundler.settings[:with] || '').split(':').join(' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def print_summary(cfgs, results)
|
||||||
|
return if results.size <= 1 # no summary for single-DB runs
|
||||||
|
|
||||||
|
puts "\n#{'═' * 64}"
|
||||||
|
puts ' SUMMARY'
|
||||||
|
puts '═' * 64
|
||||||
|
results.each do |db_name, status|
|
||||||
|
adapter = cfgs.dig(db_name, 'adapter') || '?'
|
||||||
|
icon = status == :pass ? '✓' : (status == :skipped ? '–' : '✗')
|
||||||
|
label = { pass: 'PASS', fail: 'FAIL',
|
||||||
|
prepare_failed: 'PREPARE FAILED', skipped: 'SKIPPED' }[status]
|
||||||
|
puts " #{icon} #{db_name.ljust(26)} (#{adapter.ljust(12)}) #{label}"
|
||||||
|
end
|
||||||
|
puts '═' * 64
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rewrite every standard test task to run against all configured databases.
|
||||||
|
# This file loads after railties/testing.rake, so all tasks already exist.
|
||||||
|
# Single-database setups are completely unaffected.
|
||||||
|
if MultiDbTests.test_configs.size > 1
|
||||||
|
MultiDbTests::WRAPPED_TASKS.each do |task_name|
|
||||||
|
next unless Rake::Task.task_defined?(task_name)
|
||||||
|
|
||||||
|
Rake::Task[task_name].clear_actions
|
||||||
|
Rake::Task[task_name].enhance do
|
||||||
|
MultiDbTests.run(task_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -229,7 +229,7 @@ class UsersTest < ApplicationSystemTestCase
|
|||||||
user = User.find_by_email!(first(:link).text)
|
user = User.find_by_email!(first(:link).text)
|
||||||
inject_button_to first('td:not(.link)'), "update status", user_path(user), method: :patch,
|
inject_button_to first('td:not(.link)'), "update status", user_path(user), method: :patch,
|
||||||
params: {user: {status: User.statuses.keys.sample}}, data: {turbo: false}
|
params: {user: {status: User.statuses.keys.sample}}, data: {turbo: false}
|
||||||
execute_script("arguments[0].click()", find_button("update status"))
|
click_on "update status"
|
||||||
end
|
end
|
||||||
assert_title 'The change you wanted was rejected (422)'
|
assert_title 'The change you wanted was rejected (422)'
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user