specky/ruby/specky_formatter.rb
author Mahlon E. Smith <mahlon@martini.nu>
Wed, 02 Jan 2013 09:14:17 -0800
branchvim-stuff
changeset 28 2b198f0a86fe
parent 24 38db5185d698
permissions -rw-r--r--
Update for rspec 2.12.


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