MiniTest::Unit
# File test/unit.rb, line 676
def _prepare_run(suites, type)
options[:job_status] ||= :replace if @tty && !@verbose
case options[:color]
when :always
color = true
when :auto, nil
color = @options[:job_status] == :replace && /dumb/ !~ ENV["TERM"]
else
color = false
end
if color
# dircolors-like style
colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:]*)/)] : {}
@passed_color = "\e[#{colors["pass"] || "32"}m"
@failed_color = "\e[#{colors["fail"] || "31"}m"
@skipped_color = "\e[#{colors["skip"] || "33"}m"
@reset_color = "\e[m"
else
@passed_color = @failed_color = @skipped_color = @reset_color = ""
end
if color or @options[:job_status] == :replace
@verbose = !options[:parallel]
@output = StatusLineOutput.new(self)
end
if /\A\/(.*)\/\z/ =~ (filter = options[:filter])
filter = Regexp.new($1)
end
type = "#{type}_methods"
total = if filter
suites.inject(0) {|n, suite| n + suite.send(type).grep(filter).size}
else
suites.inject(0) {|n, suite| n + suite.send(type).size}
end
@test_count = 0
@total_tests = total.to_s(10)
end
# File test/unit.rb, line 564
def _run_parallel suites, type, result
if @options[:parallel] < 1
warn "Error: parameter of -j option should be greater than 0."
return
end
# 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 = []
@total_tests = @tasks.size.to_s(10)
rep = [] # FIXME: more good naming
@workers = [] # Array of workers.
@workers_hash = {} # out-IO => worker
@ios = [] # Array of worker IOs
begin
# Thread: watchdog
watchdog = start_watchdog
@options[:parallel].times {launch_worker}
while _io = IO.select(@ios)[0]
break if _io.any? do |io|
@need_quit or
(deal(io, type, result, rep).nil? and
!@workers.any? {|x| [:running, :prepare].include? x.status})
end
end
rescue Interrupt => ex
@interrupt = ex
return result
ensure
watchdog.kill if watchdog
if @interrupt
@ios.select!{|x| @workers_hash[x].status == :running }
while !@ios.empty? && (__io = IO.select(@ios,[],[],10))
__io[0].reject! {|io| deal(io, type, result, rep, true)}
end
end
quit_workers
unless @interrupt || !@options[:retry] || @need_quit
@options[:parallel] = false
suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}}
suites.map {|r| r[:file]}.uniq.each {|file| require file}
suites.map! {|r| eval("::"+r[:testcase])}
del_status_line or puts
unless suites.empty?
puts "Retrying..."
_run_suites(suites, type)
end
end
unless @options[:retry]
del_status_line or puts
end
unless rep.empty?
rep.each do |r|
r[:report].each do |f|
puke(*f) if f
end
end
if @options[:retry]
@errors += rep.map{|x| x[:result][0] }.inject(:+)
@failures += rep.map{|x| x[:result][1] }.inject(:+)
@skips += rep.map{|x| x[:result][2] }.inject(:+)
end
end
unless @warnings.empty?
warn ""
@warnings.uniq! {|w| w[1].message}
@warnings.each do |w|
warn "#{w[0]}: #{w[1].message} (#{w[1].class})"
end
warn ""
end
end
end
# File test/unit.rb, line 647
def _run_suites suites, type
_prepare_run(suites, type)
@interrupt = nil
result = []
GC.start
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]
report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : (r.start_with?("Failure:") ? 1 : 2) }
result
end
# File test/unit.rb, line 425
def add_status(line)
unless @options[:job_status] == :replace
print(line)
return
end
@status_line_size ||= 0
line = line[0...(terminal_width-@status_line_size)]
print line
$stdout.flush
@status_line_size += line.size
end
# File test/unit.rb, line 372
def after_worker_down(worker, e=nil, c=false)
return unless @options[:parallel]
return if @interrupt
warn e if e
@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 449
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 521
def deal(io, type, result, rep, shutting_down = false)
worker = @workers_hash[io]
case worker.read
when /^okay$/
worker.status = :running
jobs_status
when /^ready(!)?$/
bang = $1
worker.status = :ready
return nil unless task = @tasks.shift
if @options[:separate] and not bang
worker.quit
worker = add_worker
end
worker.run(task, type)
@test_count += 1
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!
return true
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$/, nil
if shutting_down || worker.quit_called
after_worker_quit worker
else
after_worker_down worker
end
end
return false
end
# File test/unit.rb, line 444
def del_jobs_status
return unless @options[:job_status] == :replace && @status_line_size.nonzero?
del_status_line
end
# File test/unit.rb, line 400
def del_status_line
@status_line_size ||= 0
unless @options[:job_status] == :replace
$stdout.puts
return
end
print "\r"+" "*@status_line_size+"\r"
$stdout.flush
@status_line_size = 0
end
# File test/unit.rb, line 473
def delete_worker(worker)
@workers_hash.delete worker.io
@workers.delete worker
@ios.delete worker.io
end
# File test/unit.rb, line 726
def failed(s)
sep = "\n"
@report_count ||= 0
report.each do |msg|
if msg.start_with? "Skipped:"
if @options[:hide_skip]
del_status_line
next
end
color = @skipped_color
else
color = @failed_color
end
msg = msg.split(/$/, 2)
$stdout.printf("%s%s%3d) %s%s%s\n",
sep, color, @report_count += 1,
msg[0], @reset_color, msg[1])
sep = nil
end
report.clear
end
# File test/unit.rb, line 437
def jobs_status
return unless @options[:job_status]
puts "" unless @options[:verbose] or @options[:job_status] == :replace
status_line = @workers.map(&:to_s).join(" ")
update_status(status_line) or (puts; nil)
end
# File test/unit.rb, line 457
def launch_worker
begin
worker = Worker.launch(@options[:ruby],@args)
rescue => e
abort "ERROR: Failed to launch job process - #{e.class}: #{e.message}"
end
worker.hook(:dead) do |w,info|
after_worker_quit w
after_worker_down w, *info if !info.empty? && !worker.quit_called
end
@workers << worker
@ios << worker.io
@workers_hash[worker.io] = worker
worker
end
# File test/unit.rb, line 713
def new_test(s)
@test_count += 1
update_status(s)
end
# File test/unit.rb, line 672
def output
(@output ||= nil) || super
end
Overriding of MiniTest::Unit#puke
# File test/unit.rb, line 749
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.
n = report.size
rep = super
if MiniTest::Skip === e and /no message given\z/ =~ e.message
report.slice!(n..-1)
rep = "."
end
rep
end
# File test/unit.rb, line 411
def put_status(line)
unless @options[:job_status] == :replace
print(line)
return
end
@status_line_size ||= 0
del_status_line
$stdout.flush
line = line[0...terminal_width]
print line
$stdout.flush
@status_line_size = line.size
end
# File test/unit.rb, line 479
def quit_workers
return if @workers.empty?
@workers.reject! do |worker|
begin
timeout(1) do
worker.quit
end
rescue Errno::EPIPE
rescue Timeout::Error
end
worker.close
end
return if @workers.empty?
begin
timeout(0.2 * @workers.size) do
Process.waitall
end
rescue Timeout::Error
@workers.each do |worker|
worker.kill
end
@worker.clear
end
end
# File test/unit.rb, line 773
def run(*args)
result = super
puts "\nruby -v: #{RUBY_DESCRIPTION}"
result
end
# File test/unit.rb, line 505
def start_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 }
next unless w
w = w.dup
if w.status != :quit && !w.quit_called?
# Worker down
w.died(nil, !stat.signaled? && stat.exitstatus)
end
end
end
end
# File test/unit.rb, line 767
def status(*args)
result = super
raise @interrupt if @interrupt
result
end
# File test/unit.rb, line 386
def terminal_width
unless @terminal_width ||= nil
begin
require 'io/console'
width = $stdout.winsize[1]
rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF
width = ENV["COLUMNS"].to_i.nonzero? || 80
end
width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM
@terminal_width = width
end
@terminal_width
end
Commenting is here to help enhance the documentation. For example, code samples, 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.
If you want to help improve the Ruby documentation, please see Improve the docs, or visit Documenting-ruby.org.