MiniTest::Unit
# File test/unit.rb, line 386
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
# File test/unit.rb, line 557
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
# File test/unit.rb, line 332
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
# File test/unit.rb, line 378
def after_worker_quit(worker)
return unless @options[:parallel]
return if @interrupt
@workers.delete(worker)
@dead_workers << worker
@ios = @workers.map(&:io)
end
# File test/unit.rb, line 373
def del_jobs_status
return unless @options[:job_status] == :replace && @jstr_size.nonzero?
print "\r"+" "*@jstr_size+"\r"
end
# File test/unit.rb, line 350
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
Overriding of MiniTest::Unit#puke
# File test/unit.rb, line 577
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
Commenting is here to help enhance the documentation. For example, sample code, or clarification of the documentation.
If you have questions about Ruby or the documentation, please post to one of the Ruby mailing lists. You will get better, faster, help that way.
If you wish to post a correction of the docs, please do so, but also file bug report so that it can be corrected for the next release. Thank you.