cucumber 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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