cucumber 0.2.0 → 0.2.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 (38) hide show
  1. data/History.txt +19 -0
  2. data/Manifest.txt +5 -1
  3. data/examples/self_test/features/tons_of_cukes.feature +52 -0
  4. data/examples/sinatra/features/support/env.rb +6 -2
  5. data/examples/tickets/features/248.feature +11 -0
  6. data/examples/tickets/features/step_definitons/248_steps.rb +15 -0
  7. data/features/cucumber_cli.feature +1 -1
  8. data/features/custom_formatter.feature +2 -2
  9. data/features/usage.feature +108 -0
  10. data/lib/autotest/cucumber_mixin.rb +1 -1
  11. data/lib/cucumber/ast/feature.rb +1 -1
  12. data/lib/cucumber/ast/features.rb +6 -0
  13. data/lib/cucumber/ast/step.rb +2 -13
  14. data/lib/cucumber/ast/step_invocation.rb +11 -3
  15. data/lib/cucumber/ast/table.rb +4 -0
  16. data/lib/cucumber/cli/configuration.rb +11 -14
  17. data/lib/cucumber/cli/main.rb +14 -21
  18. data/lib/cucumber/formatter.rb +1 -1
  19. data/lib/cucumber/formatter/html.rb +47 -8
  20. data/lib/cucumber/formatter/pretty.rb +1 -2
  21. data/lib/cucumber/formatter/rerun.rb +8 -0
  22. data/lib/cucumber/formatter/usage.rb +69 -0
  23. data/lib/cucumber/jbehave.rb +1 -0
  24. data/lib/cucumber/rails/world.rb +22 -21
  25. data/lib/cucumber/step_definition.rb +65 -54
  26. data/lib/cucumber/step_match.rb +10 -2
  27. data/lib/cucumber/step_mother.rb +1 -8
  28. data/lib/cucumber/version.rb +1 -1
  29. data/rails_generators/cucumber/templates/env.rb +2 -0
  30. data/rails_generators/feature/templates/steps.erb +1 -1
  31. data/spec/cucumber/ast/feature_spec.rb +2 -1
  32. data/spec/cucumber/ast/step_collection_spec.rb +8 -0
  33. data/spec/cucumber/cli/configuration_spec.rb +18 -6
  34. data/spec/cucumber/cli/main_spec.rb +1 -13
  35. data/spec/cucumber/parser/feature_parser_spec.rb +15 -15
  36. data/spec/cucumber/step_definition_spec.rb +21 -9
  37. metadata +7 -3
  38. data/gem_tasks/jar.rake +0 -67
@@ -32,13 +32,6 @@ module Cucumber
32
32
  step_mother.options = configuration.options
33
33
 
34
34
  require_files
35
-
36
- if(configuration.print_step_definitions?)
37
- step_mother.print_step_definitions(@out_stream)
38
- Kernel.exit(0)
39
- return # In specs, exit is stubbed
40
- end
41
-
42
35
  enable_diffing
43
36
 
44
37
  features = load_plain_text_features
@@ -53,7 +46,18 @@ module Cucumber
53
46
  Kernel.exit(failure ? 1 : 0)
54
47
  end
55
48
 
56
- private
49
+ def load_plain_text_features
50
+ features = Ast::Features.new
51
+ parser = Parser::FeatureParser.new
52
+
53
+ verbose_log("Features:")
54
+ configuration.feature_files.each do |f|
55
+ features.add_feature(parser.parse_file(f))
56
+ verbose_log(" * #{f}")
57
+ end
58
+ verbose_log("\n"*2)
59
+ features
60
+ end
57
61
 
58
62
  def configuration
59
63
  return @configuration if @configuration
@@ -62,6 +66,8 @@ module Cucumber
62
66
  @configuration.parse!(@args)
63
67
  @configuration
64
68
  end
69
+
70
+ private
65
71
 
66
72
  def require_files
67
73
  verbose_log("Ruby files required:")
@@ -77,19 +83,6 @@ module Cucumber
77
83
  verbose_log("\n")
78
84
  end
79
85
 
80
- def load_plain_text_features
81
- features = Ast::Features.new
82
- parser = Parser::FeatureParser.new
83
-
84
- verbose_log("Features:")
85
- configuration.feature_files.each do |f|
86
- features.add_feature(parser.parse_file(f))
87
- verbose_log(" * #{f}")
88
- end
89
- verbose_log("\n"*2)
90
- features
91
- end
92
-
93
86
  def verbose_log(string)
94
87
  @out_stream.puts(string) if configuration.verbose?
95
88
  end
@@ -1 +1 @@
1
- %w{color_io pretty progress profile rerun html}.each{|n| require "cucumber/formatter/#{n}"}
1
+ %w{color_io pretty progress profile rerun html usage}.each{|n| require "cucumber/formatter/#{n}"}
@@ -83,37 +83,73 @@ module Cucumber
83
83
  @builder.h4("#{keyword} #{name}")
84
84
  end
85
85
 
86
- def visit_steps(scenarios)
86
+ def visit_steps(steps)
87
87
  @builder.ol do
88
88
  super
89
89
  end
90
90
  end
91
91
 
92
+ def visit_step(step)
93
+ @step_id = step.dom_id
94
+ @builder.li(:id => @step_id) do
95
+ super
96
+ end
97
+ end
98
+
92
99
  def visit_step_name(keyword, step_match, status, source_indent, background)
93
- step_name = step_match.format_args(lambda{|param| "<span>#{param}</span>"})
94
- @builder.li("#{keyword} #{step_name}", :class => status)
100
+ @step_matches ||= []
101
+ @skip_step = @step_matches.index(step_match)
102
+ @step_matches << step_match
103
+
104
+ unless @skip_step
105
+ step_name = step_match.format_args(lambda{|param| "<span>#{param}</span>"})
106
+ @builder.div(:class => status) do |div|
107
+ div << "#{keyword} #{step_name}"
108
+ end
109
+ end
110
+ end
111
+
112
+ def visit_exception(exception, status)
113
+ @builder.pre(format_exception(exception), :class => status)
95
114
  end
96
115
 
97
116
  def visit_multiline_arg(multiline_arg)
117
+ return if @skip_step
98
118
  if Ast::Table === multiline_arg
99
119
  @builder.table do
100
120
  super
101
121
  end
102
122
  else
103
- @builder.p do
104
- super
105
- end
123
+ super
124
+ end
125
+ end
126
+
127
+ def visit_py_string(string, status)
128
+ @builder.pre(:class => status) do |pre|
129
+ pre << string
106
130
  end
107
131
  end
108
132
 
109
133
  def visit_table_row(table_row)
110
- @builder.tr do
134
+ @row_id = table_row.dom_id
135
+ @col_index = 0
136
+ @builder.tr(:id => @row_id) do
111
137
  super
112
138
  end
139
+ if table_row.exception
140
+ @builder.tr do
141
+ @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
142
+ @builder.pre do |pre|
143
+ pre << format_exception(table_row.exception)
144
+ end
145
+ end
146
+ end
147
+ end
113
148
  end
114
149
 
115
150
  def visit_table_cell_value(value, width, status)
116
- @builder.td(value, :class => status)
151
+ @builder.td(value, :class => status, :id => "#{@row_id}_#{@col_index}")
152
+ @col_index += 1
117
153
  end
118
154
 
119
155
  def announce(announcement)
@@ -128,6 +164,9 @@ module Cucumber
128
164
  end
129
165
  end
130
166
 
167
+ def format_exception(exception)
168
+ (["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n")
169
+ end
131
170
  end
132
171
  end
133
172
  end
@@ -121,16 +121,15 @@ module Cucumber
121
121
 
122
122
  def visit_step_name(keyword, step_match, status, source_indent, background)
123
123
  @step_matches ||= []
124
-
125
124
  non_failed_background_step_outside_background = !@in_background && background && (status != :failed)
126
125
  @skip_step = @step_matches.index(step_match) || non_failed_background_step_outside_background
126
+ @step_matches << step_match
127
127
 
128
128
  unless(@skip_step)
129
129
  source_indent = nil unless @options[:source]
130
130
  formatted_step_name = format_step(keyword, step_match, status, source_indent)
131
131
  @io.puts(" " + formatted_step_name)
132
132
  end
133
- @step_matches << step_match
134
133
  end
135
134
 
136
135
  def visit_multiline_arg(multiline_arg)
@@ -1,5 +1,13 @@
1
1
  module Cucumber
2
2
  module Formatter
3
+ # This formatter keeps track of all failing features and print out their location.
4
+ # Example:
5
+ #
6
+ # features/foo.feature:34 features/bar.feature:11:76:81
7
+ #
8
+ # This formatter is used by AutoTest - it will use the output to decide what
9
+ # to run the next time, simply passing the output string on the command line.
10
+ #
3
11
  class Rerun < Ast::Visitor
4
12
  def initialize(step_mother, io, options)
5
13
  super(step_mother)
@@ -0,0 +1,69 @@
1
+ require 'cucumber/formatter/progress'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ class Usage < Ast::Visitor
6
+ include Console
7
+
8
+ def initialize(step_mother, io, options)
9
+ super(step_mother)
10
+ @io = io
11
+ @options = options
12
+ @step_definitions = Hash.new { |h,step_definition| h[step_definition] = [] }
13
+ @locations = []
14
+ end
15
+
16
+ def visit_features(features)
17
+ super
18
+ print_summary
19
+ end
20
+
21
+ def visit_step(step)
22
+ @step = step
23
+ super
24
+ end
25
+
26
+ def visit_step_name(keyword, step_match, status, source_indent, background)
27
+ if step_match.step_definition
28
+ location = @step.file_colon_line
29
+ return if @locations.index(location)
30
+ @locations << location
31
+
32
+ description = format_step(keyword, step_match, status, nil)
33
+ length = (keyword + step_match.format_args).jlength
34
+ @step_definitions[step_match.step_definition] << [step_match, description, length, location]
35
+ end
36
+ end
37
+
38
+ def print_summary
39
+ sorted_defs = @step_definitions.keys.sort_by{|step_definition| step_definition.backtrace_line}
40
+
41
+ sorted_defs.each do |step_definition|
42
+ step_matches_and_descriptions = @step_definitions[step_definition].sort_by do |step_match_and_description|
43
+ step_match = step_match_and_description[0]
44
+ step_match.step_definition.regexp.inspect
45
+ end
46
+
47
+ step_matches = step_matches_and_descriptions.map{|step_match_and_description| step_match_and_description[0]}
48
+
49
+ lengths = step_matches_and_descriptions.map do |step_match_and_description|
50
+ step_match_and_description[2]
51
+ end
52
+ lengths << step_definition.text_length
53
+ max_length = lengths.max
54
+
55
+ @io.print step_definition.regexp.inspect
56
+ @io.puts format_string(" # #{step_definition.file_colon_line}".indent(max_length - step_definition.text_length), :comment)
57
+ step_matches_and_descriptions.each do |step_match_and_description|
58
+ step_match = step_match_and_description[0]
59
+ description = step_match_and_description[1]
60
+ length = step_match_and_description[2]
61
+ file_colon_line = step_match_and_description[3]
62
+ @io.print " #{description}"
63
+ @io.puts format_string(" # #{file_colon_line}".indent(max_length - length), :comment)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -27,6 +27,7 @@ if defined?(JRUBY_VERSION)
27
27
  end
28
28
 
29
29
  def step_match(name_to_match, name_to_report)
30
+ raise "FIXME"
30
31
  if(match = name_to_match.match(@regexp))
31
32
  StepMatch.new(self, name_to_match, name_to_report, match.captures)
32
33
  else
@@ -9,22 +9,10 @@ else
9
9
  end
10
10
  require 'test/unit/testresult'
11
11
 
12
- # These allow exceptions to come through as opposed to being caught and having non-helpful responses returned.
13
- ActionController::Base.class_eval do
14
- def rescue_action(exception)
15
- raise exception
16
- end
17
- end
18
- ActionController::Dispatcher.class_eval do
19
- def self.failsafe_response(output, status, exception = nil)
20
- raise exception
21
- end
22
- end
23
-
24
12
  # So that Test::Unit doesn't launch at the end - makes it think it has already been run.
25
13
  Test::Unit.run = true if Test::Unit.respond_to?(:run=)
26
14
 
27
- $cucumber_toplevel = self
15
+ $__cucumber_toplevel = self
28
16
 
29
17
  module Cucumber #:nodoc:
30
18
  module Rails
@@ -44,20 +32,21 @@ module Cucumber #:nodoc:
44
32
  def self.use_transactional_fixtures
45
33
  World.use_transactional_fixtures = true
46
34
  if defined?(ActiveRecord::Base)
47
- $cucumber_toplevel.Before do
48
- if ActiveRecord::Base.connection.respond_to?(:increment_open_transactions)
49
- ActiveRecord::Base.connection.increment_open_transactions
35
+ $__cucumber_toplevel.Before do
36
+ @__cucumber_ar_connection = ActiveRecord::Base.connection
37
+ if @__cucumber_ar_connection.respond_to?(:increment_open_transactions)
38
+ @__cucumber_ar_connection.increment_open_transactions
50
39
  else
51
40
  ActiveRecord::Base.__send__(:increment_open_transactions)
52
41
  end
53
- ActiveRecord::Base.connection.begin_db_transaction
42
+ @__cucumber_ar_connection.begin_db_transaction
54
43
  ActionMailer::Base.deliveries = [] if defined?(ActionMailer::Base)
55
44
  end
56
45
 
57
- $cucumber_toplevel.After do
58
- ActiveRecord::Base.connection.rollback_db_transaction
59
- if ActiveRecord::Base.connection.respond_to?(:decrement_open_transactions)
60
- ActiveRecord::Base.connection.decrement_open_transactions
46
+ $__cucumber_toplevel.After do
47
+ @__cucumber_ar_connection.rollback_db_transaction
48
+ if @__cucumber_ar_connection.respond_to?(:decrement_open_transactions)
49
+ @__cucumber_ar_connection.decrement_open_transactions
61
50
  else
62
51
  ActiveRecord::Base.__send__(:decrement_open_transactions)
63
52
  end
@@ -65,6 +54,18 @@ module Cucumber #:nodoc:
65
54
  end
66
55
  end
67
56
 
57
+ def self.bypass_rescue
58
+ ActionController::Base.class_eval do
59
+ def rescue_action(exception)
60
+ raise exception
61
+ end
62
+ end
63
+ ActionController::Dispatcher.class_eval do
64
+ def self.failsafe_response(output, status, exception = nil)
65
+ raise exception
66
+ end
67
+ end
68
+ end
68
69
  end
69
70
  end
70
71
 
@@ -3,6 +3,50 @@ require 'cucumber/core_ext/string'
3
3
  require 'cucumber/core_ext/proc'
4
4
 
5
5
  module Cucumber
6
+ module StepDefinitionMethods
7
+ def step_match(name_to_match, name_to_report)
8
+ if(match = name_to_match.match(regexp))
9
+ StepMatch.new(self, name_to_match, name_to_report, match.captures)
10
+ else
11
+ nil
12
+ end
13
+ end
14
+
15
+ # Formats the matched arguments of the associated Step. This method
16
+ # is usually called from visitors, which render output.
17
+ #
18
+ # The +format+ can either be a String or a Proc.
19
+ #
20
+ # If it is a String it should be a format string according to
21
+ # <tt>Kernel#sprinf</tt>, for example:
22
+ #
23
+ # '<span class="param">%s</span></tt>'
24
+ #
25
+ # If it is a Proc, it should take one argument and return the formatted
26
+ # argument, for example:
27
+ #
28
+ # lambda { |param| "[#{param}]" }
29
+ #
30
+ def format_args(step_name, format)
31
+ step_name.gzub(regexp, format)
32
+ end
33
+
34
+ def match(step_name)
35
+ case step_name
36
+ when String then regexp.match(step_name)
37
+ when Regexp then regexp == step_name
38
+ end
39
+ end
40
+
41
+ def backtrace_line
42
+ "#{file_colon_line}:in `#{regexp.inspect}'"
43
+ end
44
+
45
+ def text_length
46
+ regexp.inspect.jlength
47
+ end
48
+ end
49
+
6
50
  # A Step Definition holds a Regexp and a Proc, and is created
7
51
  # by calling <tt>Given</tt>, <tt>When</tt> or <tt>Then</tt>
8
52
  # in the <tt>step_definitions</tt> ruby files - for example:
@@ -14,7 +58,22 @@ module Cucumber
14
58
  class StepDefinition
15
59
  def self.snippet_text(step_keyword, step_name)
16
60
  escaped = Regexp.escape(step_name).gsub('\ ', ' ').gsub('/', '\/')
17
- "#{step_keyword} /^#{escaped}$/ do\n pending\nend"
61
+ param_pattern = /"([^\"]*)"/
62
+
63
+ match = escaped.match(param_pattern)
64
+ if match
65
+ n = 0
66
+ block_args = match.captures.map do |a|
67
+ n += 1
68
+ "arg#{n}"
69
+ end
70
+ block_arg_string = " |#{block_args.join(", ")}|"
71
+ else
72
+ block_arg_string = ""
73
+ end
74
+
75
+ escaped = escaped.gsub(param_pattern, '"([^\\"]*)"')
76
+ "#{step_keyword} /^#{escaped}$/ do#{block_arg_string}\n pending\nend"
18
77
  end
19
78
 
20
79
  class MissingProc < StandardError
@@ -23,7 +82,7 @@ module Cucumber
23
82
  end
24
83
  end
25
84
 
26
- attr_reader :regexp
85
+ include StepDefinitionMethods
27
86
 
28
87
  def initialize(pattern, &proc)
29
88
  raise MissingProc if proc.nil?
@@ -34,70 +93,22 @@ module Cucumber
34
93
  @regexp, @proc = pattern, proc
35
94
  end
36
95
 
37
- def step_match(name_to_match, name_to_report)
38
- if(match = name_to_match.match(@regexp))
39
- StepMatch.new(self, name_to_match, name_to_report, match.captures)
40
- else
41
- nil
42
- end
96
+ def regexp
97
+ @regexp
43
98
  end
44
99
 
45
- def invoke(world, args, step_name)
100
+ def invoke(world, args)
46
101
  args = args.map{|arg| Ast::PyString === arg ? arg.to_s : arg}
47
102
  begin
48
- world.cucumber_instance_exec(true, @regexp.inspect, *args, &@proc)
103
+ world.cucumber_instance_exec(true, regexp.inspect, *args, &@proc)
49
104
  rescue Cucumber::ArityMismatchError => e
50
105
  e.backtrace.unshift(self.backtrace_line)
51
106
  raise e
52
107
  end
53
108
  end
54
109
 
55
- #:stopdoc:
56
-
57
- def match(step_name)
58
- case step_name
59
- when String then @regexp.match(step_name)
60
- when Regexp then @regexp == step_name
61
- end
62
- end
63
-
64
- # Formats the matched arguments of the associated Step. This method
65
- # is usually called from visitors, which render output.
66
- #
67
- # The +format+ either be a String or a Proc.
68
- #
69
- # If it is a String it should be a format string according to
70
- # <tt>Kernel#sprinf</tt>, for example:
71
- #
72
- # '<span class="param">%s</span></tt>'
73
- #
74
- # If it is a Proc, it should take one argument and return the formatted
75
- # argument, for example:
76
- #
77
- # lambda { |param| "[#{param}]" }
78
- #
79
- def format_args(step_name, format)
80
- step_name.gzub(@regexp, format)
81
- end
82
-
83
- def matched_args(step_name)
84
- step_name.match(@regexp).captures
85
- end
86
-
87
- def backtrace_line
88
- "#{file_colon_line}:in `#{@regexp.inspect}'"
89
- end
90
-
91
110
  def file_colon_line
92
111
  @proc.file_colon_line
93
112
  end
94
-
95
- def text_length
96
- @regexp.inspect.jlength
97
- end
98
-
99
- def to_s(indent = 0)
100
- @regexp.inspect + (' # ').indent(indent) + file_colon_line
101
- end
102
113
  end
103
114
  end