cucumber 0.3.102 → 0.3.103

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/History.txt +18 -0
  2. data/Manifest.txt +6 -2
  3. data/examples/java/README.textile +3 -3
  4. data/examples/self_test/features/sample.feature +1 -1
  5. data/examples/self_test/features/search_sample.feature +1 -1
  6. data/features/custom_formatter.feature +4 -4
  7. data/features/steps_formatter.feature +2 -1
  8. data/lib/cucumber/ast.rb +1 -0
  9. data/lib/cucumber/ast/table.rb +4 -4
  10. data/lib/cucumber/ast/tree_walker.rb +185 -0
  11. data/lib/cucumber/ast/visitor.rb +2 -106
  12. data/lib/cucumber/cli/configuration.rb +28 -28
  13. data/lib/cucumber/cli/language_help_formatter.rb +5 -7
  14. data/lib/cucumber/cli/main.rb +3 -3
  15. data/lib/cucumber/core_ext/string.rb +0 -22
  16. data/lib/cucumber/formatter/html.rb +203 -113
  17. data/lib/cucumber/formatter/junit.rb +29 -23
  18. data/lib/cucumber/formatter/pdf.rb +74 -69
  19. data/lib/cucumber/formatter/pretty.rb +93 -78
  20. data/lib/cucumber/formatter/profile.rb +2 -2
  21. data/lib/cucumber/formatter/progress.rb +16 -10
  22. data/lib/cucumber/formatter/rerun.rb +4 -5
  23. data/lib/cucumber/formatter/steps.rb +6 -7
  24. data/lib/cucumber/formatter/tag_cloud.rb +7 -6
  25. data/lib/cucumber/formatter/usage.rb +7 -10
  26. data/lib/cucumber/language_support/step_definition_methods.rb +4 -4
  27. data/lib/cucumber/rails/action_controller.rb +1 -1
  28. data/lib/cucumber/rails/active_record.rb +27 -14
  29. data/lib/cucumber/rb_support/rb_language.rb +5 -0
  30. data/lib/cucumber/rb_support/rb_step_definition.rb +9 -16
  31. data/lib/cucumber/rb_support/regexp_argument_matcher.rb +21 -0
  32. data/lib/cucumber/step_argument.rb +9 -0
  33. data/lib/cucumber/step_match.rb +24 -5
  34. data/lib/cucumber/version.rb +1 -1
  35. data/rails_generators/cucumber/templates/env.rb +14 -0
  36. data/spec/cucumber/ast/background_spec.rb +1 -2
  37. data/spec/cucumber/ast/scenario_outline_spec.rb +3 -2
  38. data/spec/cucumber/ast/scenario_spec.rb +1 -1
  39. data/spec/cucumber/ast/tree_walker_spec.rb +18 -0
  40. data/spec/cucumber/formatter/html_spec.rb +221 -2
  41. data/spec/cucumber/formatter/progress_spec.rb +9 -4
  42. data/spec/cucumber/parser/feature_parser_spec.rb +31 -27
  43. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +18 -0
  44. data/spec/cucumber/step_match_spec.rb +40 -0
  45. metadata +8 -4
  46. data/lib/cucumber/rb_support/rb_group.rb +0 -11
  47. data/spec/cucumber/core_ext/string_spec.rb +0 -41
@@ -57,34 +57,9 @@ module Cucumber
57
57
  def drb_port
58
58
  @options[:drb_port].to_i if @options[:drb_port]
59
59
  end
60
-
61
- def build_formatter_broadcaster(step_mother)
62
- return Formatter::Pretty.new(step_mother, nil, @options) if @options[:autoformat]
63
- formatters = @options[:formats].map do |format_and_out|
64
- format = format_and_out[0]
65
- out = format_and_out[1]
66
- if String === out # file name
67
- unless File.directory?(out)
68
- out = File.open(out, Cucumber.file_mode('w'))
69
- at_exit do
70
- out.flush
71
- out.close
72
- end
73
- end
74
- end
75
-
76
- begin
77
- formatter_class = formatter_class(format)
78
- formatter_class.new(step_mother, out, @options)
79
- rescue Exception => e
80
- e.message << "\nError creating formatter: #{format}"
81
- raise e
82
- end
83
- end
84
-
85
- broadcaster = Broadcaster.new(formatters)
86
- broadcaster.options = @options
87
- return broadcaster
60
+
61
+ def build_runner(step_mother, io)
62
+ Ast::TreeWalker.new(step_mother, formatters(step_mother), @options, io)
88
63
  end
89
64
 
90
65
  def formatter_class(format)
@@ -150,6 +125,31 @@ module Cucumber
150
125
  end
151
126
 
152
127
  private
128
+
129
+ def formatters(step_mother)
130
+ return [Formatter::Pretty.new(step_mother, nil, @options)] if @options[:autoformat]
131
+ @options[:formats].map do |format_and_out|
132
+ format = format_and_out[0]
133
+ out = format_and_out[1]
134
+ if String === out # file name
135
+ unless File.directory?(out)
136
+ out = File.open(out, Cucumber.file_mode('w'))
137
+ at_exit do
138
+ out.flush
139
+ out.close
140
+ end
141
+ end
142
+ end
143
+
144
+ begin
145
+ formatter_class = formatter_class(format)
146
+ formatter_class.new(step_mother, out, @options)
147
+ rescue Exception => e
148
+ e.message << "\nError creating formatter: #{format}"
149
+ raise e
150
+ end
151
+ end
152
+ end
153
153
 
154
154
  class LogFormatter < ::Logger::Formatter
155
155
  def call(severity, time, progname, msg)
@@ -19,7 +19,8 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
19
19
  [lang, Cucumber::LANGUAGES[lang]['name'], Cucumber::LANGUAGES[lang]['native']]
20
20
  end
21
21
  table = Ast::Table.new(raw)
22
- new(nil, io, {:check_lang=>true}).visit_multiline_arg(table)
22
+ formatter = new(nil, io, {:check_lang=>true})
23
+ Ast::TreeWalker.new(nil, [formatter]).visit_multiline_arg(table)
23
24
  end
24
25
 
25
26
  def self.list_keywords(io, lang)
@@ -31,19 +32,17 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
31
32
  new(nil, io, {:incomplete => language.incomplete?}).visit_multiline_arg(table)
32
33
  end
33
34
 
34
- def visit_multiline_arg(table)
35
+ def before_visit_multiline_arg(table)
35
36
  if @options[:incomplete]
36
37
  @io.puts(format_string(INCOMPLETE, :failed))
37
38
  end
38
- super
39
39
  end
40
40
 
41
- def visit_table_row(table_row)
41
+ def before_visit_table_row(table_row)
42
42
  @col = 1
43
- super
44
43
  end
45
44
 
46
- def visit_table_cell_value(value, status)
45
+ def before_visit_table_cell_value(value, status)
47
46
  if @col == 1
48
47
  if(@options[:check_lang])
49
48
  @incomplete = Parser::NaturalLanguage.get(nil, value).incomplete?
@@ -54,7 +53,6 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
54
53
  end
55
54
 
56
55
  @col += 1
57
- super(value, status)
58
56
  end
59
57
  end
60
58
  end
@@ -50,9 +50,9 @@ module Cucumber
50
50
 
51
51
  enable_diffing
52
52
 
53
- visitor = configuration.build_formatter_broadcaster(step_mother)
54
- step_mother.visitor = visitor # Needed to support World#announce
55
- visitor.visit_features(features)
53
+ runner = configuration.build_runner(step_mother, @out_stream)
54
+ step_mother.visitor = runner # Needed to support World#announce
55
+ runner.visit_features(features)
56
56
 
57
57
  failure = if exceeded_tag_limts?(features)
58
58
  FAILURE
@@ -7,28 +7,6 @@ class String #:nodoc:
7
7
  end
8
8
  end
9
9
 
10
- # TODO: Move to StepMatch
11
- # +groups+ is an array of 2-element arrays, where
12
- # the 1st element is the value of a regexp match group,
13
- # and the 2nd element is its start index.
14
- def gzub(groups, format=nil, &proc)
15
- s = dup
16
- offset = 0
17
- groups.each do |group|
18
- replacement = if block_given?
19
- proc.call(group.val)
20
- elsif Proc === format
21
- format.call(group.val)
22
- else
23
- format % group.val
24
- end
25
-
26
- s[group.start + offset, group.val.length] = replacement
27
- offset += replacement.length - group.val.length
28
- end
29
- s
30
- end
31
-
32
10
  if (Cucumber::JRUBY && Cucumber::RAILS) || Cucumber::RUBY_1_9
33
11
  # Workaround for http://tinyurl.com/55uu3u
34
12
  alias jlength length
@@ -1,214 +1,278 @@
1
1
  require 'cucumber/formatter/ordered_xml_markup'
2
2
  require 'cucumber/formatter/duration'
3
+ require 'xml'
4
+ require 'ruby-debug'
3
5
 
4
6
  module Cucumber
5
7
  module Formatter
6
8
  # The formatter used for <tt>--format html</tt>
7
- class Html < Ast::Visitor
9
+ class Html
8
10
  include ERB::Util # for the #h method
9
11
  include Duration
10
12
 
11
13
  def initialize(step_mother, io, options)
12
- super(step_mother)
14
+ @io = io
13
15
  @options = options
14
- @builder = create_builder(io)
16
+ @buffer = {}
17
+ @current_builder = create_builder(@io)
15
18
  end
16
19
 
17
- def create_builder(io)
18
- OrderedXmlMarkup.new(:target => io, :indent => 0)
20
+ def before_features(features)
21
+ start_buffering :features
19
22
  end
20
23
 
21
- def visit_features(features)
24
+ def after_features(features)
25
+ stop_buffering :features
22
26
  # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
23
- @builder.declare!(
27
+ builder.declare!(
24
28
  :DOCTYPE,
25
29
  :html,
26
30
  :PUBLIC,
27
31
  '-//W3C//DTD XHTML 1.0 Strict//EN',
28
32
  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
29
33
  )
30
- @builder.html(:xmlns => 'http://www.w3.org/1999/xhtml') do
31
- @builder.head do
32
- @builder.meta(:content => 'text/html;charset=utf-8')
33
- @builder.title 'Cucumber'
34
+ builder.html(:xmlns => 'http://www.w3.org/1999/xhtml') do
35
+ builder.head do
36
+ builder.meta(:content => 'text/html;charset=utf-8')
37
+ builder.title 'Cucumber'
34
38
  inline_css
35
39
  end
36
- @builder.body do
37
- @builder.div(:class => 'cucumber') do
38
- super
39
- @builder.div(format_duration(features.duration), :class => 'duration')
40
+ builder.body do
41
+ builder.div(:class => 'cucumber') do
42
+ builder << buffer(:features)
43
+ builder.div(format_duration(features.duration), :class => 'duration')
40
44
  end
41
45
  end
42
46
  end
43
47
  end
44
-
45
- def visit_comment(comment)
46
- @builder.pre(:class => 'comment') do
47
- super
48
+
49
+ def before_feature(feature)
50
+ start_buffering :feature
51
+ @exceptions = []
52
+ end
53
+
54
+ def after_feature(feature)
55
+ stop_buffering :feature
56
+ builder.div(:class => 'feature') do
57
+ builder << buffer(:feature)
48
58
  end
49
59
  end
50
60
 
51
- def visit_comment_line(comment_line)
52
- @builder.text!(comment_line)
53
- @builder.br
61
+ def before_comment(comment)
62
+ start_buffering :comment
54
63
  end
55
64
 
56
- def visit_feature(feature)
57
- @exceptions = []
58
- @builder.div(:class => 'feature') do
59
- super
65
+ def after_comment(comment)
66
+ stop_buffering :comment
67
+ builder.pre(:class => 'comment') do
68
+ builder << buffer(:comment)
60
69
  end
61
70
  end
62
71
 
63
- def visit_tags(tags)
64
- super
72
+ def comment_line(comment_line)
73
+ builder.text!(comment_line)
74
+ builder.br
75
+ end
76
+
77
+ def after_tags(tags)
65
78
  @tag_spacer = nil
66
79
  end
67
-
68
- def visit_tag_name(tag_name)
69
- @builder.text!(@tag_spacer) if @tag_spacer
80
+
81
+ def tag_name(tag_name)
82
+ builder.text!(@tag_spacer) if @tag_spacer
70
83
  @tag_spacer = ' '
71
- @builder.span(tag_name, :class => 'tag')
84
+ builder.span(tag_name, :class => 'tag')
72
85
  end
73
86
 
74
- def visit_feature_name(name)
87
+ def feature_name(name)
75
88
  lines = name.split(/\r?\n/)
76
89
  return if lines.empty?
77
- @builder.h2 do |h2|
78
- @builder.span(lines[0], :class => 'val')
90
+ builder.h2 do |h2|
91
+ builder.span(lines[0], :class => 'val')
79
92
  end
80
- @builder.p(:class => 'narrative') do
93
+ builder.p(:class => 'narrative') do
81
94
  lines[1..-1].each do |line|
82
- @builder.text!(line.strip)
83
- @builder.br
95
+ builder.text!(line.strip)
96
+ builder.br
84
97
  end
85
98
  end
86
99
  end
87
100
 
88
- def visit_background(background)
89
- @builder.div(:class => 'background') do
90
- @in_background = true
91
- super
92
- @in_background = nil
101
+ def before_background(background)
102
+ @in_background = true
103
+ start_buffering :background
104
+ end
105
+
106
+ def after_background(background)
107
+ stop_buffering :background
108
+ @in_background = nil
109
+ builder.div(:class => 'background') do
110
+ builder << buffer(:background)
93
111
  end
94
112
  end
95
113
 
96
- def visit_background_name(keyword, name, file_colon_line, source_indent)
114
+ def background_name(keyword, name, file_colon_line, source_indent)
97
115
  @listing_background = true
98
- @builder.h3 do |h3|
99
- @builder.span(keyword, :class => 'keyword')
100
- @builder.text!(' ')
101
- @builder.span(name, :class => 'val')
116
+ builder.h3 do |h3|
117
+ builder.span(keyword, :class => 'keyword')
118
+ builder.text!(' ')
119
+ builder.span(name, :class => 'val')
102
120
  end
103
121
  end
104
122
 
105
- def visit_feature_element(feature_element)
123
+ def before_feature_element(feature_element)
124
+ start_buffering :feature_element
125
+ end
126
+
127
+ def after_feature_element(feature_element)
128
+ stop_buffering :feature_element
106
129
  css_class = {
107
130
  Ast::Scenario => 'scenario',
108
131
  Ast::ScenarioOutline => 'scenario outline'
109
132
  }[feature_element.class]
110
- @builder.div(:class => css_class) do
111
- super
133
+
134
+ builder.div(:class => css_class) do
135
+ builder << buffer(:feature_element)
112
136
  end
113
137
  @open_step_list = true
114
138
  end
115
-
116
- def visit_scenario_name(keyword, name, file_colon_line, source_indent)
139
+
140
+ def scenario_name(keyword, name, file_colon_line, source_indent)
117
141
  @listing_background = false
118
- @builder.h3 do
119
- @builder.span(keyword, :class => 'keyword')
120
- @builder.text!(' ')
121
- @builder.span(name, :class => 'val')
142
+ builder.h3 do
143
+ builder.span(keyword, :class => 'keyword')
144
+ builder.text!(' ')
145
+ builder.span(name, :class => 'val')
122
146
  end
123
147
  end
124
-
125
- def visit_outline_table(outline_table)
148
+
149
+ def before_outline_table(outline_table)
126
150
  @outline_row = 0
127
- @builder.table do
128
- super(outline_table)
151
+ start_buffering :outline_table
152
+ end
153
+
154
+ def after_outline_table(outline_table)
155
+ stop_buffering :outline_table
156
+ builder.table do
157
+ builder << buffer(:outline_table)
129
158
  end
130
159
  @outline_row = nil
131
160
  end
132
161
 
133
- def visit_examples(examples)
134
- @builder.div(:class => 'examples') do
135
- super(examples)
162
+ def before_examples(examples)
163
+ start_buffering :examples
164
+ end
165
+
166
+ def after_examples(examples)
167
+ stop_buffering :examples
168
+ builder.div(:class => 'examples') do
169
+ builder << buffer(:examples)
136
170
  end
137
171
  end
138
172
 
139
- def visit_examples_name(keyword, name)
140
- @builder.h4 do
141
- @builder.span(keyword, :class => 'keyword')
142
- @builder.text!(' ')
143
- @builder.span(name, :class => 'val')
173
+ def examples_name(keyword, name)
174
+ builder.h4 do
175
+ builder.span(keyword, :class => 'keyword')
176
+ builder.text!(' ')
177
+ builder.span(name, :class => 'val')
144
178
  end
145
179
  end
146
180
 
147
- def visit_steps(steps)
148
- @builder.ol do
149
- super
181
+ def before_steps(steps)
182
+ start_buffering :steps
183
+ end
184
+
185
+ def after_steps(steps)
186
+ stop_buffering :steps
187
+ builder.ol do
188
+ builder << buffer(:steps)
150
189
  end
151
190
  end
152
-
153
- def visit_step(step)
191
+
192
+ def before_step(step)
154
193
  @step_id = step.dom_id
155
- super
156
194
  end
157
195
 
158
- def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
196
+ def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
197
+ start_buffering :step_result
198
+ @hide_this_step = false
159
199
  if exception
160
- return if @exceptions.index(exception)
200
+ if @exceptions.include?(exception)
201
+ @hide_this_step = true
202
+ return
203
+ end
161
204
  @exceptions << exception
162
205
  end
163
- return if status != :failed && @in_background ^ background
206
+ if status != :failed && @in_background ^ background
207
+ @hide_this_step = true
208
+ return
209
+ end
164
210
  @status = status
165
- @builder.li(:id => @step_id, :class => "step #{status}") do
166
- super(keyword, step_match, multiline_arg, status, exception, source_indent, background)
211
+ end
212
+
213
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
214
+ stop_buffering :step_result
215
+ return if @hide_this_step
216
+ builder.li(:id => @step_id, :class => "step #{status}") do
217
+ builder << buffer(:step_result)
167
218
  end
168
219
  end
169
220
 
170
- def visit_step_name(keyword, step_match, status, source_indent, background)
221
+ def step_name(keyword, step_match, status, source_indent, background)
171
222
  @step_matches ||= []
172
223
  background_in_scenario = background && !@listing_background
173
224
  @skip_step = @step_matches.index(step_match) || background_in_scenario
174
225
  @step_matches << step_match
175
-
226
+
176
227
  unless @skip_step
177
228
  build_step(keyword, step_match, status)
178
229
  end
179
230
  end
180
231
 
181
- def visit_exception(exception, status)
182
- @builder.pre(format_exception(exception), :class => status)
232
+ def exception(exception, status)
233
+ return if @hide_this_step
234
+ builder.pre(format_exception(exception), :class => status)
235
+ end
236
+
237
+ def before_multiline_arg(multiline_arg)
238
+ start_buffering :multiline_arg
183
239
  end
184
240
 
185
- def visit_multiline_arg(multiline_arg)
186
- return if @skip_step
241
+ def after_multiline_arg(multiline_arg)
242
+ stop_buffering :multiline_arg
243
+ return if @hide_this_step || @skip_step
187
244
  if Ast::Table === multiline_arg
188
- @builder.table do
189
- super
245
+ builder.table do
246
+ builder << buffer(:multiline_arg)
190
247
  end
191
248
  else
192
- super
249
+ builder << buffer(:multiline_arg)
193
250
  end
194
251
  end
195
252
 
196
- def visit_py_string(string)
197
- @builder.pre(:class => 'val') do |pre|
198
- @builder << string.gsub("\n", '&#x000A;')
253
+ def py_string(string)
254
+ return if @hide_this_step
255
+ builder.pre(:class => 'val') do |pre|
256
+ builder << string.gsub("\n", '&#x000A;')
199
257
  end
200
258
  end
201
259
 
202
- def visit_table_row(table_row)
260
+ def before_table_row(table_row)
203
261
  @row_id = table_row.dom_id
204
262
  @col_index = 0
205
- @builder.tr(:id => @row_id) do
206
- super
263
+ start_buffering :table_row
264
+ end
265
+
266
+ def after_table_row(table_row)
267
+ stop_buffering :table_row
268
+ return if @hide_this_step
269
+ builder.tr(:id => @row_id) do
270
+ builder << buffer(:table_row)
207
271
  end
208
272
  if table_row.exception
209
- @builder.tr do
210
- @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
211
- @builder.pre do |pre|
273
+ builder.tr do
274
+ builder.td(:colspan => @col_index.to_s, :class => 'failed') do
275
+ builder.pre do |pre|
212
276
  pre << format_exception(table_row.exception)
213
277
  end
214
278
  end
@@ -217,45 +281,71 @@ module Cucumber
217
281
  @outline_row += 1 if @outline_row
218
282
  end
219
283
 
220
- def visit_table_cell_value(value, status)
284
+ def table_cell_value(value, status)
285
+ return if @hide_this_step
286
+
221
287
  cell_type = @outline_row == 0 ? :th : :td
222
288
  attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'val'}
223
289
  attributes[:class] += " #{status}" if status
224
290
  build_cell(cell_type, value, attributes)
225
291
  @col_index += 1
226
292
  end
227
-
293
+
228
294
  def announce(announcement)
229
- @builder.pre(announcement, :class => 'announcement')
295
+ builder.pre(announcement, :class => 'announcement')
230
296
  end
231
-
232
- protected
297
+
298
+ private
233
299
 
234
300
  def build_step(keyword, step_match, status)
235
301
  step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
236
- @builder.div do |div|
237
- @builder.span(keyword, :class => 'keyword')
238
- @builder.text!(' ')
239
- @builder.span(:class => 'step val') do |name|
302
+ builder.div do |div|
303
+ builder.span(keyword, :class => 'keyword')
304
+ builder.text!(' ')
305
+ builder.span(:class => 'step val') do |name|
240
306
  name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
241
307
  end
242
308
  end
243
309
  end
244
-
310
+
245
311
  def build_cell(cell_type, value, attributes)
246
- @builder.__send__(cell_type, value, attributes)
312
+ builder.__send__(cell_type, value, attributes)
247
313
  end
248
-
314
+
249
315
  def inline_css
250
- @builder.style(:type => 'text/css') do
251
- @builder.text!(File.read(File.dirname(__FILE__) + '/cucumber.css'))
316
+ builder.style(:type => 'text/css') do
317
+ builder.text!(File.read(File.dirname(__FILE__) + '/cucumber.css'))
252
318
  end
253
319
  end
254
-
320
+
255
321
  def format_exception(exception)
256
322
  h((["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n"))
257
323
  end
258
324
 
325
+ def builder
326
+ @current_builder
327
+ end
328
+
329
+ def buffer(label)
330
+ result = @buffer[label]
331
+ @buffer[label] = ''
332
+ result
333
+ end
334
+
335
+ def start_buffering(label)
336
+ @buffer[label] ||= ''
337
+ @parent_builder ||= {}
338
+ @parent_builder[label] = @current_builder
339
+ @current_builder = create_builder(@buffer[label])
340
+ end
341
+
342
+ def stop_buffering(label)
343
+ @current_builder = @parent_builder[label]
344
+ end
345
+
346
+ def create_builder(io)
347
+ OrderedXmlMarkup.new(:target => io, :indent => 0)
348
+ end
259
349
  end
260
350
  end
261
351
  end