aslakhellesoy-cucumber 0.3.5 → 0.3.6.1

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