aslakhellesoy-cucumber 0.3.5 → 0.3.6.1

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 (37) hide show
  1. data/History.txt +24 -0
  2. data/Manifest.txt +5 -2
  3. data/examples/i18n/fr/features/addition.feature +4 -4
  4. data/features/after_block_exceptions.feature +97 -0
  5. data/features/after_step_block_exceptions.feature +99 -0
  6. data/features/background.feature +47 -2
  7. data/features/custom_formatter.feature +1 -1
  8. data/features/step_definitions/cucumber_steps.rb +8 -0
  9. data/features/work_in_progress.feature +146 -0
  10. data/lib/cucumber.rb +5 -0
  11. data/lib/cucumber/ast/background.rb +3 -3
  12. data/lib/cucumber/ast/outline_table.rb +51 -4
  13. data/lib/cucumber/ast/scenario.rb +9 -3
  14. data/lib/cucumber/ast/scenario_outline.rb +4 -0
  15. data/lib/cucumber/ast/step.rb +3 -3
  16. data/lib/cucumber/ast/step_invocation.rb +13 -4
  17. data/lib/cucumber/cli/configuration.rb +54 -23
  18. data/lib/cucumber/cli/main.rb +6 -2
  19. data/lib/cucumber/formatter/console.rb +9 -0
  20. data/lib/cucumber/formatter/junit.rb +1 -1
  21. data/lib/cucumber/formatter/pretty.rb +7 -2
  22. data/lib/cucumber/formatter/progress.rb +1 -0
  23. data/lib/cucumber/formatter/tag_cloud.rb +27 -0
  24. data/lib/cucumber/languages.yml +60 -60
  25. data/lib/cucumber/rails/world.rb +16 -3
  26. data/lib/cucumber/rake/task.rb +2 -2
  27. data/lib/cucumber/step_match.rb +3 -2
  28. data/lib/cucumber/step_mother.rb +29 -4
  29. data/lib/cucumber/version.rb +2 -2
  30. data/rails_generators/cucumber/templates/cucumber.rake +1 -1
  31. data/rails_generators/feature/feature_generator.rb +1 -1
  32. data/spec/cucumber/cli/configuration_spec.rb +0 -3
  33. data/spec/cucumber/formatter/color_io_spec.rb +1 -0
  34. data/spec/cucumber/formatter/progress_spec.rb +1 -0
  35. metadata +6 -4
  36. data/examples/self_test/features/support/tag_count_formatter.rb +0 -25
  37. data/lib/cucumber/formatter.rb +0 -1
data/lib/cucumber.rb CHANGED
@@ -14,6 +14,7 @@ module Cucumber
14
14
  KEYWORD_KEYS = %w{name native encoding feature background scenario scenario_outline examples given when then but}
15
15
 
16
16
  class << self
17
+ # The currently active language
17
18
  attr_reader :lang
18
19
 
19
20
  def load_language(lang) #:nodoc:
@@ -38,6 +39,10 @@ module Cucumber
38
39
  def keyword_hash(lang=@lang)
39
40
  LANGUAGES[lang]
40
41
  end
42
+
43
+ def scenario_keyword
44
+ keyword_hash['scenario'].split('|')[0] + ':'
45
+ end
41
46
 
42
47
  def alias_step_definitions(lang) #:nodoc:
43
48
  keywords = %w{given when then and but}.map{|keyword| keyword_hash(lang)[keyword].split('|')}
@@ -28,15 +28,15 @@ module Cucumber
28
28
  visitor.step_mother.before(hook_context)
29
29
  visitor.visit_steps(@step_invocations)
30
30
  @failed = @step_invocations.detect{|step_invocation| step_invocation.exception}
31
- visitor.step_mother.after(hook_context) if @failed
31
+ visitor.step_mother.after(hook_context) if @failed || @feature_elements.empty?
32
32
  end
33
33
 
34
34
  def accept_hook?(hook)
35
35
  if hook_context != self
36
36
  hook_context.accept_hook?(hook)
37
37
  else
38
- # We have no scenarios
39
- false
38
+ # We have no scenarios, just ask our feature
39
+ @feature.accept_hook?(hook)
40
40
  end
41
41
  end
42
42
 
@@ -10,7 +10,11 @@ module Cucumber
10
10
 
11
11
  def accept(visitor)
12
12
  cells_rows.each_with_index do |row, n|
13
- visitor.visit_table_row(row)
13
+ if(visitor.options[:expand])
14
+ row.accept(visitor)
15
+ else
16
+ visitor.visit_table_row(row)
17
+ end
14
18
  end
15
19
  nil
16
20
  end
@@ -35,8 +39,14 @@ module Cucumber
35
39
  cells_rows[1..-1]
36
40
  end
37
41
 
42
+ def visit_scenario_name(visitor, row)
43
+ @scenario_outline.visit_scenario_name(visitor, row)
44
+ end
45
+
38
46
  class ExampleCells < Cells
47
+
39
48
  def create_step_invocations!(scenario_outline)
49
+ @scenario_outline = scenario_outline
40
50
  @step_invocations = scenario_outline.step_invocations(self)
41
51
  end
42
52
 
@@ -47,6 +57,10 @@ module Cucumber
47
57
  end
48
58
 
49
59
  def accept(visitor)
60
+ visitor.options[:expand] ? accept_expand(visitor) : accept_plain(visitor)
61
+ end
62
+
63
+ def accept_plain(visitor)
50
64
  if header?
51
65
  @cells.each do |cell|
52
66
  cell.status = :skipped_param
@@ -62,6 +76,22 @@ module Cucumber
62
76
  @cells.each do |cell|
63
77
  visitor.visit_table_cell(cell)
64
78
  end
79
+
80
+ visitor.visit_exception(@scenario_exception, :failed) if @scenario_exception
81
+ end
82
+ end
83
+ end
84
+
85
+ def accept_expand(visitor)
86
+ if header?
87
+ else
88
+ visitor.step_mother.before_and_after(self) do
89
+ @table.visit_scenario_name(visitor, self)
90
+ @step_invocations.each do |step_invocation|
91
+ step_invocation.invoke(visitor.step_mother, visitor.options)
92
+ @exception ||= step_invocation.exception
93
+ step_invocation.visit_step_result(visitor)
94
+ end
65
95
  end
66
96
  end
67
97
  end
@@ -69,22 +99,39 @@ module Cucumber
69
99
  def accept_hook?(hook)
70
100
  @table.accept_hook?(hook)
71
101
  end
72
-
102
+
103
+ def exception
104
+ @exception || @scenario_exception
105
+ end
106
+
107
+ def fail!(exception)
108
+ @scenario_exception = exception
109
+ end
110
+
73
111
  # Returns true if one or more steps failed
74
112
  def failed?
75
- @step_invocations.failed?
113
+ @step_invocations.failed? || !!@scenario_exception
76
114
  end
77
115
 
78
116
  # Returns true if all steps passed
79
117
  def passed?
80
- @step_invocations.passed?
118
+ !failed?
81
119
  end
82
120
 
83
121
  # Returns the status
84
122
  def status
123
+ return :failed if @scenario_exception
85
124
  @step_invocations.status
86
125
  end
87
126
 
127
+ def backtrace_line
128
+ @scenario_outline.backtrace_line(name, line)
129
+ end
130
+
131
+ def name
132
+ "| #{@cells.collect{|c| c.value }.join(' | ')} |"
133
+ end
134
+
88
135
  private
89
136
 
90
137
  def header?
@@ -28,25 +28,31 @@ module Cucumber
28
28
  visitor.step_mother.before_and_after(self, skip) do
29
29
  visitor.visit_steps(@steps)
30
30
  end
31
+ visitor.visit_exception(@exception, :failed) if @exception
31
32
  end
32
33
 
33
34
  # Returns true if one or more steps failed
34
35
  def failed?
35
- @steps.failed?
36
+ @steps.failed? || !!@exception
37
+ end
38
+
39
+ def fail!(exception)
40
+ @exception = exception
36
41
  end
37
42
 
38
43
  # Returns true if all steps passed
39
44
  def passed?
40
- @steps.passed?
45
+ !failed?
41
46
  end
42
47
 
43
48
  # Returns the first exception (if any)
44
49
  def exception
45
- @steps.exception
50
+ @exception || @steps.exception
46
51
  end
47
52
 
48
53
  # Returns the status
49
54
  def status
55
+ return :failed if @exception
50
56
  @steps.status
51
57
  end
52
58
 
@@ -61,6 +61,10 @@ module Cucumber
61
61
  end
62
62
  end
63
63
 
64
+ def visit_scenario_name(visitor, row)
65
+ visitor.visit_scenario_name(Cucumber.scenario_keyword, row.name, file_colon_line(row.line), source_indent(first_line_length))
66
+ end
67
+
64
68
  def to_sexp
65
69
  sexp = [:scenario_outline, @keyword, @name]
66
70
  comment = @comment.to_sexp
@@ -51,7 +51,7 @@ module Cucumber
51
51
  step_match = visitor.step_mother.step_match(name, @name) rescue nil
52
52
  return step_match if step_match
53
53
  end
54
- NoStepMatch.new(self)
54
+ NoStepMatch.new(self, @name)
55
55
  end
56
56
 
57
57
  def to_sexp
@@ -62,8 +62,8 @@ module Cucumber
62
62
  @feature_element.source_indent(text_length)
63
63
  end
64
64
 
65
- def text_length
66
- @keyword.jlength + @name.jlength + INDENT # Add indent as steps get indented more than scenarios
65
+ def text_length(name=@name)
66
+ @keyword.jlength + name.jlength + INDENT # Add indent as steps get indented more than scenarios
67
67
  end
68
68
 
69
69
  def backtrace_line
@@ -20,7 +20,11 @@ module Cucumber
20
20
 
21
21
  def accept(visitor)
22
22
  invoke(visitor.step_mother, visitor.options)
23
- @step.visit_step_result(visitor, @step_match, @multiline_arg, @status, @exception, @background)
23
+ visit_step_result(visitor)
24
+ end
25
+
26
+ def visit_step_result(visitor)
27
+ visitor.visit_step_result(keyword, @step_match, @multiline_arg, @status, @exception, source_indent, @background)
24
28
  end
25
29
 
26
30
  def invoke(step_mother, options)
@@ -30,6 +34,7 @@ module Cucumber
30
34
  begin
31
35
  step_mother.current_world.__cucumber_current_step = self if step_mother.current_world # Nil in Pure Java
32
36
  @step_match.invoke(step_mother.current_world, @multiline_arg)
37
+ step_mother.after_step
33
38
  status!(:passed)
34
39
  rescue Pending => e
35
40
  failed(e, false)
@@ -51,11 +56,11 @@ module Cucumber
51
56
  rescue Undefined => e
52
57
  failed(e, true)
53
58
  status!(:undefined)
54
- @step_match = NoStepMatch.new(@step)
59
+ @step_match = NoStepMatch.new(@step, @name)
55
60
  rescue Ambiguous => e
56
61
  failed(e, false)
57
62
  status!(:failed)
58
- @step_match = NoStepMatch.new(@step)
63
+ @step_match = NoStepMatch.new(@step, @name)
59
64
  end
60
65
  step_mother.step_visited(self)
61
66
  end
@@ -85,8 +90,12 @@ module Cucumber
85
90
  end
86
91
  end
87
92
 
93
+ def source_indent
94
+ @step.feature_element.source_indent(text_length)
95
+ end
96
+
88
97
  def text_length
89
- @step.text_length
98
+ @step.text_length(@name)
90
99
  end
91
100
 
92
101
  def keyword
@@ -3,7 +3,15 @@ module Cucumber
3
3
  class YmlLoadError < StandardError; end
4
4
 
5
5
  class Configuration
6
- FORMATS = %w{pretty profile progress rerun junit}
6
+ BUILTIN_FORMATS = {
7
+ 'html' => 'Cucumber::Formatter::Html',
8
+ 'pretty' => 'Cucumber::Formatter::Pretty',
9
+ 'profile' => 'Cucumber::Formatter::Profile',
10
+ 'progress' => 'Cucumber::Formatter::Progress',
11
+ 'rerun' => 'Cucumber::Formatter::Rerun',
12
+ 'usage' => 'Cucumber::Formatter::Usage',
13
+ 'junit' => 'Cucumber::Formatter::Junit'
14
+ }
7
15
  DEFAULT_FORMAT = 'pretty'
8
16
 
9
17
  attr_reader :paths
@@ -57,12 +65,15 @@ module Cucumber
57
65
  end
58
66
  opts.on("-f FORMAT", "--format FORMAT",
59
67
  "How to format features (Default: #{DEFAULT_FORMAT})",
60
- "Available formats: #{FORMATS.join(", ")}",
61
- "You can also provide your own formatter classes as long",
62
- "as they have been previously required using --require or",
63
- "if they are in the folder structure such that cucumber",
64
- "will require them automatically.",
65
- "This option can be specified multiple times.") do |v|
68
+ "Available formats: #{BUILTIN_FORMATS.keys.sort.join(", ")}",
69
+ "FORMAT can also be the fully qualified class name of",
70
+ "your own custom formatter. If the class isn't loaded,",
71
+ "Cucumber will attempt to require a file with a relative",
72
+ "file name that is the underscore name of the class name.",
73
+ "Example: --format Foo::BarZap -> Cucumber will look for",
74
+ "foo/bar_zap.rb. You can place the file with this relative",
75
+ "path underneath your features/support directory or anywhere",
76
+ "on Ruby's LOAD_PATH, for example in a Ruby gem.") do |v|
66
77
  @options[:formats][v] = @out_stream
67
78
  @active_format = v
68
79
  end
@@ -132,12 +143,18 @@ module Cucumber
132
143
  opts.on("-S", "--strict", "Fail if there are any undefined steps.") do
133
144
  @options[:strict] = true
134
145
  end
146
+ opts.on("-w", "--wip", "Fail if there are any passing scenarios.") do
147
+ @options[:wip] = true
148
+ end
135
149
  opts.on("-v", "--verbose", "Show the files and features loaded.") do
136
150
  @options[:verbose] = true
137
151
  end
138
152
  opts.on("-g", "--guess", "Guess best match for Ambiguous steps.") do
139
153
  @options[:guess] = true
140
154
  end
155
+ opts.on("-x", "--expand", "Expand Scenario Outline Tables in output.") do
156
+ @options[:expand] = true
157
+ end
141
158
  opts.on("--no-diff", "Disable diff output on failing expectations.") do
142
159
  @options[:diff_enabled] = false
143
160
  end
@@ -156,6 +173,8 @@ module Cucumber
156
173
  @options[:snippets] = true if !@quiet && @options[:snippets].nil?
157
174
  @options[:source] = true if !@quiet && @options[:source].nil?
158
175
 
176
+ raise("You can't use both --strict and --wip") if @options[:strict] && @options[:wip]
177
+
159
178
  # Whatever is left after option parsing is the FILE arguments
160
179
  @paths += args
161
180
  end
@@ -168,6 +187,10 @@ module Cucumber
168
187
  @options[:strict]
169
188
  end
170
189
 
190
+ def wip?
191
+ @options[:wip]
192
+ end
193
+
171
194
  def guess?
172
195
  @options[:guess]
173
196
  end
@@ -223,14 +246,8 @@ module Cucumber
223
246
  end
224
247
 
225
248
  def formatter_class(format)
226
- case format
227
- when 'html' then Formatter::Html
228
- when 'pretty' then Formatter::Pretty
229
- when 'profile' then Formatter::Profile
230
- when 'progress' then Formatter::Progress
231
- when 'rerun' then Formatter::Rerun
232
- when 'usage' then Formatter::Usage
233
- when 'junit' then Formatter::JUnit
249
+ if(builtin = BUILTIN_FORMATS[format])
250
+ constantize(builtin)
234
251
  else
235
252
  constantize(format)
236
253
  end
@@ -271,14 +288,28 @@ module Cucumber
271
288
  end
272
289
 
273
290
  def constantize(camel_cased_word)
274
- names = camel_cased_word.split('::')
275
- names.shift if names.empty? || names.first.empty?
291
+ begin
292
+ names = camel_cased_word.split('::')
293
+ names.shift if names.empty? || names.first.empty?
276
294
 
277
- constant = Object
278
- names.each do |name|
279
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
295
+ constant = Object
296
+ names.each do |name|
297
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
298
+ end
299
+ constant
300
+ rescue NameError
301
+ require underscore(camel_cased_word)
302
+ retry
280
303
  end
281
- constant
304
+ end
305
+
306
+ # Snagged from active_support
307
+ def underscore(camel_cased_word)
308
+ camel_cased_word.to_s.gsub(/::/, '/').
309
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
310
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
311
+ tr("-", "_").
312
+ downcase
282
313
  end
283
314
 
284
315
  def parse_args_from_profile(profile)
@@ -295,10 +326,10 @@ Defined profiles in cucumber.yml:
295
326
 
296
327
  case(args_from_yml)
297
328
  when String
298
- raise "The '#{profile}' profile in cucumber.yml was blank. Please define the command line arguments for the 'foo' profile in cucumber.yml.\n" if args_from_yml =~ /^\s*$/
329
+ raise "The '#{profile}' profile in cucumber.yml was blank. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml =~ /^\s*$/
299
330
  args_from_yml = args_from_yml.split(' ')
300
331
  when Array
301
- raise "The '#{profile}' profile in cucumber.yml was empty. Please define the command line arguments for the 'foo' profile in cucumber.yml.\n" if args_from_yml.empty?
332
+ raise "The '#{profile}' profile in cucumber.yml was empty. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml.empty?
302
333
  else
303
334
  raise "The '#{profile}' profile in cucumber.yml was a #{args_from_yml.class}. It must be a String or Array"
304
335
  end
@@ -2,7 +2,7 @@ require 'optparse'
2
2
  require 'cucumber'
3
3
  require 'ostruct'
4
4
  require 'cucumber/parser'
5
- require 'cucumber/formatter'
5
+ require 'cucumber/formatter/color_io'
6
6
  require 'cucumber/cli/language_help_formatter'
7
7
  require 'cucumber/cli/configuration'
8
8
 
@@ -40,8 +40,12 @@ module Cucumber
40
40
  step_mother.visitor = visitor # Needed to support World#announce
41
41
  visitor.visit_features(features)
42
42
 
43
- failure = step_mother.steps(:failed).any? ||
43
+ failure = if configuration.wip?
44
+ step_mother.scenarios(:passed).any?
45
+ else
46
+ step_mother.scenarios(:failed).any? ||
44
47
  (configuration.strict? && step_mother.steps(:undefined).any?)
48
+ end
45
49
  end
46
50
 
47
51
  def load_plain_text_features
@@ -85,6 +85,15 @@ module Cucumber
85
85
  @io.flush
86
86
  end
87
87
 
88
+ def print_passing_wip(options)
89
+ return unless options[:wip]
90
+ passed = step_mother.scenarios(:passed)
91
+ if passed.any?
92
+ @io.puts "\nThe --wip switch was used, so I didn't expect anything to pass. These scenarios passed:"
93
+ print_elements(passed, :passed, "scenarios")
94
+ end
95
+ end
96
+
88
97
  def announce(announcement)
89
98
  @io.puts
90
99
  @io.puts(format_string(announcement, :tag))
@@ -7,7 +7,7 @@ end
7
7
 
8
8
  module Cucumber
9
9
  module Formatter
10
- class JUnit < Cucumber::Ast::Visitor
10
+ class Junit < Cucumber::Ast::Visitor
11
11
 
12
12
  def initialize(step_mother, io, options)
13
13
  super(step_mother)