123456789_123456789_123456789_123456789_123456789_

Class: Test::Unit::UI::Console::TestRunner

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: Test::Unit::UI::TestRunner
Defined in: lib/test/unit/ui/console/testrunner.rb

Overview

Runs a ::Test::Unit::TestSuite on the console.

Constant Summary

OutputLevel - Included

IMPORTANT_FAULTS_ONLY, NORMAL, PROGRESS_ONLY, SILENT, VERBOSE

Class Method Summary

::Test::Unit::UI::TestRunner - Inherited

::Test::Unit::UI::TestRunnerUtilities - Extended

run

Creates a new TestRunner and runs the suite.

start_command_line_test

Takes care of the ARGV parsing and suite determination necessary for running one of the TestRunners from the command line.

Instance Attribute Summary

Instance Method Summary

::Test::Unit::UI::TestRunner - Inherited

Constructor Details

.new(suite, options = {}) ⇒ TestRunner

Creates a new TestRunner for running the passed suite. If quiet_mode is true, the output while running is limited to progress dots, errors and failures, and the final result. io specifies where runner output should go to; defaults to STDOUT.

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 37

def initialize(suite, options={})
  super
  @output_level = @options[:output_level] || guess_output_level
  @output = @options[:output] || STDOUT
  @use_color = @options[:use_color]
  @use_color = guess_color_availability if @use_color.nil?
  @color_scheme = @options[:color_scheme] || ColorScheme.default
  @reset_color = Color.new("reset")
  @progress_style = @options[:progress_style] || guess_progress_style
  @progress_marks = ["|", "/", "-", "\\", "|", "/", "-", "\\"]
  @progress_mark_index = 0
  @progress_row = 0
  @progress_row_max = @options[:progress_row_max]
  @progress_row_max ||= guess_progress_row_max
  @show_detail_immediately = @options[:show_detail_immediately]
  @show_detail_immediately = true if @show_detail_immediately.nil?
  @already_outputted = false
  @indent = 0
  @top_level = true
  @current_output_level = NORMAL
  @faults = []
  @code_snippet_fetcher = CodeSnippetFetcher.new
  @test_suites = []
end

Instance Attribute Details

#ruby_2_0_or_later?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 589

def ruby_2_0_or_later?
  RUBY_VERSION >= "2.0.0"
end

#windows?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 585

def windows?
  /mswin|mingw/ === RUBY_PLATFORM
end

Instance Method Details

#add_fault(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 98

def add_fault(fault)
  @faults << fault
  output_progress(fault.single_character_display,
                  fault_marker_color(fault))
  output_progress_in_detail(fault) if @show_detail_immediately
  @already_outputted = true if fault.critical?
end

#attach_to_mediator (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 81

def attach_to_mediator
  @mediator.add_listener(TestResult::FAULT,
                         &method(:add_fault))
  @mediator.add_listener(TestRunnerMediator::STARTED,
                         &method(:started))
  @mediator.add_listener(TestRunnerMediator::FINISHED,
                         &method(:finished))
  @mediator.add_listener(TestCase::STARTED_OBJECT,
                         &method(:test_started))
  @mediator.add_listener(TestCase::FINISHED_OBJECT,
                         &method(:test_finished))
  @mediator.add_listener(TestSuite::STARTED_OBJECT,
                         &method(:test_suite_started))
  @mediator.add_listener(TestSuite::FINISHED_OBJECT,
                         &method(:test_suite_finished))
end

#categorize_fault(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 176

def categorize_fault(fault)
  case fault
  when Omission
    :omissions
  when Notification
    :notifications
  else
    :need_detail_faults
  end
end

#categorize_faults (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 166

def categorize_faults
  faults = {}
  @faults.each do |fault|
    category = categorize_fault(fault)
    faults[category] ||= []
    faults[category] << fault
  end
  faults
end

#change_output_level(level) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 63

def change_output_level(level)
  old_output_level = @current_output_level
  @current_output_level = level
  yield
  @current_output_level = old_output_level
end

#color(name) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 510

def color(name)
  _color = @color_scheme[name]
  _color ||= @color_scheme["success"] if name == "pass"
  _color ||= ColorScheme.default[name]
  _color
end

#fault_class_color(fault_class) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 521

def fault_class_color(fault_class)
  color(fault_class_color_name(fault_class))
end

#fault_class_color_name(fault_class) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 517

def fault_class_color_name(fault_class)
  fault_class.name.split(/::/).last.downcase
end

#fault_color(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 525

def fault_color(fault)
  fault_class_color(fault.class)
end

#fault_marker_color(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 529

def fault_marker_color(fault)
  color("#{fault_class_color_name(fault.class)}-marker")
end

#fetch_code_snippet(file, line_number) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 245

def fetch_code_snippet(file, line_number)
  @code_snippet_fetcher.fetch(file, line_number)
end

#finished(elapsed_time) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 115

def finished(elapsed_time)
  unless @show_detail_immediately
    nl if output?(NORMAL) and !output?(VERBOSE)
    output_faults
  end
  case @progress_style
  when :inplace
    output_single("\r", nil, PROGRESS_ONLY)
  when :mark
    nl(PROGRESS_ONLY)
  end
  output_statistics(elapsed_time)
end

#format_fault(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 326

def format_fault(fault)
  fault.long_display
end

#guess_color_availability (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 560

def guess_color_availability
  return true if ENV["GITHUB_ACTIONS"] == "true"
  return false unless @output.tty?
  return true if windows? and ruby_2_0_or_later?
  case ENV["TERM"]
  when /(?:term|screen)(?:-(?:256)?color)?\z/
    true
  when TERM_COLOR_SUPPORT
    true
  else
    return true if ENV["EMACS"] == "t"
    false
  end
end

#guess_output_level (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 537

def guess_output_level
  if ENV["GITHUB_ACTIONS"] == "true"
    IMPORTANT_FAULTS_ONLY
  else
    NORMAL
  end
end

#guess_progress_row_max (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 593

def guess_progress_row_max
  term_width = guess_term_width
  if term_width.zero?
    if ENV["EMACS"] == "t"
      -1
    else
      79
    end
  else
    term_width
  end
end

#guess_progress_style (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 575

def guess_progress_style
  if @output_level >= VERBOSE
    :mark
  else
    return :fault_only if ENV["GITHUB_ACTIONS"] == "true"
    return :fault_only unless @output.tty?
    :inplace
  end
end

#guess_term_width (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 606

def guess_term_width
  guess_term_width_from_io || guess_term_width_from_env || 0
end

#guess_term_width_from_env (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 622

def guess_term_width_from_env
  env = ENV["COLUMNS"] || ENV["TERM_WIDTH"]
  return nil if env.nil?

  begin
    Integer(env)
  rescue ArgumentError
    nil
  end
end

#guess_term_width_from_io (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 610

def guess_term_width_from_io
  if @output.respond_to?(:winsize)
    begin
      @output.winsize[1]
    rescue SystemCallError
      nil
    end
  else
    nil
  end
end

#indent (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 433

def indent
  if output?(VERBOSE)
    " " * @indent
  else
    ""
  end
end

#max_digit(max_number) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 140

def max_digit(max_number)
  (Math.log10(max_number) + 1).truncate
end

#nl(level = nil) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 441

def nl(level=nil)
  output("", nil, level)
end

#output(something, color = nil, level = nil) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 445

def output(something, color=nil, level=nil)
  return unless output?(level)
  output_single(something, color, level)
  @output.puts
end

#output?(level) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 506

def output?(level)
  (level || @current_output_level) <= @output_level
end

#output_code_snippet(lines, target_line_color = nil) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 249

def output_code_snippet(lines, target_line_color=nil)
  max_n = lines.collect {|n, line, attributes| n}.max
  digits = (Math.log10(max_n) + 1).truncate
  lines.each do |n, line, attributes|
    if attributes[:target_line?]
      line_color = target_line_color
      current_line_mark = "=>"
    else
      line_color = nil
      current_line_mark = ""
    end
    output("  %2s %*d: %s" % [current_line_mark, digits, n, line],
           line_color)
  end
end

#output_failure_message(failure) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 265

def output_failure_message(failure)
  if failure.expected.respond_to?(:encoding) and
      failure.actual.respond_to?(:encoding) and
      failure.expected.encoding != failure.actual.encoding
    need_encoding = true
  else
    need_encoding = false
  end
  output(failure.user_message) if failure.user_message
  output_single("<")
  output_single(failure.inspected_expected, color("pass"))
  output_single(">")
  if need_encoding
    output_single("(")
    output_single(failure.expected.encoding.name, color("pass"))
    output_single(")")
  end
  output(" expected but was")
  output_single("<")
  output_single(failure.inspected_actual, color("failure"))
  output_single(">")
  if need_encoding
    output_single("(")
    output_single(failure.actual.encoding.name, color("failure"))
    output_single(")")
  end
  output("")
  from, to = prepare_for_diff(failure.expected, failure.actual)
  if from and to
    if need_encoding
      unless from.valid_encoding?
        from = from.dup.force_encoding("ASCII-8BIT")
      end
      unless to.valid_encoding?
        to = to.dup.force_encoding("ASCII-8BIT")
      end
    end
    from_lines = from.split(/\r?\n/)
    to_lines = to.split(/\r?\n/)
    if need_encoding
      from_lines << ""
      to_lines << ""
      from_lines << "Encoding: #{failure.expected.encoding.name}"
      to_lines << "Encoding: #{failure.actual.encoding.name}"
    end
    differ = ColorizedReadableDiffer.new(from_lines, to_lines, self)
    if differ.need_diff?
      output("")
      output("diff:")
      differ.diff
    end
  end
end

#output_fault_backtrace(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 217

def output_fault_backtrace(fault)
  detector = FaultLocationDetector.new(fault, @code_snippet_fetcher)
  backtrace = fault.location
  # workaround for test-spec. :<
  # see also GitHub:#22
  backtrace ||= []

  code_snippet_backtrace_index = nil
  code_snippet_lines = nil
  backtrace.each_with_index do |entry, i|
    next unless detector.target?(entry)
    file, line_number, = detector.split_backtrace_entry(entry)
    lines = fetch_code_snippet(file, line_number)
    unless lines.empty?
      code_snippet_backtrace_index = i
      code_snippet_lines = lines
      break
    end
  end

  backtrace.each_with_index do |entry, i|
    output(entry)
    if i == code_snippet_backtrace_index
      output_code_snippet(code_snippet_lines, fault_color(fault))
    end
  end
end

#output_fault_in_detail(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 187

def output_fault_in_detail(fault)
  if fault.is_a?(Failure) and
      fault.inspected_expected and
      fault.inspected_actual
    output_single("#{fault.label}: ")
    output(fault.test_name, fault_color(fault))
    output_fault_backtrace(fault)
    output_failure_message(fault)
  else
    output_single("#{fault.label}: ")
    output_single(fault.test_name, fault_color(fault))
    output_fault_message(fault)
    output_fault_backtrace(fault)
  end
end

#output_fault_in_short(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 319

def output_fault_in_short(fault)
  output_single("#{fault.label}: ")
  output_single(fault.message, fault_color(fault))
  output(" [#{fault.test_name}]")
  output(fault.location.first)
end

#output_fault_message(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 203

def output_fault_message(fault)
  message = fault.message
  return if message.nil?

  if message.include?("\n")
    output(":")
    message.each_line do |line|
      output("  #{line.chomp}")
    end
  else
    output(": #{message}")
  end
end

#output_faults (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 129

def output_faults
  categorized_faults = categorize_faults
  change_output_level(IMPORTANT_FAULTS_ONLY) do
    output_faults_in_detail(categorized_faults[:need_detail_faults])
  end
  output_faults_in_short("Omissions", Omission,
                         categorized_faults[:omissions])
  output_faults_in_short("Notifications", Notification,
                         categorized_faults[:notifications])
end

#output_faults_in_detail(faults) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 144

def output_faults_in_detail(faults)
  return if faults.nil?
  digit = max_digit(faults.size)
  faults.each_with_index do |fault, index|
    nl
    output_single("%#{digit}d) " % (index + 1))
    output_fault_in_detail(fault)
  end
end

#output_faults_in_short(label, fault_class, faults) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 154

def output_faults_in_short(label, fault_class, faults)
  return if faults.nil?
  digit = max_digit(faults.size)
  nl
  output_single(label, fault_class_color(fault_class))
  output(":")
  faults.each_with_index do |fault, index|
    output_single("%#{digit}d) " % (index + 1))
    output_fault_in_short(fault)
  end
end

#output_progress(mark, color = nil) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 465

def output_progress(mark, color=nil)
  if @progress_style == :inplace
    output_single("\r#{mark}", color, PROGRESS_ONLY)
  else
    return unless output?(PROGRESS_ONLY)
    output_single(mark, color)
    return unless @progress_row_max > 0
    @progress_row += mark.size
    if @progress_row >= @progress_row_max
      nl unless @output_level == VERBOSE
      @progress_row = 0
    end
  end
end

#output_progress_in_detail(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 485

def output_progress_in_detail(fault)
  return if @output_level == SILENT
  need_detail_faults = (categorize_fault(fault) == :need_detail_faults)
  if need_detail_faults
    log_level = IMPORTANT_FAULTS_ONLY
  else
    log_level = @current_output_level
  end
  change_output_level(log_level) do
    nl(NORMAL)
    output_progress_in_detail_marker(fault)
    if need_detail_faults
      output_fault_in_detail(fault)
    else
      output_fault_in_short(fault)
    end
    output_progress_in_detail_marker(fault)
    @progress_row = 0
  end
end

#output_progress_in_detail_marker(fault) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 480

def output_progress_in_detail_marker(fault)
  return if @progress_row_max <= 0
  output("=" * @progress_row_max)
end

#output_setup_end (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 75

def output_setup_end
  suite_name = @suite.to_s
  suite_name = @suite.name if @suite.kind_of?(Module)
  output("Loaded suite #{suite_name}")
end

#output_single(something, color = nil, level = nil) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 451

def output_single(something, color=nil, level=nil)
  return false unless output?(level)
  something.to_s.each_line do |line|
    if @use_color and color
      line = "%s%s%s" % [color.escape_sequence,
                         line,
                         @reset_color.escape_sequence]
    end
    @output.write(line)
  end
  @output.flush
  true
end

#output_started (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 111

def output_started
  output("Started")
end

#output_statistics(elapsed_time) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 330

def output_statistics(elapsed_time)
  change_output_level(IMPORTANT_FAULTS_ONLY) do
    output("Finished in #{elapsed_time} seconds.")
  end
  output_summary_marker
  change_output_level(IMPORTANT_FAULTS_ONLY) do
    output(@result)
  end
  output("%g%% passed" % @result.pass_percentage)
  unless elapsed_time.zero?
    output_summary_marker
    test_throughput = @result.run_count / elapsed_time
    assertion_throughput = @result.assertion_count / elapsed_time
    throughput = [
      "%.2f tests/s" % test_throughput,
      "%.2f assertions/s" % assertion_throughput,
    ]
    output(throughput.join(", "))
  end
end

#output_summary_marker (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 351

def output_summary_marker
  return if @progress_row_max <= 0
  output("-" * @progress_row_max, summary_marker_color)
end

#setup_mediator (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 70

def setup_mediator
  super
  output_setup_end
end

#started(result) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 106

def started(result)
  @result = result
  output_started
end

#suite_name(prefix, suite) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 399

def suite_name(prefix, suite)
  name = suite.name
  if name.nil?
    "(anonymous)"
  else
    name.sub(/\A#{Regexp.escape(prefix)}/, "")
  end
end

#summary_marker_color (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 533

def summary_marker_color
  color("#{@result.status}-marker")
end

#test_finished(test) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 380

def test_finished(test)
  unless @already_outputted
    case @progress_style
    when :inplace
      mark = @progress_marks[@progress_mark_index]
      @progress_mark_index =
        (@progress_mark_index + 1) % @progress_marks.size
      output_progress(mark, color("pass-marker"))
    when :mark
      output_progress(".", color("pass-marker"))
    end
  end
  @already_outputted = false

  return unless output?(VERBOSE)

  output(": (%f)" % (Time.now - @test_start), nil, VERBOSE)
end

#test_started(test) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 356

def test_started(test)
  return unless output?(VERBOSE)

  tab_width = 8
  name = test.local_name
  separator = ":"
  left_used = indent.size + name.size + separator.size
  right_space = tab_width * 2
  left_space = @progress_row_max - right_space
  if (left_used % tab_width).zero?
    left_space -= left_used
    n_tabs = 0
  else
    left_space -= ((left_used / tab_width) + 1) * tab_width
    n_tabs = 1
  end
  n_tabs += [left_space, 0].max / tab_width
  tab_stop = "\t" * n_tabs
  output_single("#{indent}#{name}#{separator}#{tab_stop}",
                nil,
                VERBOSE)
  @test_start = Time.now
end

#test_suite_finished(suite) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 428

def test_suite_finished(suite)
  @indent -= 2
  @test_suites.pop
end

#test_suite_started(suite) (private)

[ GitHub ]

  
# File 'lib/test/unit/ui/console/testrunner.rb', line 408

def test_suite_started(suite)
  last_test_suite = @test_suites.last
  @test_suites << suite
  if @top_level
    @top_level = false
    return
  end

  output_single(indent, nil, VERBOSE)
  if suite.test_case.nil?
    _color = color("suite")
  else
    _color = color("case")
  end
  prefix = "#{last_test_suite.name}::"
  output_single(suite_name(prefix, suite), _color, VERBOSE)
  output(": ", nil, VERBOSE)
  @indent += 2
end