require "test/unit" require "fileutils" require "tmpdir" module Test module Unit class TestCase class << self def inherited(sub) super sub.instance_variable_set("@priority_initialized", true) sub.instance_variable_set("@priority_table", {}) sub.priority :normal end def include(*args) args.reverse_each do |mod| super(mod) next unless defined?(@priority_initialized) mod.instance_methods(false).each do |name| set_priority(name) end end end alias_method :method_added_without_priority, :method_added def method_added(name) method_added_without_priority(name) set_priority(name) if defined?(@priority_initialized) end def priority(name, *tests) singleton_class = (class << self; self; end) priority_check_method = priority_check_method_name(name) unless singleton_class.private_method_defined?(priority_check_method) raise ArgumentError, "unknown priority: #{name}" end if tests.empty? @current_priority = name else tests.each do |test| set_priority(test, name) end end end def need_to_run?(test_name) normalized_test_name = normalize_test_name(test_name) priority = @priority_table[normalized_test_name] return true unless priority __send__(priority_check_method_name(priority), test_name) end private def priority_check_method_name(priority_name) "run_priority_#{priority_name}?" end def normalize_test_name(test_name) "test_#{test_name.to_s.sub(/^test_/, '')}" end def set_priority(name, priority=@current_priority) @priority_table[normalize_test_name(name)] = priority end def run_priority_must?(test_name) true end def run_priority_important?(test_name) rand > 0.1 end def run_priority_high?(test_name) rand > 0.3 end def run_priority_normal?(test_name) rand > 0.5 end def run_priority_low?(test_name) rand > 0.75 end def run_priority_never?(test_name) false end end def need_to_run? !previous_test_success? or self.class.need_to_run?(@method_name) end alias_method :run_without_priority, :run def run(result, &block) run_without_priority(result, &block) ensure if passed? FileUtils.touch(passed_file) else FileUtils.rm_f(passed_file) end end private def previous_test_success? File.exist?(passed_file) end def result_dir components = [".test-result", self.class.name, @method_name.to_s] parent_directories = [File.dirname($0), File.join(File.dirname(__FILE__), ".."), Dir.pwd, File.join(Dir.tmpdir, Process.uid.to_s)] parent_directories.each do |parent_directory| dir = File.expand_path(File.join(parent_directory, *components)) begin FileUtils.mkdir_p(dir) return dir rescue Errno::EACCES end end raise Errno::EACCES, parent_directories.join(", ") end def passed_file File.join(result_dir, "passed") end def escaped_method_name @method_name.to_s.gsub(/[!?]$/) do |matched| case matched when "!" ".destructive" when "?" ".predicate" end end end end class TestSuite @@priority_mode = false class << self def priority_mode=(bool) @@priority_mode = bool end end alias_method :run_without_priority_support, :run def run(*args, &block) priority_mode = @@priority_mode if priority_mode @original_tests = @tests apply_priority end run_without_priority_support(*args, &block) ensure @tests = @original_tests if priority_mode end def apply_priority @tests = @tests.reject {|test| !test.need_to_run?} end def need_to_run? apply_priority !@tests.empty? end end class AutoRunner alias_method :options_without_priority, :options def options opts = options_without_priority opts.on("--[no-]priority", "use priority mode") do |bool| TestSuite.priority_mode = bool end opts end end end end