cucumber 0.3.103 → 0.3.104

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 +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)