|
1 |
|
2 require 'rspec/core/formatters/base_text_formatter' |
|
3 |
|
4 ### SpeckyFormatter: A basic RSpec 2.x text formatter, to be used |
|
5 ### with the 'Specky' vim plugin (or from the command line, if you |
|
6 ### dig it over the default 'documentation' format!) |
|
7 ### |
|
8 ### rspec -r /path/to/this/specky_formatter.rb -f SpeckyFormatter specs |
|
9 ### |
|
10 class SpeckyFormatter < RSpec::Core::Formatters::BaseTextFormatter |
|
11 |
|
12 def initialize( *args ) |
|
13 super |
|
14 @indent_level = 0 |
|
15 @failure_index = 0 |
|
16 @failures = [] |
|
17 @txt = '' |
|
18 @summary = '' |
|
19 end |
|
20 |
|
21 |
|
22 ######################################################################## |
|
23 ### R S P E C H O O K S |
|
24 ######################################################################## |
|
25 |
|
26 ### Example group hook -- increase indentation, emit description |
|
27 ### |
|
28 def example_group_started( example_group ) |
|
29 self.out '+', '-' * (example_group.description.length + 2), '+' |
|
30 self.out '| ', example_group.description, ' |' |
|
31 self.out '+', '-' * (example_group.description.length + 2), '+' |
|
32 @indent_level += 1 |
|
33 end |
|
34 |
|
35 |
|
36 ### Example group hook -- decrease indentation |
|
37 ### |
|
38 def example_group_finished( example_group ) |
|
39 @indent_level -= 1 |
|
40 end |
|
41 |
|
42 |
|
43 ### Called on example success |
|
44 ### |
|
45 def example_passed( example ) |
|
46 msg = self.format_example( example ) |
|
47 msg << ')' |
|
48 self.out msg |
|
49 end |
|
50 |
|
51 |
|
52 ### Called on a pending example |
|
53 ### |
|
54 def example_pending( example ) |
|
55 msg = self.format_example( example ) |
|
56 pending_msg = example.metadata[ :execution_result ][ :pending_message ] |
|
57 msg << ", PENDING%s)" % [ ": #{pending_msg}" || '' ] |
|
58 self.out msg |
|
59 end |
|
60 |
|
61 |
|
62 ### Called on example failure |
|
63 ### |
|
64 def example_failed( example ) |
|
65 @failure_index += 1 |
|
66 msg = self.format_example( example ) |
|
67 msg << ", FAILED - #%d)" % [ @failure_index ] |
|
68 self.out msg |
|
69 |
|
70 @failures << example |
|
71 end |
|
72 |
|
73 |
|
74 ### Called after all examples are run. Emit details for each failed example, |
|
75 ### for Vim to fold. |
|
76 ### |
|
77 def dump_failures |
|
78 self.out "\n" unless @failures.empty? |
|
79 cwd = Regexp.new( Dir.pwd ) |
|
80 bt_regexp = /(.+?):(\d+)(|:\d+)/ |
|
81 |
|
82 @failures.each_with_index do |example, index| |
|
83 desc = example.metadata[ :full_description ] |
|
84 exception = example.execution_result[ :exception ] |
|
85 file = line = nil |
|
86 |
|
87 # remove files that are not in within the cwd. |
|
88 # |
|
89 # this isn't optimal, but it does stay within specky's notion of |
|
90 # running it from within the project directory, and makes sure we don't |
|
91 # inadvertently display code from rspec itself. |
|
92 # |
|
93 bt_file = exception.backtrace.find { |line| line =~ bt_regexp && line =~ cwd } |
|
94 file, line = $1, $2.to_i if bt_file =~ bt_regexp |
|
95 self.out "FAILURE - #%d)" % [ index + 1 ] |
|
96 self.out "%s:%d" % [ file, line ] if bt_file |
|
97 |
|
98 if exception.respond_to?( :pending_fixed? ) && exception.pending_fixed? |
|
99 self.out "%s FIXED" % [ desc ] |
|
100 self.out "Expected pending '%s' to fail. No error was raised." % [ |
|
101 example.metadata[ :execution_result ][ :pending_message ] |
|
102 ] |
|
103 else |
|
104 self.out desc |
|
105 self.out "Failure/Error: %s" % [ read_failed_line( exception, example).strip ] |
|
106 exception.message.split("\n").each {|l| self.out l} |
|
107 |
|
108 # logic taken from the base class |
|
109 if shared_group = find_shared_group(example) |
|
110 self.out "Shared Example Group: \"#{shared_group.metadata[:shared_group_name]}\" called from " + |
|
111 "#{backtrace_line(shared_group.metadata[:example_group][:location])}" |
|
112 end |
|
113 end |
|
114 |
|
115 self.out exception_source( file, line-1 ) if file && line |
|
116 end |
|
117 end |
|
118 |
|
119 |
|
120 ### Emit the source of the exception, with context lines. |
|
121 ### |
|
122 def exception_source( file, line ) |
|
123 context = '' |
|
124 low, high = line - 3, line + 3 |
|
125 |
|
126 File.open( file ).each_with_index do |cline, i| |
|
127 cline.chomp!.rstrip! |
|
128 next unless i >= low && i <= high |
|
129 context << " %s%4d: %s\n" % [ ( i == line ? '>>' : ' |' ), i, cline ] |
|
130 end |
|
131 |
|
132 return context |
|
133 |
|
134 rescue |
|
135 'Unable to parse exception context lines.' |
|
136 end |
|
137 |
|
138 |
|
139 ### Emit summary data for all examples. |
|
140 ### |
|
141 def dump_summary( duration, example_count, failure_count, pending_count ) |
|
142 succeeded = example_count - failure_count - pending_count |
|
143 @summary << "+%s+\n" % [ '-' * 49 ] |
|
144 @summary << "|%s-- Summary --%s|\n" % [ ' ' * 18, ' ' * 18 ] |
|
145 @summary << "+----------+-----------+--------+---------+-------+\n" |
|
146 @summary << "| Duration | Succeeded | Failed | Pending | Total |\n" |
|
147 @summary << "+----------+-----------+--------+---------+-------+\n" |
|
148 |
|
149 @summary << "| %7ss | %9s | %6s | %7s | %5s |\n" % [ |
|
150 "%0.3f" % duration, succeeded, failure_count, |
|
151 pending_count, example_count |
|
152 ] |
|
153 |
|
154 @summary << "+----------+-----------+--------+---------+-------+\n\n" |
|
155 end |
|
156 |
|
157 |
|
158 ### End of run. Dump it all out! |
|
159 ### |
|
160 def close |
|
161 output.puts @summary |
|
162 output.puts @txt |
|
163 end |
|
164 |
|
165 |
|
166 ######### |
|
167 protected |
|
168 ######### |
|
169 |
|
170 ### Send a string to the output IO object, after indentation. |
|
171 ### |
|
172 def out( *msg ) |
|
173 msg = msg.join |
|
174 @txt << "%s%s\n" % [ ' ' * @indent_level, msg ] |
|
175 end |
|
176 |
|
177 ### Format the basic example information, along with the run duration. |
|
178 ### |
|
179 def format_example( example ) |
|
180 metadata = example.metadata |
|
181 duration = metadata[ :execution_result ][ :run_time ] |
|
182 description = metadata[ :description ] |
|
183 return "| %s (%0.3fs" % [ description, duration ] |
|
184 end |
|
185 end # SpeckyFormatter |
|
186 |
|
187 |
|
188 ### Identical to the regular SpeckyFormatter, but it puts summary |
|
189 ### information at the bottom of the screen instead of the top, and just |
|
190 ### spits out rudamentary failure info. Mimics the progress |
|
191 ### formatter for status feedback |
|
192 ### |
|
193 class SpeckyFormatterConsole < SpeckyFormatter |
|
194 |
|
195 def example_passed( ex ); print '.'; end |
|
196 def example_failed( ex ); print 'F'; super; end |
|
197 def example_pending( ex ); print '*'; end |
|
198 |
|
199 def close |
|
200 puts |
|
201 puts "Failures:" unless @failures.empty? |
|
202 @failures.each do |test| |
|
203 metadata = test.metadata |
|
204 msg = "- %s\n %s\n %s:%d\n\n" % [ |
|
205 metadata[:full_description], |
|
206 test.exception.message, |
|
207 metadata[:file_path], |
|
208 metadata[:line_number] |
|
209 ] |
|
210 puts msg |
|
211 end |
|
212 output.puts @summary |
|
213 end |
|
214 end |
|
215 |