lib/utils/line_formatter.rb



require 'tins/terminal'
require 'term/ansicolor'

begin
  require 'rspec/core'
  require 'rspec/core/formatters'
rescue LoadError => e
  $DEBUG and warn "Caught #{e.class}: #{e}"
else
  module Utils
    class LineFormatter
      ::RSpec::Core::Formatters.register self, :start, :close,
        :example_passed, :example_pending, :example_failed, :dump_summary

      def initialize(output)
        @output = output
        @output.sync = true
        filename = 'errors.lst'
        @errors_lst = File.new(filename, 'w')
        @errors_lst.sync = true
      end

      attr_reader :output

      def start(_ignore)
        output.puts "Storing error list in #{@errors_lst.path.inspect}: "
        output.puts ?- * Tins::Terminal.columns
      end

      def close(_ignore)
        @errors_lst.close
      end

      def dump_summary(summary)
        line = summary_line(summary)
        @errors_lst.puts ?= * 80, line
        output.puts ?= * Tins::Terminal.columns, line
      end

      def example_passed(example)
        output.puts format_line(example)
      end

      def example_pending(example)
        output.puts format_line(example)
      end

      def example_failed(example)
        dump_failure_to_error_file(example)
        output.puts format_line(example)
        dump_failure(example)
      end

      private

      def summary_line(summary)
        failure_percentage = 100 * summary.failure_count.to_f / summary.example_count
        failure_percentage.nan? and failure_percentage = 0.0
        pending_percentage = 100 * summary.pending_count.to_f / summary.example_count
        pending_percentage.nan? and pending_percentage = 0.0
        "%u of %u (%.2f %%) failed, %u pending (%.2f %%) in %.3f seconds" % [
          summary.failure_count,
          summary.example_count,
          failure_percentage,
          summary.pending_count,
          pending_percentage,
          summary.duration,
        ]
      end

      def dump_failure(example)
        output.puts(
          description(example, full: true),
          dump_failure_for_example(example)
        )
      end

      def read_failed_line(example)
        ''.strip
      end

      def dump_failure_for_example(example)
        result = ''
        exception = execution_result(example).exception
        exception_class_name = exception.class.name
        result << "Failure/Error: #{read_failed_line(example)}\n"
        result << "#{exception_class_name}:\n" unless exception_class_name =~ /RSpec/
        if m = exception.message
          m.to_s.split("\n").each { |line| result << "  #{line}\n" }
        end
        result
      end

      def format_backtrace(example, folding: false, limit: nil)
        backtrace = execution_result(example).exception.backtrace
        backtrace.nil? and return ''
        if limit
          backtrace = backtrace[0, limit]
        end
        result = []
        folding and result << '{{{'
        for line in backtrace
          result << RSpec::Core::Metadata::relative_path(line)
        end
        folding and result << '}}}'
        result * ?\n
      end

      def dump_failure_to_error_file(example)
        @errors_lst.flock File::LOCK_EX
        @errors_lst.puts "%s\n%3.3fs %s\n%s\n%s" % [
          location(example), run_time(example), description(example, full: true),
          dump_failure_for_example(example), format_backtrace(example, folding: true)
        ]
      ensure
        @errors_lst.flock File::LOCK_UN
      end

      def execution_result(example)
        example.example.metadata[:execution_result]
      end

      def description(example, full: ENV['VERBOSE'].to_i == 1)
        if full
          example.example.full_description
        else
          example.example.description
        end
      end

      def run_time(example)
        execution_result(example).run_time
      end

      def format_line(example)
        args = [ location(example), run_time(example), description(example) ]
        uncolored = "%s # S %3.3fs %s" % args
        uncolored = uncolored[0, Tins::Terminal.columns]
        case execution_result(example).status
        when :passed
          success_color(uncolored)
        when :failed
          failure_color(uncolored)
        when :pending
          pending_color(uncolored)
        else
          uncolored % args
        end
      end

      def success_color(text)
        Term::ANSIColor.green(text)
      end

      def failure_color(text)
        Term::ANSIColor.red(text)
      end

      def pending_color(text)
        Term::ANSIColor.yellow(text)
      end

      def location(example)
        location = example.example.metadata[:location]
        unless location.include?(?/)
          location = example.example.metadata[:example_group][:location]
        end
        RSpec::Core::Metadata::relative_path(location)
      end
    end
  end
end