require 'optparse' require 'minitest/unit' module Rails # Handles all logic behind +rails test+ command. class TestRunner class << self # Creates a new +TestRunner+ object with an array of test files to run # based on the arguments. When no arguments are provided, it runs all test # files. When a suite argument is provided, it runs only the test files in # that suite. Otherwise, it runs the specified test file(s). def start(files, options = {}) original_fixtures_options = options.delete(:fixtures) options[:fixtures] = true case files.first when nil new(Dir['test/**/*_test.rb'], options).run when 'models' new(Dir['test/models/**/*_test.rb'], options).run when 'helpers' new(Dir['test/helpers/**/*_test.rb'], options).run when 'units' new(Dir['test/{models,helpers,unit}/**/*_test.rb'], options).run when 'controllers' new(Dir['test/controllers/**/*_test.rb'], options).run when 'mailers' new(Dir['test/mailers/**/*_test.rb'], options).run when 'functionals' new(Dir['test/{controllers,mailers,functional}/**/*_test.rb'], options).run when 'integration' new(Dir['test/integration/**/*_test.rb'], options).run else options[:fixtures] = original_fixtures_options new(files, options).run end end # Parses arguments and sets them as option flags def parse_arguments(arguments) options = {} orig_arguments = arguments.dup OptionParser.new do |opts| opts.banner = "Usage: rails test [path to test file(s) or test suite]" opts.separator "" opts.separator "Run a specific test file(s) or a test suite, under Rails'" opts.separator "environment. If the file name(s) or suit name is omitted," opts.separator "Rails will run all tests." opts.separator "" opts.separator "Specific options:" opts.on '-h', '--help', 'Display this help.' do puts opts exit end opts.on '-e', '--environment NAME', String, 'Specifies the environment to run this test under' do |e| options[:environment] = e end opts.on '-f', '--fixtures', 'Load fixtures in test/fixtures/ before running the tests' do options[:fixtures] = true end opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| options[:seed] = m.to_i end opts.on '-v', '--verbose', "Verbose. Show progress processing files." do options[:verbose] = true end opts.on '-n', '--name PATTERN', "Filter test names on pattern (e.g. /foo/)" do |n| options[:filter] = n end opts.separator "" opts.separator "Support types of test suites:" opts.separator "-------------------------------------------------------------" opts.separator "* models (test/models/**/*)" opts.separator "* helpers (test/helpers/**/*)" opts.separator "* units (test/{models,helpers,unit}/**/*" opts.separator "* controllers (test/controllers/**/*)" opts.separator "* mailers (test/mailers/**/*)" opts.separator "* functionals (test/{controllers,mailers,functional}/**/*)" opts.separator "* integration (test/integration/**/*)" opts.separator "-------------------------------------------------------------" opts.parse! arguments orig_arguments -= arguments end options end end # Creates a new +TestRunner+ object with a list of test file paths. def initialize(files, options) @files = files Rails.application.load_tasks Rake::Task['db:test:load'].invoke if options.delete(:fixtures) if defined?(ActiveRecord::Base) ActiveSupport::TestCase.send :include, ActiveRecord::TestFixtures ActiveSupport::TestCase.fixture_path = "#{Rails.root}/test/fixtures/" ActiveSupport::TestCase.fixtures :all end end MiniTest::Unit.runner.options = options MiniTest::Unit.output = SilentUntilSyncStream.new(MiniTest::Unit.output) end # Runs test files by evaluating each of them. def run @files.each { |filename| load(filename) } end # A null stream object which ignores everything until +sync+ has been set # to true. This is only used to silence unnecessary output from MiniTest, # as MiniTest calls +output.sync = true+ right before it outputs the first # test result. class SilentUntilSyncStream < File # Creates a +SilentUntilSyncStream+ object by giving it a target stream # object that will be assigned to +MiniTest::Unit.output+ after +sync+ is # set to true. def initialize(target_stream) @target_stream = target_stream super(File::NULL, 'w') end # Swaps +MiniTest::Unit.output+ to another stream when +sync+ is true. def sync=(sync) if sync @target_stream.sync = true MiniTest::Unit.output = @target_stream end super end end end end