cucumber 0.3.102 → 0.3.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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