Mimic the Progress formatter for console status feedback.
require 'rspec/core/formatters/base_text_formatter'
### SpeckyFormatter: A basic RSpec 2.x text formatter, to be used
### with the 'Specky' vim plugin (or from the command line, if you
### dig it over the default 'documentation' format!)
###
### rspec -r /path/to/this/specky_formatter.rb -f SpeckyFormatter specs
###
class SpeckyFormatter < RSpec::Core::Formatters::BaseTextFormatter
def initialize( *args )
super
@indent_level = 0
@failure_index = 0
@failures = []
@txt = ''
@summary = ''
end
########################################################################
### R S P E C H O O K S
########################################################################
### Example group hook -- increase indentation, emit description
###
def example_group_started( example_group )
self.out '+', '-' * (example_group.description.length + 2), '+'
self.out '| ', example_group.description, ' |'
self.out '+', '-' * (example_group.description.length + 2), '+'
@indent_level += 1
end
### Example group hook -- decrease indentation
###
def example_group_finished( example_group )
@indent_level -= 1
end
### Called on example success
###
def example_passed( example )
msg = self.format_example( example )
msg << ')'
self.out msg
end
### Called on a pending example
###
def example_pending( example )
msg = self.format_example( example )
pending_msg = example.metadata[ :execution_result ][ :pending_message ]
msg << ", PENDING%s)" % [ ": #{pending_msg}" || '' ]
self.out msg
end
### Called on example failure
###
def example_failed( example )
@failure_index += 1
msg = self.format_example( example )
msg << ", FAILED - #%d)" % [ @failure_index ]
self.out msg
@failures << example
end
### Called after all examples are run. Emit details for each failed example,
### for Vim to fold.
###
def dump_failures
self.out "\n" unless @failures.empty?
cwd = Regexp.new( Dir.pwd )
bt_regexp = /(.+?):(\d+)(|:\d+)/
@failures.each_with_index do |example, index|
desc = example.metadata[ :full_description ]
exception = example.execution_result[ :exception ]
file = line = nil
# remove files that are not in within the cwd.
#
# this isn't optimal, but it does stay within specky's notion of
# running it from within the project directory, and makes sure we don't
# inadvertently display code from rspec itself.
#
bt_file = exception.backtrace.find { |line| line =~ bt_regexp && line =~ cwd }
file, line = $1, $2.to_i if bt_file =~ bt_regexp
self.out "FAILURE - #%d)" % [ index + 1 ]
self.out "%s:%d" % [ file, line ] if bt_file
if exception.respond_to?( :pending_fixed? ) && exception.pending_fixed?
self.out "%s FIXED" % [ desc ]
self.out "Expected pending '%s' to fail. No error was raised." % [
example.metadata[ :execution_result ][ :pending_message ]
]
else
self.out desc
self.out "Failure/Error: %s" % [ read_failed_line( exception, example).strip ]
exception.message.split("\n").each {|l| self.out l}
# logic taken from the base class
example.example_group.ancestors.push(example.example_group).each do |group|
if group.metadata[:shared_group_name]
self.out "Shared Example Group: \"#{group.metadata[:shared_group_name]}\" called from " +
"#{backtrace_line(group.metadata[:example_group][:location])}"
break
end
end
end
self.out exception_source( file, line-1 ) if file && line
end
end
### Emit the source of the exception, with context lines.
###
def exception_source( file, line )
context = ''
low, high = line - 3, line + 3
File.open( file ).each_with_index do |cline, i|
cline.chomp!.rstrip!
next unless i >= low && i <= high
context << " %s%4d: %s\n" % [ ( i == line ? '>>' : ' |' ), i, cline ]
end
return context
rescue
'Unable to parse exception context lines.'
end
### Emit summary data for all examples.
###
def dump_summary( duration, example_count, failure_count, pending_count )
succeeded = example_count - failure_count - pending_count
@summary << "+%s+\n" % [ '-' * 49 ]
@summary << "|%s-- Summary --%s|\n" % [ ' ' * 18, ' ' * 18 ]
@summary << "+----------+-----------+--------+---------+-------+\n"
@summary << "| Duration | Succeeded | Failed | Pending | Total |\n"
@summary << "+----------+-----------+--------+---------+-------+\n"
@summary << "| %7ss | %9s | %6s | %7s | %5s |\n" % [
"%0.3f" % duration, succeeded, failure_count,
pending_count, example_count
]
@summary << "+----------+-----------+--------+---------+-------+\n\n"
end
### End of run. Dump it all out!
###
def close
output.puts @summary
output.puts @txt
end
#########
protected
#########
### Send a string to the output IO object, after indentation.
###
def out( *msg )
msg = msg.join
@txt << "%s%s\n" % [ ' ' * @indent_level, msg ]
end
### Format the basic example information, along with the run duration.
###
def format_example( example )
metadata = example.metadata
duration = metadata[ :execution_result ][ :run_time ]
description = metadata[ :description ]
return "| %s (%0.3fs" % [ description, duration ]
end
end # SpeckyFormatter
### Identical to the regular SpeckyFormatter, but it puts summary
### information at the bottom of the screen instead of the top, and just
### spits out rudamentary failure info. Mimics the progress
### formatter for status feedback
###
class SpeckyFormatterConsole < SpeckyFormatter
def example_passed( ex ); print '.'; end
def example_failed( ex ); print 'F'; end
def example_pending( ex ); print '*'; end
def close
puts
puts "Failures:" unless @failures.empty?
@failures.each do |test|
metadata = test.metadata
msg = "- %s\n %s\n %s:%d\n\n" % [
metadata[:full_description],
test.exception.message,
metadata[:file_path],
metadata[:line_number]
]
puts msg
end
output.puts @summary
end
end