class Test::Unit::UI::Console::TestRunner

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

Constants

N_REPORT_SLOW_TESTS
TERM_COLOR_SUPPORT

Public Class Methods

new(suite, options={}) click to toggle source

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.

Calls superclass method Test::Unit::UI::TestRunner::new
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 40
def initialize(suite, options={})
  super
  @on_github_actions = (ENV["GITHUB_ACTIONS"] == "true")
  @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 = []
  @test_statistics = []
end

Private Instance Methods

add_fault(fault) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 103
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() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 86
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 181
def categorize_fault(fault)
  case fault
  when Omission
    :omissions
  when Notification
    :notifications
  else
    :need_detail_faults
  end
end
categorize_faults() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 171
def categorize_faults
  faults = {}
  @faults.each do |fault|
    category = categorize_fault(fault)
    faults[category] ||= []
    faults[category] << fault
  end
  faults
end
change_output_level(level) { || ... } click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 68
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 597
def color(name)
  _color = @color_scheme[name]
  _color ||= @color_scheme["success"] if name == "pass"
  _color ||= ColorScheme.default[name]
  _color
end
detect_target_location_on_github_actions(fault) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 224
def detect_target_location_on_github_actions(fault)
  return nil unless @on_github_actions

  base_dir = ENV["GITHUB_WORKSPACE"]
  return nil unless base_dir
  base_dir = Pathname(base_dir).expand_path

  detector = FaultLocationDetector.new(fault, @code_snippet_fetcher)
  backtrace = fault.location || []
  backtrace.each_with_index do |entry, i|
    next unless detector.target?(entry)
    file, line, = detector.split_backtrace_entry(entry)
    file = Pathname(file).expand_path
    relative_file = file.relative_path_from(base_dir)
    first_component = relative_file.descend do |component|
      break component
    end
    # file isn't under base_dir
    next if first_component.to_s == "..."
    return [relative_file, line]
  end
  nil
end
fault_class_color(fault_class) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 608
def fault_class_color(fault_class)
  color(fault_class_color_name(fault_class))
end
fault_class_color_name(fault_class) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 604
def fault_class_color_name(fault_class)
  fault_class.name.split(/::/).last.downcase
end
fault_color(fault) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 612
def fault_color(fault)
  fault_class_color(fault.class)
end
fault_marker_color(fault) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 616
def fault_marker_color(fault)
  color("#{fault_class_color_name(fault.class)}-marker")
end
fetch_code_snippet(file, line_number) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 309
def fetch_code_snippet(file, line_number)
  @code_snippet_fetcher.fetch(file, line_number)
end
finished(elapsed_time) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 120
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 390
def format_fault(fault)
  fault.long_display
end
guess_color_availability() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 647
def guess_color_availability
  return true if @on_github_actions
  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() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 624
def guess_output_level
  if @on_github_actions
    IMPORTANT_FAULTS_ONLY
  else
    NORMAL
  end
end
guess_progress_row_max() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 680
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() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 662
def guess_progress_style
  if @output_level >= VERBOSE
    :mark
  else
    return :fault_only if @on_github_actions
    return :fault_only unless @output.tty?
    :inplace
  end
end
guess_term_width() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 693
def guess_term_width
  guess_term_width_from_io || guess_term_width_from_env || 0
end
guess_term_width_from_env() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 709
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() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 697
def guess_term_width_from_io
  if @output.respond_to?(:winsize)
    begin
      @output.winsize[1]
    rescue SystemCallError
      nil
    end
  else
    nil
  end
end
indent() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 520
def indent
  if output?(VERBOSE)
    " " * @indent
  else
    ""
  end
end
max_digit(max_number) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 145
def max_digit(max_number)
  (Math.log10(max_number) + 1).truncate
end
nl(level=nil) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 528
def nl(level=nil)
  output("", nil, level)
end
output(something, color=nil, level=nil) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 532
def output(something, color=nil, level=nil)
  return unless output?(level)
  output_single(something, color, level)
  @output.puts
end
output?(level) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 593
def output?(level)
  (level || @current_output_level) <= @output_level
end
output_code_snippet(lines, target_line_color=nil) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 313
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 329
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 281
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 192
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)
    output_fault_on_github_actions(fault)
  else
    output_single("#{fault.label}: ")
    output_single(fault.test_name, fault_color(fault))
    output_fault_message(fault)
    output_fault_backtrace(fault)
    output_fault_on_github_actions(fault) if fault.is_a?(Error)
    if fault.is_a?(Error) and fault.exception.respond_to?(:cause)
      cause = fault.exception.cause
      i = 0
      while cause
        sub_fault = Error.new(fault.test_name,
                              cause,
                              method_name: fault.method_name)
        output_single("Cause#{i}", fault_color(sub_fault))
        output_fault_message(sub_fault)
        output_fault_backtrace(sub_fault)
        cause = cause.cause
        i += 1
      end
    end
  end
end
output_fault_in_short(fault) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 383
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 267
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_fault_on_github_actions(fault) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 248
def output_fault_on_github_actions(fault)
  location = detect_target_location_on_github_actions(fault)
  return unless location

  parameters = [
    "file=#{location[0]}",
    "line=#{location[1]}",
    "title=#{fault.label}",
  ].join(",")
  message = fault.message
  if fault.is_a?(Error)
    message = ([message] + (fault.location || [])).join("\n")
  end
  # We need to use URL encode for new line:
  # https://github.com/actions/toolkit/issues/193
  message = message.gsub("\n", "%0A")
  output("::error #{parameters}::#{message}")
end
output_faults() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 134
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 149
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 159
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 552
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 572
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 567
def output_progress_in_detail_marker(fault)
  return if @progress_row_max <= 0
  output("=" * @progress_row_max)
end
output_setup_end() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 80
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) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 538
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() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 116
def output_started
  output("Started")
end
output_statistics(elapsed_time) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 394
def output_statistics(elapsed_time)
  change_output_level(IMPORTANT_FAULTS_ONLY) do
    output("Finished in #{elapsed_time} seconds.")
  end
  if @options[:report_slow_tests]
    output_summary_marker
    output("Top #{N_REPORT_SLOW_TESTS} slow tests")
    @test_statistics.sort_by {|statistic| -statistic[:elapsed_time]}
                    .first(N_REPORT_SLOW_TESTS)
                    .each do |slow_statistic|
      left_side = "#{slow_statistic[:name]}: "
      right_width = @progress_row_max - left_side.size
      output("%s%*f" % [
               left_side,
               right_width,
               slow_statistic[:elapsed_time],
             ])
      output("--location #{slow_statistic[:location]}")
    end
  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() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 431
def output_summary_marker
  return if @progress_row_max <= 0
  output("-" * @progress_row_max, summary_marker_color)
end
ruby_2_0_or_later?() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 676
def ruby_2_0_or_later?
  RUBY_VERSION >= "2.0.0"
end
setup_mediator() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 75
def setup_mediator
  super
  output_setup_end
end
started(result) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 111
def started(result)
  @result = result
  output_started
end
suite_name(prefix, suite) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 486
def suite_name(prefix, suite)
  name = suite.name
  if name.nil?
    "(anonymous)"
  else
    name.sub(/\A#{Regexp.escape(prefix)}/, "")
  end
end
summary_marker_color() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 620
def summary_marker_color
  color("#{@result.status}-marker")
end
test_finished(test) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 459
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

  if @options[:report_slow_tests]
    @test_statistics << {
      name: test.name,
      elapsed_time: test.elapsed_time,
      location: test.method(test.method_name).source_location.join(":"),
    }
  end

  return unless output?(VERBOSE)

  output(": (%f)" % test.elapsed_time, nil, VERBOSE)
end
test_started(test) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 436
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)
end
test_suite_finished(suite) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 515
def test_suite_finished(suite)
  @indent -= 2
  @test_suites.pop
end
test_suite_started(suite) click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 495
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
windows?() click to toggle source
# File test-unit-3.6.4/lib/test/unit/ui/console/testrunner.rb, line 672
def windows?
  /mswin|mingw/ === RUBY_PLATFORM
end