Added Rspec and Webrat plugins and started porting Selenium on Rails tests to Rspec Plain Text Stories driving Webrat driving Selenium.

This commit is contained in:
Luke Melia 2008-06-18 02:57:57 -04:00
parent 0600756bbf
commit 0f7d6f7a1d
602 changed files with 47788 additions and 29 deletions

View file

@ -0,0 +1,77 @@
module Spec
module Runner
module Formatter
# Baseclass for formatters that implements all required methods as no-ops.
class BaseFormatter
attr_accessor :example_group, :options, :where
def initialize(options, where)
@options = options
@where = where
end
# This method is invoked before any examples are run, right after
# they have all been collected. This can be useful for special
# formatters that need to provide progress on feedback (graphical ones)
#
# This method will only be invoked once, and the next one to be invoked
# is #add_example_group
def start(example_count)
end
# This method is invoked at the beginning of the execution of each example_group.
# +example_group+ is the example_group.
#
# The next method to be invoked after this is #example_failed or #example_finished
def add_example_group(example_group)
@example_group = example_group
end
# This method is invoked when an +example+ starts.
def example_started(example)
end
# This method is invoked when an +example+ passes.
def example_passed(example)
end
# This method is invoked when an +example+ fails, i.e. an exception occurred
# inside it (such as a failed should or other exception). +counter+ is the
# sequence number of the failure (starting at 1) and +failure+ is the associated
# Failure object.
def example_failed(example, counter, failure)
end
# This method is invoked when an example is not yet implemented (i.e. has not
# been provided a block), or when an ExamplePendingError is raised.
# +message+ is the message from the ExamplePendingError, if it exists, or the
# default value of "Not Yet Implemented"
def example_pending(example, message)
end
# This method is invoked after all of the examples have executed. The next method
# to be invoked after this one is #dump_failure (once for each failed example),
def start_dump
end
# Dumps detailed information about an example failure.
# This method is invoked for each failed example after all examples have run. +counter+ is the sequence number
# of the associated example. +failure+ is a Failure object, which contains detailed
# information about the failure.
def dump_failure(counter, failure)
end
# This method is invoked after the dumping of examples and failures.
def dump_summary(duration, example_count, failure_count, pending_count)
end
# This gets invoked after the summary if option is set to do so.
def dump_pending
end
# This method is invoked at the very end. Allows the formatter to clean up, like closing open streams.
def close
end
end
end
end
end

View file

@ -0,0 +1,130 @@
require 'spec/runner/formatter/base_formatter'
module Spec
module Runner
module Formatter
# Baseclass for text-based formatters. Can in fact be used for
# non-text based ones too - just ignore the +output+ constructor
# argument.
class BaseTextFormatter < BaseFormatter
attr_reader :output, :pending_examples
# Creates a new instance that will write to +where+. If +where+ is a
# String, output will be written to the File with that name, otherwise
# +where+ is exected to be an IO (or an object that responds to #puts and #write).
def initialize(options, where)
super
if where.is_a?(String)
@output = File.open(where, 'w')
elsif where == STDOUT
@output = Kernel
def @output.flush
STDOUT.flush
end
else
@output = where
end
@pending_examples = []
end
def example_pending(example, message)
@pending_examples << [example.__full_description, message]
end
def dump_failure(counter, failure)
@output.puts
@output.puts "#{counter.to_s})"
@output.puts colourise("#{failure.header}\n#{failure.exception.message}", failure)
@output.puts format_backtrace(failure.exception.backtrace)
@output.flush
end
def colourise(s, failure)
if(failure.expectation_not_met?)
red(s)
elsif(failure.pending_fixed?)
blue(s)
else
magenta(s)
end
end
def dump_summary(duration, example_count, failure_count, pending_count)
return if dry_run?
@output.puts
@output.puts "Finished in #{duration} seconds"
@output.puts
summary = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}"
summary << ", #{pending_count} pending" if pending_count > 0
if failure_count == 0
if pending_count > 0
@output.puts yellow(summary)
else
@output.puts green(summary)
end
else
@output.puts red(summary)
end
@output.flush
end
def dump_pending
unless @pending_examples.empty?
@output.puts
@output.puts "Pending:"
@pending_examples.each do |pending_example|
@output.puts "#{pending_example[0]} (#{pending_example[1]})"
end
end
@output.flush
end
def close
if IO === @output
@output.close
end
end
def format_backtrace(backtrace)
return "" if backtrace.nil?
backtrace.map { |line| backtrace_line(line) }.join("\n")
end
protected
def colour?
@options.colour ? true : false
end
def dry_run?
@options.dry_run ? true : false
end
def backtrace_line(line)
line.sub(/\A([^:]+:\d+)$/, '\\1:')
end
def colour(text, colour_code)
return text unless colour? && output_to_tty?
"#{colour_code}#{text}\e[0m"
end
def output_to_tty?
begin
@output == Kernel || @output.tty?
rescue NoMethodError
false
end
end
def green(text); colour(text, "\e[32m"); end
def red(text); colour(text, "\e[31m"); end
def magenta(text); colour(text, "\e[35m"); end
def yellow(text); colour(text, "\e[33m"); end
def blue(text); colour(text, "\e[34m"); end
end
end
end
end

View file

@ -0,0 +1,27 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class FailingExampleGroupsFormatter < BaseTextFormatter
def example_failed(example, counter, failure)
if @example_group
description_parts = @example_group.description_parts.collect do |description|
description =~ /(.*) \(druby.*\)$/ ? $1 : description
end
@output.puts ::Spec::Example::ExampleGroupMethods.description_text(*description_parts)
@output.flush
@example_group = nil
end
end
def dump_failure(counter, failure)
end
def dump_summary(duration, example_count, failure_count, pending_count)
end
end
end
end
end

View file

@ -0,0 +1,20 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class FailingExamplesFormatter < BaseTextFormatter
def example_failed(example, counter, failure)
@output.puts "#{example_group.description} #{example.description}"
@output.flush
end
def dump_failure(counter, failure)
end
def dump_summary(duration, example_count, failure_count, pending_count)
end
end
end
end
end

View file

@ -0,0 +1,337 @@
require 'erb'
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class HtmlFormatter < BaseTextFormatter
include ERB::Util # for the #h method
def initialize(options, output)
super
@example_group_number = 0
@example_number = 0
end
def method_missing(sym, *args)
# no-op
end
# The number of the currently running example_group
def example_group_number
@example_group_number
end
# The number of the currently running example (a global counter)
def example_number
@example_number
end
def start(example_count)
@example_count = example_count
@output.puts html_header
@output.puts report_header
@output.flush
end
def add_example_group(example_group)
super
@example_group_red = false
@example_group_red = false
@example_group_number += 1
unless example_group_number == 1
@output.puts " </dl>"
@output.puts "</div>"
end
@output.puts "<div class=\"example_group\">"
@output.puts " <dl>"
@output.puts " <dt id=\"example_group_#{example_group_number}\">#{h(example_group.description)}</dt>"
@output.flush
end
def start_dump
@output.puts " </dl>"
@output.puts "</div>"
@output.flush
end
def example_started(example)
@example_number += 1
end
def example_passed(example)
move_progress
@output.puts " <dd class=\"spec passed\"><span class=\"passed_spec_name\">#{h(example.description)}</span></dd>"
@output.flush
end
def example_failed(example, counter, failure)
extra = extra_failure_content(failure)
failure_style = failure.pending_fixed? ? 'pending_fixed' : 'failed'
@output.puts " <script type=\"text/javascript\">makeRed('rspec-header');</script>" unless @header_red
@header_red = true
@output.puts " <script type=\"text/javascript\">makeRed('example_group_#{example_group_number}');</script>" unless @example_group_red
@example_group_red = true
move_progress
@output.puts " <dd class=\"spec #{failure_style}\">"
@output.puts " <span class=\"failed_spec_name\">#{h(example.description)}</span>"
@output.puts " <div class=\"failure\" id=\"failure_#{counter}\">"
@output.puts " <div class=\"message\"><pre>#{h(failure.exception.message)}</pre></div>" unless failure.exception.nil?
@output.puts " <div class=\"backtrace\"><pre>#{format_backtrace(failure.exception.backtrace)}</pre></div>" unless failure.exception.nil?
@output.puts extra unless extra == ""
@output.puts " </div>"
@output.puts " </dd>"
@output.flush
end
def example_pending(example, message)
@output.puts " <script type=\"text/javascript\">makeYellow('rspec-header');</script>" unless @header_red
@output.puts " <script type=\"text/javascript\">makeYellow('example_group_#{example_group_number}');</script>" unless @example_group_red
move_progress
@output.puts " <dd class=\"spec not_implemented\"><span class=\"not_implemented_spec_name\">#{h(example.description)} (PENDING: #{h(message)})</span></dd>"
@output.flush
end
# Override this method if you wish to output extra HTML for a failed spec. For example, you
# could output links to images or other files produced during the specs.
#
def extra_failure_content(failure)
require 'spec/runner/formatter/snippet_extractor'
@snippet_extractor ||= SnippetExtractor.new
" <pre class=\"ruby\"><code>#{@snippet_extractor.snippet(failure.exception)}</code></pre>"
end
def move_progress
@output.puts " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
@output.flush
end
def percent_done
result = 100.0
if @example_count != 0
result = ((example_number).to_f / @example_count.to_f * 1000).to_i / 10.0
end
result
end
def dump_failure(counter, failure)
end
def dump_summary(duration, example_count, failure_count, pending_count)
if dry_run?
totals = "This was a dry-run"
else
totals = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}"
totals << ", #{pending_count} pending" if pending_count > 0
end
@output.puts "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{duration} seconds</strong>\";</script>"
@output.puts "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{totals}\";</script>"
@output.puts "</div>"
@output.puts "</div>"
@output.puts "</body>"
@output.puts "</html>"
@output.flush
end
def html_header
<<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>RSpec results</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Expires" content="-1" />
<meta http-equiv="Pragma" content="no-cache" />
<style type="text/css">
body {
margin: 0;
padding: 0;
background: #fff;
font-size: 80%;
}
</style>
</head>
<body>
EOF
end
def report_header
<<-EOF
<div class="rspec-report">
<script type="text/javascript">
// <![CDATA[
#{global_scripts}
// ]]>
</script>
<style type="text/css">
#{global_styles}
</style>
<div id="rspec-header">
<h1>RSpec Results</h1>
<div id="summary">
<p id="totals">&nbsp;</p>
<p id="duration">&nbsp;</p>
</div>
</div>
<div class="results">
EOF
end
def global_scripts
<<-EOF
function moveProgressBar(percentDone) {
document.getElementById("rspec-header").style.width = percentDone +"%";
}
function makeRed(element_id) {
document.getElementById(element_id).style.background = '#C40D0D';
document.getElementById(element_id).style.color = '#FFFFFF';
}
function makeYellow(element_id) {
if (element_id == "rspec-header" && document.getElementById(element_id).style.background != '#C40D0D')
{
document.getElementById(element_id).style.background = '#FAF834';
document.getElementById(element_id).style.color = '#000000';
}
else
{
document.getElementById(element_id).style.background = '#FAF834';
document.getElementById(element_id).style.color = '#000000';
}
}
EOF
end
def global_styles
<<-EOF
#rspec-header {
background: #65C400; color: #fff;
}
.rspec-report h1 {
margin: 0px 10px 0px 10px;
padding: 10px;
font-family: "Lucida Grande", Helvetica, sans-serif;
font-size: 1.8em;
}
#summary {
margin: 0; padding: 5px 10px;
font-family: "Lucida Grande", Helvetica, sans-serif;
text-align: right;
position: absolute;
top: 0px;
right: 0px;
}
#summary p {
margin: 0 0 0 2px;
}
#summary #totals {
font-size: 1.2em;
}
.example_group {
margin: 0 10px 5px;
background: #fff;
}
dl {
margin: 0; padding: 0 0 5px;
font: normal 11px "Lucida Grande", Helvetica, sans-serif;
}
dt {
padding: 3px;
background: #65C400;
color: #fff;
font-weight: bold;
}
dd {
margin: 5px 0 5px 5px;
padding: 3px 3px 3px 18px;
}
dd.spec.passed {
border-left: 5px solid #65C400;
border-bottom: 1px solid #65C400;
background: #DBFFB4; color: #3D7700;
}
dd.spec.failed {
border-left: 5px solid #C20000;
border-bottom: 1px solid #C20000;
color: #C20000; background: #FFFBD3;
}
dd.spec.not_implemented {
border-left: 5px solid #FAF834;
border-bottom: 1px solid #FAF834;
background: #FCFB98; color: #131313;
}
dd.spec.pending_fixed {
border-left: 5px solid #0000C2;
border-bottom: 1px solid #0000C2;
color: #0000C2; background: #D3FBFF;
}
.backtrace {
color: #000;
font-size: 12px;
}
a {
color: #BE5C00;
}
/* Ruby code, style similar to vibrant ink */
.ruby {
font-size: 12px;
font-family: monospace;
color: white;
background-color: black;
padding: 0.1em 0 0.2em 0;
}
.ruby .keyword { color: #FF6600; }
.ruby .constant { color: #339999; }
.ruby .attribute { color: white; }
.ruby .global { color: white; }
.ruby .module { color: white; }
.ruby .class { color: white; }
.ruby .string { color: #66FF00; }
.ruby .ident { color: white; }
.ruby .method { color: #FFCC00; }
.ruby .number { color: white; }
.ruby .char { color: white; }
.ruby .comment { color: #9933CC; }
.ruby .symbol { color: white; }
.ruby .regex { color: #44B4CC; }
.ruby .punct { color: white; }
.ruby .escape { color: white; }
.ruby .interp { color: white; }
.ruby .expr { color: white; }
.ruby .offending { background-color: gray; }
.ruby .linenum {
width: 75px;
padding: 0.1em 1em 0.2em 0;
color: #000000;
background-color: #FFFBD3;
}
EOF
end
end
end
end
end

View file

@ -0,0 +1,65 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class NestedTextFormatter < BaseTextFormatter
attr_reader :previous_nested_example_groups
def initialize(options, where)
super
@previous_nested_example_groups = []
end
def add_example_group(example_group)
super
current_nested_example_groups = described_example_group_chain
current_nested_example_groups.each_with_index do |nested_example_group, i|
unless nested_example_group == previous_nested_example_groups[i]
output.puts "#{' ' * i}#{nested_example_group.description_args}"
end
end
@previous_nested_example_groups = described_example_group_chain
end
def example_failed(example, counter, failure)
message = if failure.expectation_not_met?
"#{current_indentation}#{example.description} (FAILED - #{counter})"
else
"#{current_indentation}#{example.description} (ERROR - #{counter})"
end
output.puts(failure.expectation_not_met? ? red(message) : magenta(message))
output.flush
end
def example_passed(example)
message = "#{current_indentation}#{example.description}"
output.puts green(message)
output.flush
end
def example_pending(example, message)
super
output.puts yellow("#{current_indentation}#{example.description} (PENDING: #{message})")
output.flush
end
def current_indentation
' ' * previous_nested_example_groups.length
end
def described_example_group_chain
example_group_chain = []
example_group.send(:execute_in_class_hierarchy) do |parent_example_group|
if parent_example_group.description_args && !parent_example_group.description_args.empty?
example_group_chain << parent_example_group
end
end
example_group_chain
end
end
end
end
end

View file

@ -0,0 +1,51 @@
require 'spec/runner/formatter/progress_bar_formatter'
module Spec
module Runner
module Formatter
class ProfileFormatter < ProgressBarFormatter
def initialize(options, where)
super
@example_times = []
end
def start(count)
@output.puts "Profiling enabled."
end
def example_started(example)
@time = Time.now
end
def example_passed(example)
super
@example_times << [
example_group.description,
example.description,
Time.now - @time
]
end
def start_dump
super
@output.puts "\n\nTop 10 slowest examples:\n"
@example_times = @example_times.sort_by do |description, example, time|
time
end.reverse
@example_times[0..9].each do |description, example, time|
@output.print red(sprintf("%.7f", time))
@output.puts " #{description} #{example}"
end
@output.flush
end
def method_missing(sym, *args)
# ignore
end
end
end
end
end

View file

@ -0,0 +1,34 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class ProgressBarFormatter < BaseTextFormatter
def example_failed(example, counter, failure)
@output.print colourise('F', failure)
@output.flush
end
def example_passed(example)
@output.print green('.')
@output.flush
end
def example_pending(example, message)
super
@output.print yellow('P')
@output.flush
end
def start_dump
@output.puts
@output.flush
end
def method_missing(sym, *args)
# ignore
end
end
end
end
end

View file

@ -0,0 +1,52 @@
module Spec
module Runner
module Formatter
# This class extracts code snippets by looking at the backtrace of the passed error
class SnippetExtractor #:nodoc:
class NullConverter; def convert(code, pre); code; end; end #:nodoc:
begin; require 'rubygems'; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end
def snippet(error)
raw_code, line = snippet_for(error.backtrace[0])
highlighted = @@converter.convert(raw_code, false)
highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
post_process(highlighted, line)
end
def snippet_for(error_line)
if error_line =~ /(.*):(\d+)/
file = $1
line = $2.to_i
[lines_around(file, line), line]
else
["# Couldn't get snippet for #{error_line}", 1]
end
end
def lines_around(file, line)
if File.file?(file)
lines = File.open(file).read.split("\n")
min = [0, line-3].max
max = [line+1, lines.length-1].min
selected_lines = []
selected_lines.join("\n")
lines[min..max].join("\n")
else
"# Couldn't get snippet for #{file}"
end
end
def post_process(highlighted, offending_line)
new_lines = []
highlighted.split("\n").each_with_index do |line, i|
new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
new_lines << new_line
end
new_lines.join("\n")
end
end
end
end
end

View file

@ -0,0 +1,39 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class SpecdocFormatter < BaseTextFormatter
def add_example_group(example_group)
super
output.puts
output.puts example_group.description
output.flush
end
def example_failed(example, counter, failure)
message = if failure.expectation_not_met?
"- #{example.description} (FAILED - #{counter})"
else
"- #{example.description} (ERROR - #{counter})"
end
output.puts(failure.expectation_not_met? ? red(message) : magenta(message))
output.flush
end
def example_passed(example)
message = "- #{example.description}"
output.puts green(message)
output.flush
end
def example_pending(example, message)
super
output.puts yellow("- #{example.description} (PENDING: #{message})")
output.flush
end
end
end
end
end

View file

@ -0,0 +1,161 @@
require 'erb'
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
module Story
class HtmlFormatter < BaseTextFormatter
include ERB::Util
def initialize(options, where)
super
@previous_type = nil
@scenario_text = ""
@story_text = ""
@scenario_failed = false
@story_failed = false
end
def run_started(count)
@output.puts <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Stories</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Expires" content="-1" />
<meta http-equiv="Pragma" content="no-cache" />
<script src="javascripts/prototype.js" type="text/javascript"></script>
<script src="javascripts/scriptaculous.js" type="text/javascript"></script>
<script src="javascripts/rspec.js" type="text/javascript"></script>
<link href="stylesheets/rspec.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="container">
EOF
end
def collected_steps(steps)
unless steps.empty?
@output.puts " <ul id=\"stock_steps\" style=\"display: none;\">"
steps.each do |step|
@output.puts " <li>#{step}</li>"
end
@output.puts " </ul>"
end
end
def run_ended
@output.puts <<-EOF
</div>
</body>
</head>
EOF
end
def story_started(title, narrative)
@story_failed = false
@story_text = <<-EOF
<dt>Story: #{h title}</dt>
<dd>
<p>
#{h(narrative).split("\n").join("<br />")}
</p>
EOF
end
def story_ended(title, narrative)
if @story_failed
@output.puts <<-EOF
<dl class="story failed">
EOF
else
@output.puts <<-EOF
<dl class="story passed">
EOF
end
@output.puts <<-EOF
#{@story_text}
</dd>
</dl>
EOF
end
def scenario_started(story_title, scenario_name)
@scenario_failed = false
@scenario_text = <<-EOF
<dt>Scenario: #{h scenario_name}</dt>
<dd>
<ul class="steps">
EOF
end
def scenario_ended
if @scenario_failed
@story_text += <<-EOF
<dl class="failed">
EOF
else
@story_text += <<-EOF
<dl class="passed">
EOF
end
@story_text += <<-EOF
#{@scenario_text}
</ul>
</dd>
</dl>
EOF
end
def found_scenario(type, description)
end
def scenario_succeeded(story_title, scenario_name)
scenario_ended
end
def scenario_pending(story_title, scenario_name, reason)
scenario_ended
end
def scenario_failed(story_title, scenario_name, err)
@scenario_failed = true
@story_failed = true
scenario_ended
end
def step_upcoming(type, description, *args)
end
def step_succeeded(type, description, *args)
print_step('passed', type, description, *args) # TODO: uses succeeded CSS class
end
def step_pending(type, description, *args)
print_step('pending', type, description, *args)
end
def step_failed(type, description, *args)
print_step('failed', type, description, *args)
end
def print_step(klass, type, description, *args)
spans = args.map { |arg| "<span class=\"param\">#{arg}</span>" }
desc_string = description.step_name
arg_regexp = description.arg_regexp
i = -1
inner = type.to_s.capitalize + ' ' + desc_string.gsub(arg_regexp) { |param| spans[i+=1] }
@scenario_text += " <li class=\"#{klass}\">#{inner}</li>\n"
end
end
end
end
end
end

View file

@ -0,0 +1,188 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
module Story
class PlainTextFormatter < BaseTextFormatter
def initialize(options, where)
super
@successful_scenario_count = 0
@pending_scenario_count = 0
@pre_story_pending_count = 0
@pre_story_successful_count = 0
@failed_scenarios = []
@pending_steps = []
@previous_type = nil
@scenario_body_text = ""
@story_body_text = ""
@scenario_head_text = ""
@story_head_text = ""
@scenario_failed = false
@story_failed = false
end
def run_started(count)
@count = count
@output.puts "Running #@count scenarios\n\n"
end
def story_started(title, narrative)
@pre_story_pending_count = @pending_scenario_count
@pre_story_successful_count = @successful_scenario_count
@current_story_title = title
@story_failed = false
@story_body_text = ""
@story_head_text = "Story: #{title}\n\n"
narrative.each_line do |line|
@story_head_text += " "
@story_head_text += line
end
end
def story_ended(title, narrative)
if @story_failed
@output.print red(@story_head_text)
elsif @pre_story_successful_count == @successful_scenario_count &&
@pending_scenario_count >= @pre_story_pending_count
@output.print yellow(@story_head_text)
else
@output.print green(@story_head_text)
end
@output.print @story_body_text
@output.puts
@output.puts
end
def scenario_started(story_title, scenario_name)
@current_scenario_name = scenario_name
@scenario_already_failed = false
@scenario_head_text = "\n\n Scenario: #{scenario_name}"
@scenario_body_text = ""
@scenario_ok = true
@scenario_pending = false
@scenario_failed = false
end
def scenario_succeeded(story_title, scenario_name)
@successful_scenario_count += 1
scenario_ended
end
def scenario_failed(story_title, scenario_name, err)
@options.backtrace_tweaker.tweak_backtrace(err)
@failed_scenarios << [story_title, scenario_name, err] unless @scenario_already_failed
@scenario_already_failed = true
@story_failed = true
@scenario_failed = true
scenario_ended
end
def scenario_pending(story_title, scenario_name, msg)
@pending_scenario_count += 1 unless @scenario_already_failed
@scenario_pending = true
@scenario_already_failed = true
scenario_ended
end
def scenario_ended
if @scenario_failed
@story_body_text += red(@scenario_head_text)
elsif @scenario_pending
@story_body_text += yellow(@scenario_head_text)
else
@story_body_text += green(@scenario_head_text)
end
@story_body_text += @scenario_body_text
end
def run_ended
@output.puts "#@count scenarios: #@successful_scenario_count succeeded, #{@failed_scenarios.size} failed, #@pending_scenario_count pending"
unless @pending_steps.empty?
@output.puts "\nPending Steps:"
@pending_steps.each_with_index do |pending, i|
story_name, scenario_name, msg = pending
@output.puts "#{i+1}) #{story_name} (#{scenario_name}): #{msg}"
end
end
unless @failed_scenarios.empty?
@output.print "\nFAILURES:"
@failed_scenarios.each_with_index do |failure, i|
title, scenario_name, err = failure
@output.print %[
#{i+1}) #{title} (#{scenario_name}) FAILED
#{err.class}: #{err.message}
#{err.backtrace.join("\n")}
]
end
end
end
def step_upcoming(type, description, *args)
end
def step_succeeded(type, description, *args)
found_step(type, description, false, false, *args)
end
def step_pending(type, description, *args)
found_step(type, description, false, true, *args)
@pending_steps << [@current_story_title, @current_scenario_name, description]
@scenario_body_text += yellow(" (PENDING)")
@scenario_pending = true
@scenario_ok = false
end
def step_failed(type, description, *args)
found_step(type, description, true, @scenario_pending, *args)
if @scenario_pending
@scenario_body_text += yellow(" (SKIPPED)")
else
@scenario_body_text += red(@scenario_ok ? " (FAILED)" : " (SKIPPED)")
end
@scenario_ok = false
end
def collected_steps(steps)
end
def method_missing(sym, *args, &block) #:nodoc:
# noop - ignore unknown messages
end
private
def found_step(type, description, failed, pending, *args)
desc_string = description.step_name
arg_regexp = description.arg_regexp
text = if(type == @previous_type)
"\n And "
else
"\n\n #{type.to_s.capitalize} "
end
i = -1
text << desc_string.gsub(arg_regexp) { |param| args[i+=1] }
if pending
@scenario_body_text += yellow(text)
else
@scenario_body_text += (failed ? red(text) : green(text))
end
if type == :'given scenario'
@previous_type = :given
else
@previous_type = type
end
end
end
end
end
end
end

View file

@ -0,0 +1,16 @@
require 'spec/runner/formatter/html_formatter'
module Spec
module Runner
module Formatter
# Formats backtraces so they're clickable by TextMate
class TextMateFormatter < HtmlFormatter
def backtrace_line(line)
line.gsub(/([^:]*\.rb):(\d*)/) do
"<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
end
end
end
end
end
end