class Test::Unit::Runner

Public Class Methods

autorun() click to toggle source
# File lib/test/unit.rb, line 363
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

Public Instance Methods

_prepare_run(suites, type) click to toggle source
# File lib/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
_print(s) click to toggle source
# File lib/test/unit.rb, line 723
def _print(s); $stdout.print(s); end
_run_parallel(suites, type, result) click to toggle source
# File lib/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
_run_suites(suites, type) click to toggle source
# File lib/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
add_status(line) click to toggle source
# File lib/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
after_worker_down(worker, e=nil, c=false) click to toggle source
# File lib/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
after_worker_quit(worker) click to toggle source
# File lib/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
deal(io, type, result, rep, shutting_down = false) click to toggle source
# File lib/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
del_jobs_status() click to toggle source
# File lib/test/unit.rb, line 444
def del_jobs_status
  return unless @options[:job_status] == :replace && @status_line_size.nonzero?
  del_status_line
end
del_status_line() click to toggle source
# File lib/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
delete_worker(worker) click to toggle source
# File lib/test/unit.rb, line 473
def delete_worker(worker)
  @workers_hash.delete worker.io
  @workers.delete worker
  @ios.delete worker.io
end
failed(s) click to toggle source
# File lib/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
jobs_status() click to toggle source
# File lib/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
launch_worker() click to toggle source
# File lib/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
new_test(s) click to toggle source
# File lib/test/unit.rb, line 713
def new_test(s)
  @test_count += 1
  update_status(s)
end
output() click to toggle source
Calls superclass method
# File lib/test/unit.rb, line 672
def output
  (@output ||= nil) || super
end
puke(klass, meth, e) click to toggle source

Overriding of MiniTest::Unit#puke

Calls superclass method
# File lib/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
put_status(line) click to toggle source
# File lib/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
quit_workers() click to toggle source
# File lib/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
run(*args) click to toggle source
Calls superclass method Test::Unit::RunCount#run
# File lib/test/unit.rb, line 773
def run(*args)
  result = super
  puts "\nruby -v: #{RUBY_DESCRIPTION}"
  result
end
start_watchdog() click to toggle source
# File lib/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
status(*args) click to toggle source
Calls superclass method
# File lib/test/unit.rb, line 767
def status(*args)
  result = super
  raise @interrupt if @interrupt
  result
end
succeed() click to toggle source
# File lib/test/unit.rb, line 724
def succeed; del_status_line; end
terminal_width() click to toggle source
# File lib/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, Errno::EINVAL
      width = ENV["COLUMNS"].to_i.nonzero? || 80
    end
    width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM
    @terminal_width = width
  end
  @terminal_width
end
update_status(s) click to toggle source
# File lib/test/unit.rb, line 718
def update_status(s)
  count = @test_count.to_s(10).rjust(@total_tests.size)
  put_status("#{@passed_color}[#{count}/#{@total_tests}]#{@reset_color} #{s}")
end