cucumber 0.2.0 → 0.2.1

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