spec: make specs resilient to pre-existing records, refs SD-1570
in jenkins-land we've historically used db snapshots so the db will be empty. but if you `rake db:migrate` your test db, you'll have things like default accounts, notifications, etc., which can break certain specs Change-Id: I175e651d1b50671e630dc0181d1c4d25e78abe97 Reviewed-on: https://gerrit.instructure.com/95249 Reviewed-by: Landon Wilkins <lwilkins@instructure.com> Product-Review: Landon Wilkins <lwilkins@instructure.com> QA-Review: Landon Wilkins <lwilkins@instructure.com> Tested-by: Jenkins
This commit is contained in:
parent
26242baa7f
commit
82eac2107c
|
@ -60,19 +60,15 @@ module WebMock::API
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ensure people aren't creating records outside the rspec lifecycle, e.g.
|
# nuke the db (say, if `rake db:migrate RAILS_ENV=test` created records),
|
||||||
# inside a describe/context block rather than a let/before/example
|
# and then ensure people aren't creating records outside the rspec
|
||||||
|
# lifecycle, e.g. inside a describe/context block rather than a
|
||||||
|
# let/before/example
|
||||||
require_relative 'support/blank_slate_protection'
|
require_relative 'support/blank_slate_protection'
|
||||||
BlankSlateProtection.enable!
|
BlankSlateProtection.install!
|
||||||
|
|
||||||
require_relative 'support/discourage_slow_specs'
|
require_relative 'support/discourage_slow_specs'
|
||||||
|
|
||||||
RSpec::Core::ExampleGroup.singleton_class.prepend(Module.new {
|
|
||||||
def run_examples(*)
|
|
||||||
BlankSlateProtection.disable { super }
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
|
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
|
||||||
|
|
||||||
ActionView::TestCase::TestController.view_paths = ApplicationController.view_paths
|
ActionView::TestCase::TestController.view_paths = ApplicationController.view_paths
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
require_relative "./call_stack_utils"
|
require_relative "./call_stack_utils"
|
||||||
|
|
||||||
module BlankSlateProtection
|
module BlankSlateProtection
|
||||||
def create_or_update
|
module ActiveRecord
|
||||||
return super unless BlankSlateProtection.enabled?
|
def create_or_update
|
||||||
return super if caller.grep(BlankSlateProtection.exempt_patterns).present?
|
return super unless BlankSlateProtection.enabled?
|
||||||
|
return super if caller.grep(BlankSlateProtection.exempt_patterns).present?
|
||||||
|
|
||||||
location = CallStackUtils.best_line_for(caller).sub(/:in .*/, '')
|
location = CallStackUtils.best_line_for(caller).sub(/:in .*/, '')
|
||||||
if caller.grep(/_context_hooks/).present?
|
if caller.grep(/_context_hooks/).present?
|
||||||
$stderr.puts "\e[31mError: Don't create records inside `:all` hooks!"
|
$stderr.puts "\e[31mError: Don't create records inside `:all` hooks!"
|
||||||
$stderr.puts "See: " + location + "\e[0m"
|
$stderr.puts "See: " + location + "\e[0m"
|
||||||
|
$stderr.puts
|
||||||
|
$stderr.puts "\e[33mTIP:\e[0m change this to `:each`, or if you are really concerned"
|
||||||
|
$stderr.puts "about performance, use `:once`. `:all` hooks are dangerous because"
|
||||||
|
$stderr.puts "they can leave around garbage that affects later specs"
|
||||||
|
else
|
||||||
|
$stderr.puts "\e[31mError: Don't create records outside the rspec lifecycle!"
|
||||||
|
$stderr.puts "See: " + location + "\e[0m"
|
||||||
|
$stderr.puts
|
||||||
|
$stderr.puts "\e[33mTIP:\e[0m move this into a `before`, `let` or `it`. Otherwise it will exist"
|
||||||
|
$stderr.puts "before *any* specs start, and possibly be deleted/modified before the"
|
||||||
|
$stderr.puts "spec that needs it actually runs."
|
||||||
|
end
|
||||||
$stderr.puts
|
$stderr.puts
|
||||||
$stderr.puts "\e[33mTIP:\e[0m change this to `:each`, or if you are really concerned"
|
exit! 1
|
||||||
$stderr.puts "about performance, use `:once`. `:all` hooks are dangerous because"
|
end
|
||||||
$stderr.puts "they can leave around garbage that affects later specs"
|
end
|
||||||
else
|
|
||||||
$stderr.puts "\e[31mError: Don't create records outside the rspec lifecycle!"
|
module ExampleGroup
|
||||||
$stderr.puts "See: " + location + "\e[0m"
|
def run_examples(*)
|
||||||
$stderr.puts
|
BlankSlateProtection.disable { super }
|
||||||
$stderr.puts "\e[33mTIP:\e[0m move this into a `before`, `let` or `it`. Otherwise it will exist"
|
|
||||||
$stderr.puts "before *any* specs start, and possibly be deleted/modified before the"
|
|
||||||
$stderr.puts "spec that needs it actually runs."
|
|
||||||
end
|
end
|
||||||
$stderr.puts
|
|
||||||
exit! 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# switchman and once-ler have special snowflake context hooks where data
|
# switchman and once-ler have special snowflake context hooks where data
|
||||||
|
@ -34,26 +42,51 @@ module BlankSlateProtection
|
||||||
@enabled
|
@enabled
|
||||||
end
|
end
|
||||||
|
|
||||||
def enable!
|
def install!
|
||||||
|
truncate_all_tables!
|
||||||
|
::RSpec::Core::ExampleGroup.singleton_class.prepend ExampleGroup
|
||||||
|
::ActiveRecord::Base.include ActiveRecord
|
||||||
@enabled = true
|
@enabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable!
|
|
||||||
@enabled = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def disable
|
def disable
|
||||||
enabled = @enabled
|
@enabled = false
|
||||||
disable!
|
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
@enabled = enabled
|
@enabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
def exempt_patterns
|
def exempt_patterns
|
||||||
Regexp.new(EXEMPT_PATTERNS.map { |pattern| Regexp.escape(pattern) }.join("|"))
|
Regexp.new(EXEMPT_PATTERNS.map { |pattern| Regexp.escape(pattern) }.join("|"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_table_names(connection)
|
||||||
|
# use custom SQL to exclude tables from extensions
|
||||||
|
schema = connection.shard.name if connection.instance_variable_get(:@config)[:use_qualified_names]
|
||||||
|
table_names = connection.query(<<-SQL, 'SCHEMA').map(&:first)
|
||||||
|
SELECT relname
|
||||||
|
FROM pg_class INNER JOIN pg_namespace ON relnamespace=pg_namespace.oid
|
||||||
|
WHERE nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
||||||
|
AND relkind='r'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_depend WHERE deptype='e' AND objid=pg_class.oid
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
table_names.delete('schema_migrations')
|
||||||
|
table_names
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_all_tables!(quick: true)
|
||||||
|
return if quick && Account.all.empty? # this is the most likely table to have stuff
|
||||||
|
puts "truncating all tables..."
|
||||||
|
Shard.with_each_shard do
|
||||||
|
model_connections = ::ActiveRecord::Base.descendants.map(&:connection).uniq
|
||||||
|
model_connections.each do |connection|
|
||||||
|
table_names = get_table_names(connection)
|
||||||
|
next if table_names.empty?
|
||||||
|
connection.execute("TRUNCATE TABLE #{table_names.map { |t| connection.quote_table_name(t) }.join(',')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.include BlankSlateProtection
|
|
||||||
|
|
Loading…
Reference in New Issue