cucumber 0.3.103 → 0.3.104

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 +27 -2
  2. data/Manifest.txt +10 -4
  3. data/examples/ramaze/README.textile +7 -0
  4. data/examples/ramaze/Rakefile +6 -0
  5. data/examples/ramaze/app.rb +21 -0
  6. data/examples/ramaze/features/add.feature +11 -0
  7. data/examples/ramaze/features/step_definitions/add_steps.rb +15 -0
  8. data/examples/ramaze/features/support/env.rb +32 -0
  9. data/examples/ramaze/layout/default.html.erb +8 -0
  10. data/examples/ramaze/view/index.html.erb +5 -0
  11. data/examples/sinatra/features/support/env.rb +1 -1
  12. data/features/cucumber_cli.feature +5 -5
  13. data/features/usage_and_stepdefs_formatter.feature +169 -0
  14. data/lib/cucumber/ast/step_invocation.rb +7 -0
  15. data/lib/cucumber/ast/tags.rb +6 -1
  16. data/lib/cucumber/ast/tree_walker.rb +5 -11
  17. data/lib/cucumber/cli/options.rb +20 -11
  18. data/lib/cucumber/formatter/html.rb +0 -2
  19. data/lib/cucumber/formatter/stepdefs.rb +14 -0
  20. data/lib/cucumber/formatter/usage.rb +106 -50
  21. data/lib/cucumber/language_support/language_methods.rb +6 -9
  22. data/lib/cucumber/rb_support/rb_language.rb +16 -3
  23. data/lib/cucumber/rb_support/rb_step_definition.rb +7 -1
  24. data/lib/cucumber/step_match.rb +4 -0
  25. data/lib/cucumber/step_mother.rb +8 -37
  26. data/lib/cucumber/version.rb +1 -1
  27. data/spec/cucumber/ast/background_spec.rb +0 -6
  28. data/spec/cucumber/ast/tree_walker_spec.rb +0 -7
  29. data/spec/cucumber/cli/options_spec.rb +12 -0
  30. data/spec/cucumber/formatter/html_spec.rb +0 -1
  31. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +0 -9
  32. data/spec/cucumber/step_mother_spec.rb +13 -34
  33. metadata +12 -6
  34. data/features/steps_formatter.feature +0 -26
  35. data/features/usage.feature +0 -126
  36. data/lib/cucumber/formatter/profile.rb +0 -78
  37. data/lib/cucumber/formatters/unicode.rb +0 -7
@@ -10,7 +10,6 @@ module Cucumber
10
10
  end
11
11
 
12
12
  def visit_features(features)
13
- warn "The listener(s) (#{deprecated_listeners.map{ |l| l.class }}) appear to support the legacy Ast::Visitor interface, which is no longer supported." if deprecated_listeners.any?
14
13
  broadcast(features) do
15
14
  features.accept(self)
16
15
  end
@@ -154,14 +153,13 @@ module Cucumber
154
153
  message = extract_method_name_from(caller)
155
154
  message.gsub!('visit_', '')
156
155
 
157
- unless block_given?
156
+ if block_given?
157
+ send_to_all("before_#{message}", *args)
158
+ yield if block_given?
159
+ send_to_all("after_#{message}", *args)
160
+ else
158
161
  send_to_all(message, *args)
159
- return
160
162
  end
161
-
162
- send_to_all("before_#{message}", *args)
163
- yield if block_given?
164
- send_to_all("after_#{message}", *args)
165
163
  end
166
164
 
167
165
  def send_to_all(message, *args)
@@ -176,10 +174,6 @@ module Cucumber
176
174
  call_stack[0].match(/in `(.*)'/).captures[0]
177
175
  end
178
176
 
179
- def deprecated_listeners
180
- @listeners.select{ |l| l.respond_to?(:visit_features) }
181
- end
182
-
183
177
  end
184
178
  end
185
179
  end
@@ -9,13 +9,17 @@ module Cucumber
9
9
  'pdf' => ['Cucumber::Formatter::Pdf', "Generates a PDF report. You need to have the\n" +
10
10
  "#{' ' * 51}prawn gem installed. Will pick up logo from\n" +
11
11
  "#{' ' * 51}features/support/logo.png if present."],
12
- 'profile' => ['Cucumber::Formatter::Profile', 'Prints the 10 slowest steps at the end.'],
13
12
  'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
14
13
  'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
15
- 'usage' => ['Cucumber::Formatter::Usage', 'Prints where step definitions are used.'],
14
+ 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" +
15
+ "#{' ' * 51}The slowest step definitions (with duration) are\n" +
16
+ "#{' ' * 51}listed first. If --dry-run is used the duration\n" +
17
+ "#{' ' * 51}is not shown, and step definitions are sorted by\n" +
18
+ "#{' ' * 51}filename instead."],
19
+ 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" +
20
+ "the usage formatter, except that steps are not printed."],
16
21
  'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'],
17
- 'tag_cloud' => ['Cucumber::Formatter::TagCloud', 'Prints a tag cloud of tag usage.'],
18
- 'steps' => ['Cucumber::Formatter::Steps', 'Prints location of step definitions.']
22
+ 'tag_cloud' => ['Cucumber::Formatter::TagCloud', 'Prints a tag cloud of tag usage.']
19
23
  }
20
24
  max = BUILTIN_FORMATS.keys.map{|s| s.length}.max
21
25
  FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
@@ -171,9 +175,9 @@ module Cucumber
171
175
  end
172
176
  opts.on("-d", "--dry-run", "Invokes formatters without executing the steps.",
173
177
  "This also omits the loading of your support/env.rb file if it exists.",
174
- "Implies --quiet.") do
178
+ "Implies --no-snippets.") do
175
179
  @options[:dry_run] = true
176
- @quiet = true
180
+ @options[:snippets] = false
177
181
  end
178
182
  opts.on("-a", "--autoformat DIRECTORY",
179
183
  "Reformats (pretty prints) feature files and write them to DIRECTORY.",
@@ -257,12 +261,16 @@ module Cucumber
257
261
  attr_reader :options, :profiles, :expanded_args
258
262
  protected :options, :profiles, :expanded_args
259
263
 
260
- def non_stdout_formats
261
- @options[:formats].select {|format, output| output != @out_stream }
262
- end
263
-
264
264
  private
265
265
 
266
+ def non_stdout_formats
267
+ @options[:formats].select {|format, output| output != @out_stream }
268
+ end
269
+
270
+ def stdout_formats
271
+ @options[:formats].select {|format, output| output == @out_stream }
272
+ end
273
+
266
274
  def extract_environment_variables
267
275
  @args.delete_if do |arg|
268
276
  if arg =~ /^(\w+)=(.*)$/
@@ -337,7 +345,8 @@ module Cucumber
337
345
  if @options[:formats].empty?
338
346
  @options[:formats] = other_options[:formats]
339
347
  else
340
- @options[:formats] += other_options.non_stdout_formats
348
+ @options[:formats] += other_options[:formats]
349
+ @options[:formats] = stdout_formats[0..0] + non_stdout_formats
341
350
  end
342
351
 
343
352
  self
@@ -1,7 +1,5 @@
1
1
  require 'cucumber/formatter/ordered_xml_markup'
2
2
  require 'cucumber/formatter/duration'
3
- require 'xml'
4
- require 'ruby-debug'
5
3
 
6
4
  module Cucumber
7
5
  module Formatter
@@ -0,0 +1,14 @@
1
+ require 'cucumber/formatter/usage'
2
+
3
+ module Cucumber
4
+ module Formatter
5
+ class Stepdefs < Usage
6
+ def print_steps(stepdef_key)
7
+ end
8
+
9
+ def max_step_length
10
+ 0
11
+ end
12
+ end
13
+ end
14
+ end
@@ -2,82 +2,138 @@ require 'cucumber/formatter/progress'
2
2
 
3
3
  module Cucumber
4
4
  module Formatter
5
- # The formatter used for <tt>--format usage</tt>
6
- class Usage
5
+ class Usage < Progress
7
6
  include Console
8
7
 
8
+ class StepDefKey
9
+ attr_reader :regexp_source, :file_colon_line
10
+ attr_accessor :mean_duration, :status
11
+
12
+ def initialize(regexp_source, file_colon_line)
13
+ @regexp_source, @file_colon_line = regexp_source, file_colon_line
14
+ end
15
+
16
+ def eql?(o)
17
+ regexp_source == o.regexp_source && file_colon_line == o.file_colon_line
18
+ end
19
+
20
+ def hash
21
+ regexp_source.hash + 17*file_colon_line.hash
22
+ end
23
+ end
24
+
9
25
  def initialize(step_mother, io, options)
26
+ @step_mother = step_mother
10
27
  @io = io
11
28
  @options = options
12
- @step_definitions = Hash.new { |h,step_definition| h[step_definition] = [] }
13
- @all_step_definitions = step_mother.step_definitions.dup
14
- @locations = []
15
- end
16
-
17
- def after_features(features)
18
- print_summary(features)
29
+ @stepdef_to_match = Hash.new{|h,stepdef_key| h[stepdef_key] = []}
19
30
  end
20
31
 
21
32
  def before_step(step)
22
33
  @step = step
23
34
  end
24
35
 
25
- def step_name(keyword, step_match, status, source_indent, background)
26
- if step_match.step_definition
27
- location = @step.file_colon_line
28
- return if @locations.index(location)
29
- @locations << location
30
-
31
- description = format_step(keyword, step_match, status, nil)
32
- length = (keyword + step_match.format_args).jlength
33
- @step_definitions[step_match.step_definition] << [step_match, description, length, location]
34
- @all_step_definitions.delete(step_match.step_definition)
36
+ def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
37
+ @step_duration = Time.now
38
+ end
39
+
40
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
41
+ duration = Time.now - @step_duration
42
+ if step_match.name.nil? # nil if it's from a scenario outline
43
+ stepdef_key = StepDefKey.new(step_match.step_definition.regexp_source, step_match.step_definition.file_colon_line)
44
+
45
+ @stepdef_to_match[stepdef_key] << {
46
+ :keyword => keyword,
47
+ :step_match => step_match,
48
+ :status => status,
49
+ :file_colon_line => @step.file_colon_line,
50
+ :duration => duration
51
+ }
35
52
  end
53
+ super
36
54
  end
37
55
 
38
56
  def print_summary(features)
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_source
45
- end
57
+ add_unused_stepdefs
58
+ aggregate_info
46
59
 
47
- step_matches = step_matches_and_descriptions.map{|step_match_and_description| step_match_and_description[0]}
60
+ if @options[:dry_run]
61
+ keys = @stepdef_to_match.keys.sort {|a,b| a.regexp_source <=> b.regexp_source}
62
+ else
63
+ keys = @stepdef_to_match.keys.sort {|a,b| a.mean_duration <=> b.mean_duration}.reverse
64
+ end
65
+
66
+ keys.each do |stepdef_key|
67
+ print_step_definition(stepdef_key)
48
68
 
49
- lengths = step_matches_and_descriptions.map do |step_match_and_description|
50
- step_match_and_description[2]
69
+ if @stepdef_to_match[stepdef_key].any?
70
+ print_steps(stepdef_key)
71
+ else
72
+ @io.puts(" " + format_string("NOT MATCHED BY ANY STEPS", :failed))
51
73
  end
52
- lengths << step_definition.text_length
53
- max_length = lengths.max
54
-
55
- @io.print step_definition.regexp_source
56
- @io.puts format_string(" # #{step_definition.file_colon_line}".indent(max_length - step_definition.text_length), :comment)
57
- da = step_matches_and_descriptions.map 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
- " #{description}" + format_string(" # #{file_colon_line}".indent(max_length - length), :comment)
74
+ end
75
+ @io.puts
76
+ super
77
+ end
78
+
79
+ def print_step_definition(stepdef_key)
80
+ @io.print format_string(sprintf("%.7f", stepdef_key.mean_duration), :skipped) + " " unless @options[:dry_run]
81
+ @io.print format_string(stepdef_key.regexp_source, stepdef_key.status)
82
+ if @options[:source]
83
+ indent = max_length - stepdef_key.regexp_source.jlength
84
+ line_comment = " # #{stepdef_key.file_colon_line}".indent(indent)
85
+ @io.print(format_string(line_comment, :comment))
86
+ end
87
+ @io.puts
88
+ end
89
+
90
+ def print_steps(stepdef_key)
91
+ @stepdef_to_match[stepdef_key].each do |step|
92
+ @io.print " "
93
+ @io.print format_string(sprintf("%.7f", step[:duration]), :skipped) + " " unless @options[:dry_run]
94
+ @io.print format_step(step[:keyword], step[:step_match], step[:status], nil)
95
+ if @options[:source]
96
+ indent = max_length - (step[:keyword].jlength + step[:step_match].format_args.jlength)
97
+ line_comment = " # #{step[:file_colon_line]}".indent(indent)
98
+ @io.print(format_string(line_comment, :comment))
63
99
  end
64
- da.sort.each{|d| puts d}
100
+ @io.puts
65
101
  end
102
+ end
103
+
104
+ def max_length
105
+ [max_stepdef_length, max_step_length].compact.max
106
+ end
66
107
 
67
- print_unused_step_definitions
108
+ def max_stepdef_length
109
+ @stepdef_to_match.keys.flatten.map{|key| key.regexp_source.jlength}.max
68
110
  end
69
111
 
70
- def print_unused_step_definitions
71
- if @all_step_definitions.any?
72
- max_length = @all_step_definitions.map{|step_definition| step_definition.text_length}.max
112
+ def max_step_length
113
+ @stepdef_to_match.values.flatten.map do |step|
114
+ step[:keyword].jlength + step[:step_match].format_args.jlength
115
+ end.max
116
+ end
73
117
 
74
- @io.puts format_string("(::) UNUSED (::)", :failed)
75
- @all_step_definitions.each do |step_definition|
76
- @io.print format_string(step_definition.regexp_source, :failed)
77
- @io.puts format_string(" # #{step_definition.file_colon_line}".indent(max_length - step_definition.text_length), :comment)
118
+ def aggregate_info
119
+ @stepdef_to_match.each do |key, steps|
120
+ if steps.empty?
121
+ key.status = :skipped
122
+ key.mean_duration = 0
123
+ else
124
+ key.status = Ast::StepInvocation.worst_status(steps.map{|step| step[:status]})
125
+ total_duration = steps.inject(0) {|sum, step| step[:duration] + sum}
126
+ key.mean_duration = total_duration / steps.length
78
127
  end
79
128
  end
80
129
  end
130
+
131
+ def add_unused_stepdefs
132
+ @step_mother.unmatched_step_definitions.each do |step_definition|
133
+ stepdef_key = StepDefKey.new(step_definition.regexp_source, step_definition.file_colon_line)
134
+ @stepdef_to_match[stepdef_key] = []
135
+ end
136
+ end
81
137
  end
82
138
  end
83
- end
139
+ end
@@ -1,6 +1,12 @@
1
+ require 'cucumber/step_match'
2
+
1
3
  module Cucumber
2
4
  module LanguageSupport
3
5
  module LanguageMethods
6
+ def create_step_match(step_definition, step_name, formatted_step_name, step_arguments)
7
+ StepMatch.new(step_definition, step_name, formatted_step_name, step_arguments)
8
+ end
9
+
4
10
  def before(scenario)
5
11
  begin_scenario
6
12
  execute_before(scenario)
@@ -41,15 +47,6 @@ module Cucumber
41
47
  transform
42
48
  end
43
49
 
44
- def add_step_definition(step_definition)
45
- step_definitions << step_definition
46
- step_definition
47
- end
48
-
49
- def step_definitions
50
- @step_definitions ||= []
51
- end
52
-
53
50
  def hooks_for(phase, scenario) #:nodoc:
54
51
  hooks[phase.to_sym].select{|hook| scenario.accept_hook?(hook)}
55
52
  end
@@ -34,6 +34,7 @@ module Cucumber
34
34
 
35
35
  def initialize(step_mother)
36
36
  @step_mother = step_mother
37
+ @step_definitions = []
37
38
  RbDsl.rb_language = self
38
39
  end
39
40
 
@@ -56,10 +57,20 @@ module Cucumber
56
57
  end
57
58
  end
58
59
 
60
+ def step_matches(step_name, formatted_step_name)
61
+ @step_definitions.map do |step_definition|
62
+ step_definition.step_match(step_name, formatted_step_name)
63
+ end.compact
64
+ end
65
+
59
66
  def arguments_from(regexp, step_name)
60
67
  @regexp_argument_matcher.arguments_from(regexp, step_name)
61
68
  end
62
69
 
70
+ def unmatched_step_definitions
71
+ @step_definitions.select{|step_definition| !step_definition.matched?}
72
+ end
73
+
63
74
  def snippet_text(step_keyword, step_name, multiline_arg_class = nil)
64
75
  escaped = Regexp.escape(step_name).gsub('\ ', ' ').gsub('/', '\/')
65
76
  escaped = escaped.gsub(PARAM_PATTERN, ESCAPED_PARAM_PATTERN)
@@ -94,7 +105,9 @@ module Cucumber
94
105
  end
95
106
 
96
107
  def register_rb_step_definition(regexp, proc)
97
- add_step_definition(RbStepDefinition.new(self, regexp, proc))
108
+ step_definition = RbStepDefinition.new(self, regexp, proc)
109
+ @step_definitions << step_definition
110
+ step_definition
98
111
  end
99
112
 
100
113
  def build_rb_world_factory(world_modules, proc)
@@ -106,12 +119,12 @@ module Cucumber
106
119
  @world_modules += world_modules
107
120
  end
108
121
 
109
- protected
110
-
111
122
  def load_code_file(code_file)
112
123
  require code_file # This will cause self.add_step_definition, self.add_hook, and self.add_transform to be called from RbDsl
113
124
  end
114
125
 
126
+ protected
127
+
115
128
  def begin_scenario
116
129
  begin_rb_scenario
117
130
  end
@@ -42,7 +42,9 @@ module Cucumber
42
42
  end
43
43
 
44
44
  def arguments_from(step_name)
45
- RegexpArgumentMatcher.arguments_from(@regexp, step_name)
45
+ args = RegexpArgumentMatcher.arguments_from(@regexp, step_name)
46
+ @matched = true if args
47
+ args
46
48
  end
47
49
 
48
50
  def invoke(args)
@@ -56,6 +58,10 @@ module Cucumber
56
58
  end
57
59
  end
58
60
 
61
+ def matched?
62
+ @matched
63
+ end
64
+
59
65
  def file_colon_line
60
66
  @proc.file_colon_line
61
67
  end
@@ -69,6 +69,10 @@ module Cucumber
69
69
  end
70
70
  s
71
71
  end
72
+
73
+ def inspect #:nodoc:
74
+ sprintf("#<%s:0x%x>", self.class, self.object_id)
75
+ end
72
76
  end
73
77
 
74
78
  class NoStepMatch #:nodoc:
@@ -38,16 +38,6 @@ module Cucumber
38
38
  end
39
39
  end
40
40
 
41
- # Raised when 2 or more StepDefinition have the same Regexp
42
- class Redundant < StandardError
43
- def initialize(step_def_1, step_def_2)
44
- message = "Multiple step definitions have the same Regexp:\n\n"
45
- message << step_def_1.backtrace_line << "\n"
46
- message << step_def_2.backtrace_line << "\n\n"
47
- super(message)
48
- end
49
- end
50
-
51
41
  # This is the meaty part of Cucumber that ties everything together.
52
42
  class StepMother
53
43
  include Constantize
@@ -87,17 +77,12 @@ module Cucumber
87
77
  def load_code_file(step_def_file)
88
78
  if programming_language = programming_language_for(step_def_file)
89
79
  log.debug(" * #{step_def_file}\n")
90
- step_definitions = programming_language.step_definitions_for(step_def_file)
91
- register_step_definitions(step_definitions)
80
+ programming_language.load_code_file(step_def_file)
92
81
  else
93
82
  log.debug(" * #{step_def_file} [NOT SUPPORTED]\n")
94
83
  end
95
84
  end
96
85
 
97
- def register_step_definitions(step_definitions)
98
- step_definitions.each{|step_definition| register_step_definition(step_definition)}
99
- end
100
-
101
86
  # Loads and registers programming language implementation.
102
87
  # Instances are cached, so calling with the same argument
103
88
  # twice will return the same instance.
@@ -152,7 +137,9 @@ module Cucumber
152
137
  end
153
138
 
154
139
  def step_match(step_name, formatted_step_name=nil) #:nodoc:
155
- matches = step_definitions.map { |d| d.step_match(step_name, formatted_step_name) }.compact
140
+ matches = @programming_languages.map do |programming_language|
141
+ programming_language.step_matches(step_name, formatted_step_name)
142
+ end.flatten
156
143
  raise Undefined.new(step_name) if matches.empty?
157
144
  matches = best_matches(step_name, matches) if matches.size > 1 && options[:guess]
158
145
  raise Ambiguous.new(step_name, matches, options[:guess]) if matches.size > 1
@@ -174,16 +161,11 @@ module Cucumber
174
161
  top_groups
175
162
  end
176
163
  end
177
-
178
- def clear! #:nodoc:
179
- step_definitions.clear
180
- hooks.clear
181
- steps.clear
182
- scenarios.clear
183
- end
184
164
 
185
- def step_definitions #:nodoc:
186
- @step_definitions ||= []
165
+ def unmatched_step_definitions
166
+ @programming_languages.map do |programming_language|
167
+ programming_language.unmatched_step_definitions
168
+ end.flatten
187
169
  end
188
170
 
189
171
  def snippet_text(step_keyword, step_name, multiline_arg_class) #:nodoc:
@@ -244,17 +226,6 @@ module Cucumber
244
226
 
245
227
  private
246
228
 
247
- # Registers a StepDefinition. This can be a Ruby StepDefintion,
248
- # or any other kind of object that implements the StepDefintion
249
- # contract (API).
250
- def register_step_definition(step_definition)
251
- step_definitions.each do |already|
252
- raise Redundant.new(already, step_definition) if already == step_definition
253
- end
254
- step_definitions << step_definition
255
- step_definition
256
- end
257
-
258
229
  def programming_language_for(step_def_file) #:nodoc:
259
230
  if ext = File.extname(step_def_file)[1..-1]
260
231
  return nil if @unsupported_programming_languages.index(ext)