@@ -1,164 +0,0 @@
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