We run a large app and test times even with spring where around 6s, so I fixed that 🙂
- Preload environment
- Preload test_helper via forking_test_runner
- Disable slow at_exit handler
- Enable by line running via testrbl
- Preload fixtures
- Turn logging on by default
# config/environment.rb
Spring::Commands::Test.after_environment if ENV['SPRING_PRELOADED']
# config/spring.rb
if defined?(Spring)
raise "Spring is out of date: gem install spring" if Gem::Version.new(Spring::VERSION) < Gem::Version.new("1.3.6")
ENV['SPRING_LOG'] = 'log/spring.log' # turn logging mode on
ENV['SPRING_PRELOADED'] = 'true'
class Spring::Commands::Test
def env(*)
"test"
end
def call
# running by line number ?
if ARGV.first =~ /^(\S+):(\d+)$/
file, line = $1, $2
pattern = Testrbl.pattern_from_file(File.readlines(file), line)
ARGV[0..0] = [file, "-n", "/#{pattern}/"]
end
# running with --changed flag ?
if ARGV.delete("--changed")
ARGV[0...0] = Testrbl.send(:changed_files)
end
# load all the tests
ForkingTestRunner.send(:enable_test_autorun, 'lib/slug_ids') # require an innocent files since we handle require ourselves
ARGV.each do |arg|
break if arg.start_with?("-")
require_test(File.expand_path(arg))
end
end
def description
<<-USAGE
Run a test.
test/unit/xxx_test.rb:123 # test by line number
test/unit # everything _test.rb in a folder (on 1.8 this would be test/unit/*)
xxx_test.rb yyy_test.rb # multiple files
--changed # run changed tests
test/unit/xxx_test.rb -n "/hello/" # test by name
USAGE
end
# we need to do some initialization after everything is loaded
# otherwise we end up with for example extra tests running because urls are not cleaned out
def self.after_environment
return unless Rails.env.test?
require 'testrbl'
# preload as much of the test environment as possible for fast test startup
require "forking_test_runner"
ForkingTestRunner.send(:disable_test_autorun)
require_relative '../test/helpers/test_helper'
if User.count.zero?
puts "No fixture data loaded. preload using rake db:fixtures:load"
puts "Falling back to slower fixture loading strategy..."
else
ForkingTestRunner.send(:preload_fixtures)
end
# make test process not hang 2+s after it is done with tests
# minitest calls at_exit twice and we need to hook in after the second
# this might lead to some tempfiles pollution or logs missing ... time will tell
require 'minitest/unit'
class << MiniTest::Unit
def at_exit(&block)
@at_exit_called ||= 0
@at_exit_called += 1
super
if @at_exit_called == 2
super do
status = (($!.respond_to?(:status) && $!.status) || 1)
exit! status
end
end
end
end
end
private
def require_test(path)
if File.directory?(path)
Dir[File.join path, "**", "*_test.rb"].each { |f| require f }
else
require path
end
end
end
Spring.register_command "test", Spring::Commands::Test.new
end