From 3b35de2a90e26b99e2a6d4f61dc56d6ce7ded748 Mon Sep 17 00:00:00 2001 From: Anas Nashif Date: Tue, 6 Nov 2012 11:57:02 -0800 Subject: Imported Upstream version 1.9.3.p194 --- lib/test/unit.rb | 640 ++++++++++++++++++++++++++++++++++++++++++++ lib/test/unit/assertions.rb | 324 ++++++++++++++++++++++ lib/test/unit/parallel.rb | 157 +++++++++++ lib/test/unit/testcase.rb | 25 ++ 4 files changed, 1146 insertions(+) create mode 100644 lib/test/unit.rb create mode 100644 lib/test/unit/assertions.rb create mode 100644 lib/test/unit/parallel.rb create mode 100644 lib/test/unit/testcase.rb (limited to 'lib/test') diff --git a/lib/test/unit.rb b/lib/test/unit.rb new file mode 100644 index 0000000..09708d1 --- /dev/null +++ b/lib/test/unit.rb @@ -0,0 +1,640 @@ +# test/unit compatibility layer using minitest. + +require 'minitest/unit' +require 'test/unit/assertions' +require 'test/unit/testcase' +require 'optparse' + +module Test + module Unit + TEST_UNIT_IMPLEMENTATION = 'test/unit compatibility layer using minitest' + + module RunCount + @@run_count = 0 + + def self.have_run? + @@run_count.nonzero? + end + + def run(*) + @@run_count += 1 + super + end + + def run_once + return if have_run? + return if $! # don't run if there was an exception + yield + end + module_function :run_once + end + + module Options + def initialize(*, &block) + @init_hook = block + @options = nil + super(&nil) + end + + def option_parser + @option_parser ||= OptionParser.new + end + + def process_args(args = []) + return @options if @options + orig_args = args.dup + options = {} + opts = option_parser + setup_options(opts, options) + opts.parse!(args) + orig_args -= args + args = @init_hook.call(args, options) if @init_hook + non_options(args, options) + @help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " " + @options = options + if @options[:parallel] + @files = args + @args = orig_args + end + options + end + + private + def setup_options(opts, options) + opts.separator 'minitest options:' + opts.version = MiniTest::Unit::VERSION + + opts.on '-h', '--help', 'Display this help.' do + puts opts + exit + end + + opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| + options[:seed] = m + end + + opts.on '-v', '--verbose', "Verbose. Show progress processing files." do + options[:verbose] = true + self.verbose = options[:verbose] + end + + opts.on '-n', '--name PATTERN', "Filter test names on pattern." do |a| + options[:filter] = a + end + + opts.on '--jobs-status [TYPE]', [:normal, :replace], + "Show status of jobs every file; Disabled when --jobs isn't specified." do |type| + options[:job_status] = type || :normal + end + + opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a| + if /^t/ =~ a + options[:testing] = true # For testing + options[:parallel] = a[1..-1].to_i + else + options[:parallel] = a.to_i + end + end + + opts.on '--no-retry', "Don't retry running testcase when --jobs specified" do + options[:no_retry] = true + end + + opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a| + options[:ruby] = a.split(/ /).reject(&:empty?) + end + + opts.on '-q', '--hide-skip', 'Hide skipped tests' do + options[:hide_skip] = true + end + end + + def non_options(files, options) + begin + require "rbconfig" + rescue LoadError + warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument" + options[:parallel] = nil + else + options[:ruby] ||= RbConfig.ruby + end + + true + end + end + + module GlobOption + include Options + + @@testfile_prefix = "test" + + def setup_options(parser, options) + super + parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir| + options[:base_directory] = dir + end + parser.on '-x', '--exclude PATTERN', 'Exclude test files on pattern.' do |pattern| + (options[:reject] ||= []) << pattern + end + end + + def non_options(files, options) + paths = [options.delete(:base_directory), nil].uniq + if reject = options.delete(:reject) + reject_pat = Regexp.union(reject.map {|r| /#{r}/ }) + end + files.map! {|f| + f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR + ((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix| + if prefix + path = f.empty? ? prefix : "#{prefix}/#{f}" + else + next if f.empty? + path = f + end + if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty? + if reject + match.reject! {|n| + n[(prefix.length+1)..-1] if prefix + reject_pat =~ n + } + end + break match + elsif !reject or reject_pat !~ f and File.exist? path + break path + end + end or + raise ArgumentError, "file not found: #{f}" + } + files.flatten! + super(files, options) + end + end + + module LoadPathOption + include Options + + def setup_options(parser, options) + super + parser.on '-Idirectory', 'Add library load path' do |dirs| + dirs.split(':').each { |d| $LOAD_PATH.unshift d } + end + end + end + + module GCStressOption + def setup_options(parser, options) + super + parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag| + options[:gc_stress] = flag + end + end + + def non_options(files, options) + if options.delete(:gc_stress) + MiniTest::Unit::TestCase.class_eval do + oldrun = instance_method(:run) + define_method(:run) do |runner| + begin + gc_stress, GC.stress = GC.stress, true + oldrun.bind(self).call(runner) + ensure + GC.stress = gc_stress + end + end + end + end + super + end + end + + module RequireFiles + def non_options(files, options) + return false if !super + result = false + files.each {|f| + d = File.dirname(path = File.expand_path(f)) + unless $:.include? d + $: << d + end + begin + require path unless options[:parallel] + result = true + rescue LoadError + puts "#{f}: #{$!}" + end + } + result + end + end + + class Runner < MiniTest::Unit + include Test::Unit::Options + include Test::Unit::GlobOption + include Test::Unit::LoadPathOption + include Test::Unit::GCStressOption + include Test::Unit::RunCount + + class Worker + def self.launch(ruby,args=[]) + io = IO.popen([*ruby, + "#{File.dirname(__FILE__)}/unit/parallel.rb", + *args], "rb+") + new(io, io.pid, :waiting) + end + + def initialize(io, pid, status) + @io = io + @pid = pid + @status = status + @file = nil + @real_file = nil + @loadpath = [] + @hooks = {} + end + + def puts(*args) + @io.puts(*args) + end + + def run(task,type) + @file = File.basename(task).gsub(/\.rb/,"") + @real_file = task + begin + puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m").gsub("\n","")}" + @loadpath = $:.dup + puts "run #{task} #{type}" + @status = :prepare + rescue Errno::EPIPE + died + rescue IOError + raise unless ["stream closed","closed stream"].include? $!.message + died + end + end + + def hook(id,&block) + @hooks[id] ||= [] + @hooks[id] << block + self + end + + def read + res = (@status == :quit) ? @io.read : @io.gets + res && res.chomp + end + + def close + @io.close + self + end + + def died(*additional) + @status = :quit + @io.close + + call_hook(:dead,*additional) + end + + def to_s + if @file + "#{@pid}=#{@file}" + else + "#{@pid}:#{@status.to_s.ljust(7)}" + end + end + + attr_reader :io, :pid + attr_accessor :status, :file, :real_file, :loadpath + + private + + def call_hook(id,*additional) + @hooks[id] ||= [] + @hooks[id].each{|hook| hook[self,additional] } + self + end + + end + + class << self; undef autorun; end + + @@stop_auto_run = false + def self.autorun + at_exit { + Test::Unit::RunCount.run_once { + exit(Test::Unit::Runner.new.run(ARGV) || true) + } unless @@stop_auto_run + } unless @@installed_at_exit + @@installed_at_exit = true + end + + def after_worker_down(worker, e=nil, c=false) + return unless @options[:parallel] + return if @interrupt + if e + b = e.backtrace + warn "#{b.shift}: #{e.message} (#{e.class})" + STDERR.print b.map{|s| "\tfrom #{s}"}.join("\n") + end + @need_quit = true + warn "" + warn "Some worker was crashed. It seems ruby interpreter's bug" + warn "or, a bug of test/unit/parallel.rb. try again without -j" + warn "option." + warn "" + STDERR.flush + exit c + end + + def jobs_status + return unless @options[:job_status] + puts "" unless @options[:verbose] + status_line = @workers.map(&:to_s).join(" ") + if @options[:job_status] == :replace and $stdout.tty? + @terminal_width ||= + begin + require 'io/console' + $stdout.winsize[1] + rescue LoadError, NoMethodError + ENV["COLUMNS"].to_i.nonzero? || 80 + end + @jstr_size ||= 0 + del_jobs_status + $stdout.flush + print status_line[0...@terminal_width] + $stdout.flush + @jstr_size = [status_line.size, @terminal_width].min + else + puts status_line + end + end + + def del_jobs_status + return unless @options[:job_status] == :replace && @jstr_size.nonzero? + print "\r"+" "*@jstr_size+"\r" + end + + def after_worker_quit(worker) + return unless @options[:parallel] + return if @interrupt + @workers.delete(worker) + @dead_workers << worker + @ios = @workers.map(&:io) + end + + def _run_parallel suites, type, result + if @options[:parallel] < 1 + warn "Error: parameter of -j option should be greater than 0." + return + end + + begin + # Require needed things for parallel running + require 'thread' + require 'timeout' + @tasks = @files.dup # Array of filenames. + @need_quit = false + @dead_workers = [] # Array of dead workers. + @warnings = [] + shutting_down = false + rep = [] # FIXME: more good naming + + # Array of workers. + @workers = @options[:parallel].times.map { + worker = Worker.launch(@options[:ruby],@args) + worker.hook(:dead) do |w,info| + after_worker_quit w + after_worker_down w, *info unless info.empty? + end + worker + } + + # Thread: watchdog + watchdog = Thread.new do + while stat = Process.wait2 + break if @interrupt # Break when interrupt + pid, stat = stat + w = (@workers + @dead_workers).find{|x| pid == x.pid }.dup + next unless w + unless w.status == :quit + # Worker down + w.died(nil, !stat.signaled? && stat.exitstatus) + end + end + end + + @workers_hash = Hash[@workers.map {|w| [w.io,w] }] # out-IO => worker + @ios = @workers.map{|w| w.io } # Array of worker IOs + + while _io = IO.select(@ios)[0] + break unless _io.each do |io| + break if @need_quit + worker = @workers_hash[io] + case worker.read + when /^okay$/ + worker.status = :running + jobs_status + when /^ready$/ + worker.status = :ready + if @tasks.empty? + break unless @workers.find{|x| x.status == :running } + else + worker.run(@tasks.shift, type) + end + + jobs_status + when /^done (.+?)$/ + r = Marshal.load($1.unpack("m")[0]) + result << r[0..1] unless r[0..1] == [nil,nil] + rep << {file: worker.real_file, + report: r[2], result: r[3], testcase: r[5]} + $:.push(*r[4]).uniq! + when /^p (.+?)$/ + del_jobs_status + print $1.unpack("m")[0] + jobs_status if @options[:job_status] == :replace + when /^after (.+?)$/ + @warnings << Marshal.load($1.unpack("m")[0]) + when /^bye (.+?)$/ + after_worker_down worker, Marshal.load($1.unpack("m")[0]) + when /^bye$/ + if shutting_down + after_worker_quit worker + else + after_worker_down worker + end + end + break if @need_quit + end + end + rescue Interrupt => e + @interrupt = e + return result + ensure + shutting_down = true + + watchdog.kill if watchdog + if @interrupt + @ios.select!{|x| @workers_hash[x].status == :running } + while !@ios.empty? && (__io = IO.select(@ios,[],[],10)) + _io = __io[0] + _io.each do |io| + worker = @workers_hash[io] + case worker.read + when /^done (.+?)$/ + r = Marshal.load($1.unpack("m")[0]) + result << r[0..1] unless r[0..1] == [nil,nil] + rep << {file: worker.real_file, + report: r[2], result: r[3], testcase: r[5]} + $:.push(*r[4]).uniq! + @ios.delete(io) + end + end + end + end + @workers.each do |worker| + begin + timeout(1) do + worker.puts "quit" + end + rescue Errno::EPIPE + rescue Timeout::Error + end + worker.close + end + begin + timeout(0.2*@workers.size) do + Process.waitall + end + rescue Timeout::Error + @workers.each do |worker| + begin + Process.kill(:KILL,worker.pid) + rescue Errno::ESRCH; end + end + end + + if @interrupt || @options[:no_retry] || @need_quit + rep.each do |r| + report.push(*r[:report]) + end + @errors += rep.map{|x| x[:result][0] }.inject(:+) + @failures += rep.map{|x| x[:result][1] }.inject(:+) + @skips += rep.map{|x| x[:result][2] }.inject(:+) + else + puts "" + puts "Retrying..." + puts "" + rep.each do |r| + if r[:testcase] && r[:file] && !r[:report].empty? + require r[:file] + _run_suite(eval(r[:testcase]),type) + else + report.push(*r[:report]) + @errors += r[:result][0] + @failures += r[:result][1] + @skips += r[:result][2] + end + end + end + if @warnings + warn "" + ary = [] + @warnings.reject! do |w| + r = ary.include?(w[1].message) + ary << w[1].message + r + end + @warnings.each do |w| + warn "#{w[0]}: #{w[1].message} (#{w[1].class})" + end + warn "" + end + end + end + + def _run_suites suites, type + @interrupt = nil + result = [] + if @options[:parallel] + _run_parallel suites, type, result + else + suites.each {|suite| + begin + result << _run_suite(suite, type) + rescue Interrupt => e + @interrupt = e + break + end + } + end + report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip] + result + end + + # Overriding of MiniTest::Unit#puke + def puke klass, meth, e + # TODO: + # this overriding is for minitest feature that skip messages are + # hidden when not verbose (-v), note this is temporally. + e = case e + when MiniTest::Skip then + @skips += 1 + "Skipped:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n" + when MiniTest::Assertion then + @failures += 1 + "Failure:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n" + else + @errors += 1 + bt = MiniTest::filter_backtrace(e.backtrace).join "\n " + "Error:\n#{meth}(#{klass}):\n#{e.class}: #{e.message}\n #{bt}\n" + end + @report << e + e[0, 1] + end + + def status(*args) + result = super + raise @interrupt if @interrupt + result + end + end + + class AutoRunner + class Runner < Test::Unit::Runner + include Test::Unit::RequireFiles + end + + attr_accessor :to_run, :options + + def initialize(force_standalone = false, default_dir = nil, argv = ARGV) + @runner = Runner.new do |files, options| + options[:base_directory] ||= default_dir + files << default_dir if files.empty? and default_dir + @to_run = files + yield self if block_given? + files + end + Runner.runner = @runner + @options = @runner.option_parser + @argv = argv + end + + def process_args(*args) + @runner.process_args(*args) + !@to_run.empty? + end + + def run + @runner.run(@argv) || true + end + + def self.run(*args) + new(*args).run + end + end + end +end + +Test::Unit::Runner.autorun diff --git a/lib/test/unit/assertions.rb b/lib/test/unit/assertions.rb new file mode 100644 index 0000000..39a3c85 --- /dev/null +++ b/lib/test/unit/assertions.rb @@ -0,0 +1,324 @@ +require 'minitest/unit' +require 'pp' + +module Test + module Unit + module Assertions + include MiniTest::Assertions + + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end + + MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + + UNASSIGNED = Object.new # :nodoc: + + # :call-seq: + # assert( test, failure_message = UNASSIGNED ) + # + #Tests if +test+ is true. + # + #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used + #as the failure message. Otherwise, the result of calling +msg+ will be + #used as the message if the assertion fails. + # + #If no +msg+ is given, a default message will be used. + # + # assert(false, "This was expected to be true") + def assert(test, msg = UNASSIGNED) + case msg + when UNASSIGNED + msg = nil + when String, Proc + else + bt = caller.reject { |s| s.rindex(MINI_DIR, 0) } + raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt + end + super + end + + # :call-seq: + # assert_block( failure_message = nil ) + # + #Tests the result of the given block. If the block does not return true, + #the assertion will fail. The optional +failure_message+ argument is the same as in + #Assertions#assert. + # + # assert_block do + # [1, 2, 3].any? { |num| num < 1 } + # end + def assert_block(*msgs) + assert yield, *msgs + end + + # :call-seq: + # assert_raise( *args, &block ) + # + #Tests if the given block raises an exception. Acceptable exception + #types maye be given as optional arguments. If the last argument is a + #String, it will be used as the error message. + # + # assert_raise do #Fails, no Exceptions are raised + # end + # + # assert_raise NameError do + # puts x #Raises NameError, so assertion succeeds + # end + def assert_raise(*args, &b) + assert_raises(*args, &b) + end + + # :call-seq: + # assert_nothing_raised( *args, &block ) + # + #If any exceptions are given as arguments, the assertion will + #fail if one of those exceptions are raised. Otherwise, the test fails + #if any exceptions are raised. + # + #The final argument may be a failure message. + # + # assert_nothing_raised RuntimeError do + # raise Exception #Assertion passes, Exception is not a RuntimeError + # end + # + # assert_nothing_raised do + # raise Exception #Assertion fails + # end + def assert_nothing_raised(*args) + self._assertions += 1 + if Module === args.last + msg = nil + else + msg = args.pop + end + begin + line = __LINE__; yield + rescue MiniTest::Skip + raise + rescue Exception => e + bt = e.backtrace + as = e.instance_of?(MiniTest::Assertion) + if as + ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o + bt.reject! {|ln| ans =~ ln} + end + if ((args.empty? && !as) || + args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) + msg = message(msg) { "Exception raised:\n<#{mu_pp(e)}>" } + raise MiniTest::Assertion, msg.call, bt + else + raise + end + end + nil + end + + # :call-seq: + # assert_nothing_thrown( failure_message = nil, &block ) + # + #Fails if the given block uses a call to Kernel#throw. + # + #An optional failure message may be provided as the final argument. + # + # assert_nothing_thrown "Something was thrown!" do + # throw :problem? + # end + def assert_nothing_thrown(msg=nil) + begin + yield + rescue ArgumentError => error + raise error if /\Auncaught throw (.+)\z/m !~ error.message + msg = message(msg) { "<#{$1}> was thrown when nothing was expected" } + flunk(msg) + end + assert(true, "Expected nothing to be thrown") + end + + # :call-seq: + # assert_equal( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is equal to +actual+. + # + #An optional failure message may be provided as the final argument. + def assert_equal(exp, act, msg = nil) + msg = message(msg) { + exp_str = mu_pp(exp) + act_str = mu_pp(act) + exp_comment = '' + act_comment = '' + if exp_str == act_str + if (exp.is_a?(String) && act.is_a?(String)) || + (exp.is_a?(Regexp) && act.is_a?(Regexp)) + exp_comment = " (#{exp.encoding})" + act_comment = " (#{act.encoding})" + elsif exp.is_a?(Float) && act.is_a?(Float) + exp_str = "%\#.#{Float::DIG+2}g" % exp + act_str = "%\#.#{Float::DIG+2}g" % act + elsif exp.is_a?(Time) && act.is_a?(Time) + if exp.subsec * 1000_000_000 == exp.nsec + exp_comment = " (#{exp.nsec}[ns])" + else + exp_comment = " (subsec=#{exp.subsec})" + end + if act.subsec * 1000_000_000 == act.nsec + act_comment = " (#{act.nsec}[ns])" + else + act_comment = " (subsec=#{act.subsec})" + end + elsif exp.class != act.class + # a subclass of Range, for example. + exp_comment = " (#{exp.class})" + act_comment = " (#{act.class})" + end + elsif !Encoding.compatible?(exp_str, act_str) + if exp.is_a?(String) && act.is_a?(String) + exp_str = exp.dump + act_str = act.dump + exp_comment = " (#{exp.encoding})" + act_comment = " (#{act.encoding})" + else + exp_str = exp_str.dump + act_str = act_str.dump + end + end + "<#{exp_str}>#{exp_comment} expected but was\n<#{act_str}>#{act_comment}" + } + assert(exp == act, msg) + end + + # :call-seq: + # assert_not_nil( expression, failure_message = nil ) + # + #Tests if +expression+ is not nil. + # + #An optional failure message may be provided as the final argument. + def assert_not_nil(exp, msg=nil) + msg = message(msg) { "<#{mu_pp(exp)}> expected to not be nil" } + assert(!exp.nil?, msg) + end + + # :call-seq: + # assert_not_equal( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is not equal to +actual+. + # + #An optional failure message may be provided as the final argument. + def assert_not_equal(exp, act, msg=nil) + msg = message(msg) { "<#{mu_pp(exp)}> expected to be != to\n<#{mu_pp(act)}>" } + assert(exp != act, msg) + end + + # :call-seq: + # assert_no_match( regexp, string, failure_message = nil ) + # + #Tests if the given Regexp does not match a given String. + # + #An optional failure message may be provided as the final argument. + def assert_no_match(regexp, string, msg=nil) + assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.") + self._assertions -= 1 + msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" } + assert(regexp !~ string, msg) + end + + # :call-seq: + # assert_not_same( expected, actual, failure_message = nil ) + # + #Tests if +expected+ is not the same object as +actual+. + #This test uses Object#equal? to test equality. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_same("x", "x") #Succeeds + def assert_not_same(expected, actual, message="") + msg = message(msg) { build_message(message, < +with id expected to not be equal\\? to + +with id . +EOT + assert(!actual.equal?(expected), msg) + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to obj, meth, msg = nil + #get rid of overcounting + super if !caller[0].rindex(MINI_DIR, 0) || !obj.respond_to?(meth) + end + + # :call-seq: + # assert_send( +send_array+, failure_message = nil ) + # + # Passes if the method send returns a true value. + # + # +send_array+ is composed of: + # * A receiver + # * A method + # * Arguments to the method + # + # Example: + # assert_send([[1, 2], :member?, 1]) # -> pass + # assert_send([[1, 2], :member?, 4]) # -> fail + def assert_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + if args.empty? + argsstr = "" + else + (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') + end + "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return true" + } + assert recv.__send__(msg, *args), m + end + + # :call-seq: + # assert_not_send( +send_array+, failure_message = nil ) + # + # Passes if the method send doesn't return a true value. + # + # +send_array+ is composed of: + # * A receiver + # * A method + # * Arguments to the method + # + # Example: + # assert_not_send([[1, 2], :member?, 1]) # -> fail + # assert_not_send([[1, 2], :member?, 4]) # -> pass + def assert_not_send send_ary, m = nil + recv, msg, *args = send_ary + m = message(m) { + if args.empty? + argsstr = "" + else + (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') + end + "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return false" + } + assert !recv.__send__(msg, *args), m + end + + ms = instance_methods(true).map {|sym| sym.to_s } + ms.grep(/\Arefute_/) do |m| + mname = ('assert_not_' << m.to_s[/.*?_(.*)/, 1]) + alias_method(mname, m) unless ms.include? mname + end + alias assert_include assert_includes + alias assert_not_include assert_not_includes + + def build_message(head, template=nil, *arguments) #:nodoc: + template &&= template.chomp + template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) } + end + end + end +end diff --git a/lib/test/unit/parallel.rb b/lib/test/unit/parallel.rb new file mode 100644 index 0000000..7a7c73d --- /dev/null +++ b/lib/test/unit/parallel.rb @@ -0,0 +1,157 @@ +require 'test/unit' + +module Test + module Unit + class Worker < Runner + class << self + undef autorun + end + + alias orig_run_suite _run_suite + undef _run_suite + undef _run_suites + undef run + + def increment_io(orig) + *rest, io = 32.times.inject([orig.dup]){|ios, | ios << ios.last.dup } + rest.each(&:close) + io + end + + def _run_suites(suites, type) + suites.map do |suite| + _run_suite(suite, type) + end + end + + def _run_suite(suite, type) + r = report.dup + orig_testout = MiniTest::Unit.output + i,o = IO.pipe + + MiniTest::Unit.output = o + orig_stdin, orig_stdout = $stdin, $stdout + + th = Thread.new do + begin + while buf = (self.verbose ? i.gets : i.read(5)) + @stdout.puts "p #{[buf].pack("m").gsub("\n","")}" + end + rescue IOError + rescue Errno::EPIPE + end + end + + e, f, s = @errors, @failures, @skips + + begin + result = orig_run_suite(suite, type) + rescue Interrupt + @need_exit = true + result = [nil,nil] + end + + MiniTest::Unit.output = orig_testout + $stdin = orig_stdin + $stdout = orig_stdout + + o.close + begin + th.join + rescue IOError + raise unless ["stream closed","closed stream"].include? $!.message + end + i.close + + result << (report - r) + result << [@errors-e,@failures-f,@skips-s] + result << ($: - @old_loadpath) + result << suite.name + + begin + @stdout.puts "done #{[Marshal.dump(result)].pack("m").gsub("\n","")}" + rescue Errno::EPIPE; end + return result + ensure + MiniTest::Unit.output = orig_stdout + $stdin = orig_stdin + $stdout = orig_stdout + o.close if o && !o.closed? + i.close if i && !i.closed? + end + + def run(args = []) + process_args args + @@stop_auto_run = true + @opts = @options.dup + @need_exit = false + + @old_loadpath = [] + begin + @stdout = increment_io(STDOUT) + @stdin = increment_io(STDIN) + @stdout.sync = true + @stdout.puts "ready" + while buf = @stdin.gets + case buf.chomp + when /^loadpath (.+?)$/ + @old_loadpath = $:.dup + $:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq! + when /^run (.+?) (.+?)$/ + @stdout.puts "okay" + + @options = @opts.dup + suites = MiniTest::Unit::TestCase.test_suites + + begin + require $1 + rescue LoadError + @stdout.puts "after #{[Marshal.dump([$1, $!])].pack("m").gsub("\n","")}" + @stdout.puts "ready" + next + end + _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym + + if @need_exit + begin + @stdout.puts "bye" + rescue Errno::EPIPE; end + exit + else + @stdout.puts "ready" + end + when /^quit$/ + begin + @stdout.puts "bye" + rescue Errno::EPIPE; end + exit + end + end + rescue Errno::EPIPE + rescue Exception => e + begin + @stdout.puts "bye #{[Marshal.dump(e)].pack("m").gsub("\n","")}" + rescue Errno::EPIPE;end + exit + ensure + @stdin.close + @stdout.close + end + end + end + end +end + +if $0 == __FILE__ + module Test + module Unit + class TestCase < MiniTest::Unit::TestCase + def on_parallel_worker? + true + end + end + end + end + + Test::Unit::Worker.new.run(ARGV) +end diff --git a/lib/test/unit/testcase.rb b/lib/test/unit/testcase.rb new file mode 100644 index 0000000..02f0f97 --- /dev/null +++ b/lib/test/unit/testcase.rb @@ -0,0 +1,25 @@ +require 'test/unit/assertions' + +module Test + module Unit + # remove silly TestCase class + remove_const(:TestCase) if defined?(self::TestCase) + + class TestCase < MiniTest::Unit::TestCase + include Assertions + + def on_parallel_worker? + false + end + + def run runner + @options = runner.options + super runner + end + + def self.test_order + :sorted + end + end + end +end -- cgit v1.2.3