diff -r 2b198f0a86fe -r a0e6ddfadf82 ruby/specky_formatter.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ruby/specky_formatter.rb Sat Jan 16 11:31:53 2016 -0800 @@ -0,0 +1,215 @@ + +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 + if shared_group = find_shared_group(example) + self.out "Shared Example Group: \"#{shared_group.metadata[:shared_group_name]}\" called from " + + "#{backtrace_line(shared_group.metadata[:example_group][:location])}" + 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'; super; 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 +