cucumber 0.4.2 → 0.4.3

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 (78) hide show
  1. data/History.txt +30 -4
  2. data/Rakefile +11 -8
  3. data/VERSION.yml +1 -1
  4. data/bin/cucumber +1 -2
  5. data/cucumber.gemspec +40 -38
  6. data/examples/i18n/da/features/{summering.feature → sammenlaegning.feature} +5 -5
  7. data/examples/i18n/da/features/step_definitons/{kalkulator_steps.rb → lommeregner_steps.rb} +3 -3
  8. data/examples/i18n/da/lib/{kalkulator.rb → lommeregner.rb} +2 -2
  9. data/examples/tickets/Rakefile +5 -1
  10. data/examples/tickets/features.html +138 -0
  11. data/examples/watir/Rakefile +4 -0
  12. data/examples/watir/features/{step_definitons → step_definitions}/search_steps.rb +0 -0
  13. data/examples/watir/features/support/screenshots.rb +45 -0
  14. data/features/announce.feature +122 -0
  15. data/features/html_formatter/a.html +1 -1
  16. data/features/step_definitions/cucumber_steps.rb +1 -1
  17. data/features/step_definitions/wire_steps.rb +14 -0
  18. data/features/support/env.rb +1 -1
  19. data/features/support/fake_wire_server.rb +63 -0
  20. data/features/tag_logic.feature +226 -0
  21. data/features/wire_protocol.feature +177 -0
  22. data/lib/cucumber/ast/examples.rb +4 -0
  23. data/lib/cucumber/ast/feature_element.rb +2 -1
  24. data/lib/cucumber/ast/scenario_outline.rb +4 -0
  25. data/lib/cucumber/ast/table.rb +13 -8
  26. data/lib/cucumber/ast/tags.rb +56 -12
  27. data/lib/cucumber/ast/tree_walker.rb +6 -0
  28. data/lib/cucumber/cli/main.rb +7 -5
  29. data/lib/cucumber/cli/options.rb +19 -10
  30. data/lib/cucumber/filter.rb +1 -2
  31. data/lib/cucumber/formatter/ansicolor.rb +17 -2
  32. data/lib/cucumber/formatter/console.rb +50 -32
  33. data/lib/cucumber/formatter/html.rb +21 -1
  34. data/lib/cucumber/formatter/junit.rb +8 -0
  35. data/lib/cucumber/formatter/pretty.rb +3 -0
  36. data/lib/cucumber/formatter/summary.rb +35 -0
  37. data/lib/cucumber/formatter/usage.rb +4 -4
  38. data/lib/cucumber/rails/active_record.rb +18 -10
  39. data/lib/cucumber/rb_support/rb_language.rb +7 -2
  40. data/lib/cucumber/rb_support/rb_world.rb +4 -0
  41. data/lib/cucumber/step_match.rb +3 -2
  42. data/lib/cucumber/step_mother.rb +6 -1
  43. data/lib/cucumber/wire_support/connection.rb +42 -0
  44. data/lib/cucumber/wire_support/request_handler.rb +19 -0
  45. data/lib/cucumber/wire_support/wire_exception.rb +10 -0
  46. data/lib/cucumber/wire_support/wire_language.rb +52 -0
  47. data/lib/cucumber/wire_support/wire_packet.rb +34 -0
  48. data/lib/cucumber/wire_support/wire_protocol.rb +64 -0
  49. data/lib/cucumber/wire_support/wire_step_definition.rb +21 -0
  50. data/rails_generators/cucumber/cucumber_generator.rb +7 -4
  51. data/rails_generators/cucumber/templates/cucumber_environment.rb +4 -4
  52. data/rails_generators/cucumber/templates/version_check.rb +6 -4
  53. data/spec/cucumber/ast/table_spec.rb +11 -1
  54. data/spec/cucumber/ast/tags_spec.rb +29 -0
  55. data/spec/cucumber/cli/options_spec.rb +8 -4
  56. data/spec/cucumber/formatter/junit_spec.rb +11 -0
  57. data/spec/cucumber/step_match_spec.rb +11 -0
  58. data/spec/cucumber/wire_support/wire_language_spec.rb +47 -0
  59. data/spec/cucumber/wire_support/wire_packet_spec.rb +26 -0
  60. metadata +38 -36
  61. data/examples/cs/.gitignore +0 -1
  62. data/examples/cs/README.textile +0 -1
  63. data/examples/cs/Rakefile +0 -12
  64. data/examples/cs/compile.bat +0 -1
  65. data/examples/cs/features/addition.feature +0 -16
  66. data/examples/cs/features/step_definitons/calculator_steps.rb +0 -19
  67. data/examples/cs/src/demo/Calculator.cs +0 -20
  68. data/examples/java/.gitignore +0 -1
  69. data/examples/java/README.textile +0 -18
  70. data/examples/java/build.xml +0 -33
  71. data/examples/java/features/hello.feature +0 -11
  72. data/examples/java/features/step_definitons/hello_steps.rb +0 -23
  73. data/examples/java/features/step_definitons/tree_steps.rb +0 -14
  74. data/examples/java/features/tree.feature +0 -9
  75. data/examples/java/src/.gitignore +0 -1
  76. data/examples/java/src/cucumber/demo/.gitignore +0 -1
  77. data/examples/java/src/cucumber/demo/Hello.java +0 -16
  78. data/examples/pure_java/README.textile +0 -5
@@ -3,8 +3,7 @@ module Cucumber
3
3
  class Filter #:nodoc:
4
4
  def initialize(lines, options)
5
5
  @lines = lines
6
-
7
- @tag_names = options[:tag_names] ? options[:tag_names].keys : []
6
+ @tag_names = options[:tag_names] ? options[:tag_names].map{|tags_with_limit| tags_with_limit.keys } : []
8
7
  @name_regexps = options[:name_regexps] || []
9
8
  end
10
9
 
@@ -1,8 +1,8 @@
1
1
  require 'term/ansicolor'
2
+ require 'cucumber/platform'
2
3
 
3
4
  if Cucumber::WINDOWS_MRI
4
5
  begin
5
- gem 'win32console', '>= 1.2.0'
6
6
  require 'Win32/Console/ANSI'
7
7
  rescue LoadError
8
8
  STDERR.puts %{*** WARNING: You must "gem install win32console" (1.2.0 or higher) to get coloured output on MRI/Windows}
@@ -128,7 +128,22 @@ module Cucumber
128
128
  end
129
129
 
130
130
  define_grey
131
-
131
+
132
+ def cukes(n)
133
+ ("(::) " * n).strip
134
+ end
135
+
136
+ def green_cukes(n)
137
+ blink(green(cukes(n)))
138
+ end
139
+
140
+ def red_cukes(n)
141
+ blink(red(cukes(n)))
142
+ end
143
+
144
+ def yellow_cukes(n)
145
+ blink(yellow(cukes(n)))
146
+ end
132
147
  end
133
148
  end
134
149
  end
@@ -1,5 +1,6 @@
1
1
  require 'cucumber/formatter/ansicolor'
2
2
  require 'cucumber/formatter/duration'
3
+ require 'cucumber/formatter/summary'
3
4
 
4
5
  module Cucumber
5
6
  module Formatter
@@ -8,6 +9,7 @@ module Cucumber
8
9
  module Console
9
10
  extend ANSIColor
10
11
  include Duration
12
+ include Summary
11
13
 
12
14
  FORMATS = Hash.new{|hash, format| hash[format] = method(format).to_proc}
13
15
 
@@ -73,11 +75,8 @@ module Cucumber
73
75
  @io.puts
74
76
  end
75
77
 
76
- @io.print dump_count(step_mother.scenarios.length, "scenario")
77
- print_status_counts{|status| step_mother.scenarios(status)}
78
-
79
- @io.print dump_count(step_mother.steps.length, "step")
80
- print_status_counts{|status| step_mother.steps(status)}
78
+ @io.puts scenario_summary(step_mother) {|status_count, status| format_string(status_count, status)}
79
+ @io.puts step_summary(step_mother) {|status_count, status| format_string(status_count, status)}
81
80
 
82
81
  @io.puts(format_duration(features.duration)) if features && features.duration
83
82
 
@@ -128,15 +127,17 @@ module Cucumber
128
127
  def print_tag_limit_warnings(options)
129
128
  if @tag_occurrences
130
129
  first_tag = true
131
- options[:tag_names].each do |tag_name, limit|
132
- unless Ast::Tags.exclude_tag?(tag_name)
133
- tag_frequency = @tag_occurrences[tag_name].size
134
- if limit && tag_frequency > limit
135
- @io.puts if first_tag
136
- first_tag = false
137
- @io.puts format_string("#{tag_name} occurred #{tag_frequency} times, but the limit was set to #{limit}", :failed)
138
- @tag_occurrences[tag_name].each {|location| @io.puts format_string(" #{location}", :failed)}
139
- @io.flush
130
+ options[:tag_names].each do |tag_list|
131
+ tag_list.each do |tag_name, limit|
132
+ unless Ast::Tags.exclude_tag?(tag_name)
133
+ tag_frequency = @tag_occurrences[tag_name].size
134
+ if limit && tag_frequency > limit
135
+ @io.puts if first_tag
136
+ first_tag = false
137
+ @io.puts format_string("#{tag_name} occurred #{tag_frequency} times, but the limit was set to #{limit}", :failed)
138
+ @tag_occurrences[tag_name].each {|location| @io.puts format_string(" #{location}", :failed)}
139
+ @io.flush
140
+ end
140
141
  end
141
142
  end
142
143
  end
@@ -145,37 +146,54 @@ module Cucumber
145
146
 
146
147
  def record_tag_occurrences(feature_element, options)
147
148
  @tag_occurrences ||= Hash.new{|k,v| k[v] = []}
148
- options[:tag_names].each do |tag_name, limit|
149
- if !Ast::Tags.exclude_tag?(tag_name) && feature_element.tag_count(tag_name) > 0
150
- @tag_occurrences[tag_name] << feature_element.file_colon_line
149
+ options[:tag_names].each do |tag_list|
150
+ tag_list.each do |tag_name, limit|
151
+ if !Ast::Tags.exclude_tag?(tag_name) && feature_element.tag_count(tag_name) > 0
152
+ @tag_occurrences[tag_name] << feature_element.file_colon_line
153
+ end
151
154
  end
152
155
  end
153
156
  end
154
157
 
155
- def announce(announcement)
156
- @io.puts
157
- @io.puts(format_string(announcement, :tag))
158
- @io.flush
158
+ def embed(file, mime_type)
159
+ # no-op
159
160
  end
160
161
 
161
- private
162
-
163
- def print_status_counts
164
- counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
165
- elements = yield status
166
- elements.any? ? format_string("#{elements.length} #{status.to_s}", status) : nil
167
- end.compact
168
- if counts.any?
169
- @io.puts(" (#{counts.join(', ')})")
162
+ #define @delayed_announcements = [] in your Formatter if you want to
163
+ #activate this feature
164
+ def announce(announcement)
165
+ if @delayed_announcements
166
+ @delayed_announcements << announcement
170
167
  else
171
168
  @io.puts
169
+ @io.puts(format_string(announcement, :tag))
170
+ @io.flush
172
171
  end
173
172
  end
174
173
 
175
- def dump_count(count, what, state=nil)
176
- [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(" ")
174
+ def print_announcements()
175
+ @delayed_announcements.each {|ann| print_announcement(ann)}
176
+ empty_announcements
177
177
  end
178
178
 
179
+ def print_table_row_announcements
180
+ return if @delayed_announcements.empty?
181
+ @io.print(format_string(@delayed_announcements.join(', '), :tag).indent(2))
182
+ @io.flush
183
+ empty_announcements
184
+ end
185
+
186
+ def print_announcement(announcement)
187
+ @io.puts(format_string(announcement, :tag).indent(@indent))
188
+ @io.flush
189
+ end
190
+
191
+ def empty_announcements
192
+ @delayed_announcements = []
193
+ end
194
+
195
+ private
196
+
179
197
  def format_for(*keys)
180
198
  key = keys.join('_').to_sym
181
199
  fmt = FORMATS[key]
@@ -1,5 +1,6 @@
1
1
  require 'cucumber/formatter/ordered_xml_markup'
2
2
  require 'cucumber/formatter/duration'
3
+ require 'cucumber/formatter/summary'
3
4
 
4
5
  module Cucumber
5
6
  module Formatter
@@ -7,11 +8,13 @@ module Cucumber
7
8
  class Html
8
9
  include ERB::Util # for the #h method
9
10
  include Duration
11
+ include Summary
10
12
 
11
13
  def initialize(step_mother, io, options)
12
14
  @io = io
13
15
  @options = options
14
16
  @buffer = {}
17
+ @step_mother = step_mother
15
18
  @current_builder = create_builder(@io)
16
19
  end
17
20
 
@@ -38,6 +41,8 @@ module Cucumber
38
41
  builder.body do
39
42
  builder.div(:class => 'cucumber') do
40
43
  builder << buffer(:features)
44
+ builder.div(scenario_summary(@step_mother) {|status_count, _| status_count}, :class => 'summary')
45
+ builder.div(step_summary(@step_mother) {|status_count, _| status_count}, :class => 'summary')
41
46
  builder.div(format_duration(features.duration), :class => 'duration')
42
47
  end
43
48
  end
@@ -292,9 +297,24 @@ module Cucumber
292
297
  def announce(announcement)
293
298
  builder.pre(announcement, :class => 'announcement')
294
299
  end
295
-
300
+
301
+ def embed(file, mime_type)
302
+ case(mime_type)
303
+ when /^image\/(png|gif|jpg)/
304
+ embed_image(file)
305
+ end
306
+ end
307
+
296
308
  private
297
309
 
310
+ def embed_image(file)
311
+ id = file.hash
312
+ builder.pre(:class => 'embed') do |pre|
313
+ pre << %{<a href="#" onclick="img=document.getElementById('#{id}'); img.style.display = (img.style.display == 'none' ? 'block' : 'none');">Screenshot</a>
314
+ <img id="#{id}" style="display: none" src="#{file}" />}
315
+ end
316
+ end
317
+
298
318
  def build_step(keyword, step_match, status)
299
319
  step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
300
320
  builder.div do |div|
@@ -4,6 +4,12 @@ module Cucumber
4
4
  module Formatter
5
5
  # The formatter used for <tt>--format junit</tt>
6
6
  class Junit
7
+ class UnNamedFeatureError < StandardError
8
+ def initialize(feature_file)
9
+ super("The feature in '#{feature_file}' does not have a name. The JUnit XML format requires a name for the testsuite element.")
10
+ end
11
+ end
12
+
7
13
  def initialize(step_mother, io, options)
8
14
  raise "You *must* specify --out DIR for the junit formatter" unless String === io && File.directory?(io)
9
15
  @reportdir = io
@@ -11,6 +17,7 @@ module Cucumber
11
17
  end
12
18
 
13
19
  def before_feature(feature)
20
+ @current_feature = feature
14
21
  @failures = @errors = @tests = 0
15
22
  @builder = OrderedXmlMarkup.new( :indent => 2 )
16
23
  @time = 0
@@ -40,6 +47,7 @@ module Cucumber
40
47
  end
41
48
 
42
49
  def feature_name(name)
50
+ raise UnNamedFeatureError.new(@current_feature.file) if name.empty?
43
51
  lines = name.split(/\r?\n/)
44
52
  @feature_name = lines[0].sub(/Feature\:/, '').strip
45
53
  end
@@ -21,6 +21,7 @@ module Cucumber
21
21
  @exceptions = []
22
22
  @indent = 0
23
23
  @prefixes = options[:prefixes] || {}
24
+ @delayed_announcements = []
24
25
  end
25
26
 
26
27
  def after_features(features)
@@ -150,6 +151,7 @@ module Cucumber
150
151
  source_indent = nil unless @options[:source]
151
152
  name_to_report = format_step(keyword, step_match, status, source_indent)
152
153
  @io.puts(name_to_report.indent(@scenario_indent + 2))
154
+ print_announcements
153
155
  end
154
156
 
155
157
  def py_string(string)
@@ -183,6 +185,7 @@ module Cucumber
183
185
 
184
186
  def after_table_row(table_row)
185
187
  return unless @table
188
+ print_table_row_announcements
186
189
  @io.puts
187
190
  if table_row.exception && !@exceptions.include?(table_row.exception)
188
191
  print_exception(table_row.exception, :failed, @indent)
@@ -0,0 +1,35 @@
1
+ module Cucumber
2
+ module Formatter
3
+ module Summary
4
+
5
+ def scenario_summary(step_mother, &block)
6
+ scenarios_proc = lambda{|status| step_mother.scenarios(status)}
7
+ dump_count(step_mother.scenarios.length, "scenario") + dump_status_counts(scenarios_proc, &block)
8
+ end
9
+
10
+ def step_summary(step_mother, &block)
11
+ steps_proc = lambda{|status| step_mother.steps(status)}
12
+ dump_count(step_mother.steps.length, "step") + dump_status_counts(steps_proc, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def dump_status_counts(find_elements_proc)
18
+ counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
19
+ elements = find_elements_proc.call(status)
20
+ elements.any? ? yield("#{elements.length} #{status.to_s}", status) : nil
21
+ end.compact
22
+ if counts.any?
23
+ " (#{counts.join(', ')})"
24
+ else
25
+ ""
26
+ end
27
+ end
28
+
29
+ def dump_count(count, what, state=nil)
30
+ [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(" ")
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -19,14 +19,14 @@ module Cucumber
19
19
 
20
20
  def before_step(step)
21
21
  @step = step
22
+ @start_time = Time.now
22
23
  end
23
24
 
24
- def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
25
- @step_duration = Time.now
25
+ def before_step_result(*args)
26
+ @duration = Time.now - @start_time
26
27
  end
27
28
 
28
29
  def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
29
- duration = Time.now - @step_duration
30
30
  if step_match.name.nil? # nil if it's from a scenario outline
31
31
  stepdef_key = StepDefKey.new(step_match.step_definition.regexp_source, step_match.step_definition.file_colon_line)
32
32
 
@@ -35,7 +35,7 @@ module Cucumber
35
35
  :step_match => step_match,
36
36
  :status => status,
37
37
  :file_colon_line => @step.file_colon_line,
38
- :duration => duration
38
+ :duration => @duration
39
39
  }
40
40
  end
41
41
  super
@@ -13,24 +13,32 @@ if defined?(ActiveRecord::Base)
13
13
 
14
14
  Before do
15
15
  if Cucumber::Rails::World.use_transactional_fixtures
16
- @__cucumber_ar_connection = ActiveRecord::Base.connection
17
- if @__cucumber_ar_connection.respond_to?(:increment_open_transactions)
18
- @__cucumber_ar_connection.increment_open_transactions
16
+ @__cucumber_ar_connections = if ActiveRecord::Base.respond_to?(:connection_handler)
17
+ ActiveRecord::Base.connection_handler.connection_pools.values.map {|pool| pool.connection}
19
18
  else
20
- ActiveRecord::Base.__send__(:increment_open_transactions)
19
+ [ActiveRecord::Base.connection] # Rails <= 2.1.2
20
+ end
21
+ @__cucumber_ar_connections.each do |__cucumber_ar_connection|
22
+ if __cucumber_ar_connection.respond_to?(:increment_open_transactions)
23
+ __cucumber_ar_connection.increment_open_transactions
24
+ else
25
+ ActiveRecord::Base.__send__(:increment_open_transactions)
26
+ end
27
+ __cucumber_ar_connection.begin_db_transaction
21
28
  end
22
- @__cucumber_ar_connection.begin_db_transaction
23
29
  end
24
30
  ActionMailer::Base.deliveries = [] if defined?(ActionMailer::Base)
25
31
  end
26
32
 
27
33
  After do
28
34
  if Cucumber::Rails::World.use_transactional_fixtures
29
- @__cucumber_ar_connection.rollback_db_transaction
30
- if @__cucumber_ar_connection.respond_to?(:decrement_open_transactions)
31
- @__cucumber_ar_connection.decrement_open_transactions
32
- else
33
- ActiveRecord::Base.__send__(:decrement_open_transactions)
35
+ @__cucumber_ar_connections.each do |__cucumber_ar_connection|
36
+ __cucumber_ar_connection.rollback_db_transaction
37
+ if __cucumber_ar_connection.respond_to?(:decrement_open_transactions)
38
+ __cucumber_ar_connection.decrement_open_transactions
39
+ else
40
+ ActiveRecord::Base.__send__(:decrement_open_transactions)
41
+ end
34
42
  end
35
43
  end
36
44
  end
@@ -37,6 +37,9 @@ module Cucumber
37
37
  RbDsl.rb_language = self
38
38
  end
39
39
 
40
+ # Tell the language about other i18n translations so that
41
+ # they can alias Given, When Then etc. Only useful if the language
42
+ # has a mechanism for this - typically a dynamic language.
40
43
  def alias_adverbs(adverbs)
41
44
  adverbs.each do |adverb|
42
45
  RbDsl.alias_adverb(adverb)
@@ -44,9 +47,11 @@ module Cucumber
44
47
  end
45
48
  end
46
49
 
47
- def step_definitions_for(code_file)
50
+ # Gets called for each file under features (or whatever is overridden
51
+ # with --require).
52
+ def step_definitions_for(rb_file)
48
53
  begin
49
- load_code_file(code_file)
54
+ require rb_file # This will cause self.add_step_definition and self.add_hook to be called from RbDsl
50
55
  step_definitions
51
56
  rescue LoadError => e
52
57
  e.message << "\nFailed to load #{code_file}"
@@ -64,6 +64,10 @@ module Cucumber
64
64
  @__cucumber_step_mother.announce(announcement)
65
65
  end
66
66
 
67
+ def embed(file, mime_type)
68
+ @__cucumber_step_mother.embed(file, mime_type)
69
+ end
70
+
67
71
  # Mark the matched step as pending.
68
72
  def pending(message = "TODO")
69
73
  if block_given?
@@ -6,6 +6,7 @@ module Cucumber
6
6
  # in which case +name_to_report+ is used instead.
7
7
  #
8
8
  def initialize(step_definition, name_to_match, name_to_report, step_arguments)
9
+ raise "name_to_match can't be nil" if name_to_match.nil?
9
10
  @step_definition, @name_to_match, @name_to_report, @step_arguments = step_definition, name_to_match, name_to_report, step_arguments
10
11
  end
11
12
 
@@ -67,8 +68,8 @@ module Cucumber
67
68
  format % step_argument.val
68
69
  end
69
70
 
70
- s[step_argument.pos + offset, step_argument.val.jlength] = replacement
71
- offset += replacement.length - step_argument.val.jlength
71
+ s[step_argument.pos + offset, step_argument.val.length] = replacement
72
+ offset += replacement.jlength - step_argument.val.jlength
72
73
  end
73
74
  s
74
75
  end
@@ -130,6 +130,10 @@ module Cucumber
130
130
  @visitor.announce(msg)
131
131
  end
132
132
 
133
+ def embed(file, mime_type)
134
+ @visitor.embed(file, mime_type)
135
+ end
136
+
133
137
  def scenarios(status = nil) #:nodoc:
134
138
  @scenarios ||= []
135
139
  if(status)
@@ -238,7 +242,8 @@ module Cucumber
238
242
  return nil if @unsupported_programming_languages.index(ext)
239
243
  begin
240
244
  load_programming_language(ext)
241
- rescue LoadError
245
+ rescue LoadError => e
246
+ log.debug("Failed to load '#{ext}' programming language for file #{step_def_file}: #{e.message}\n")
242
247
  @unsupported_programming_languages << ext
243
248
  nil
244
249
  end