aslakhellesoy-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 +179 -0
  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 +11 -0
  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 +14 -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
@@ -0,0 +1,179 @@
1
+ module Cucumber
2
+ module Ast
3
+ # Walks the AST, executing steps and notifying listeners
4
+ class TreeWalker
5
+ attr_accessor :options #:nodoc:
6
+ attr_reader :step_mother #:nodoc:
7
+
8
+ def initialize(step_mother, listeners = [], options = {}, io = STDOUT)
9
+ @step_mother, @listeners, @options, @io = step_mother, listeners, options, io
10
+ end
11
+
12
+ def visit_features(features)
13
+ broadcast(features) do
14
+ features.accept(self)
15
+ end
16
+ end
17
+
18
+ def visit_feature(feature)
19
+ broadcast(feature) do
20
+ feature.accept(self)
21
+ end
22
+ end
23
+
24
+ def visit_comment(comment)
25
+ broadcast(comment) do
26
+ comment.accept(self)
27
+ end
28
+ end
29
+
30
+ def visit_comment_line(comment_line)
31
+ broadcast(comment_line)
32
+ end
33
+
34
+ def visit_tags(tags)
35
+ broadcast(tags) do
36
+ tags.accept(self)
37
+ end
38
+ end
39
+
40
+ def visit_tag_name(tag_name)
41
+ broadcast(tag_name)
42
+ end
43
+
44
+ def visit_feature_name(name)
45
+ broadcast(name)
46
+ end
47
+
48
+ # +feature_element+ is either Scenario or ScenarioOutline
49
+ def visit_feature_element(feature_element)
50
+ broadcast(feature_element) do
51
+ feature_element.accept(self)
52
+ end
53
+ end
54
+
55
+ def visit_background(background)
56
+ broadcast(background) do
57
+ background.accept(self)
58
+ end
59
+ end
60
+
61
+ def visit_background_name(keyword, name, file_colon_line, source_indent)
62
+ broadcast(keyword, name, file_colon_line, source_indent)
63
+ end
64
+
65
+ def visit_examples_array(examples_array)
66
+ broadcast(examples_array) do
67
+ examples_array.accept(self)
68
+ end
69
+ end
70
+
71
+ def visit_examples(examples)
72
+ broadcast(examples) do
73
+ examples.accept(self)
74
+ end
75
+ end
76
+
77
+ def visit_examples_name(keyword, name)
78
+ broadcast(keyword, name)
79
+ end
80
+
81
+ def visit_outline_table(outline_table)
82
+ broadcast(outline_table) do
83
+ outline_table.accept(self)
84
+ end
85
+ end
86
+
87
+ def visit_scenario_name(keyword, name, file_colon_line, source_indent)
88
+ broadcast(keyword, name, file_colon_line, source_indent)
89
+ end
90
+
91
+ def visit_steps(steps)
92
+ broadcast(steps) do
93
+ steps.accept(self)
94
+ end
95
+ end
96
+
97
+ def visit_step(step)
98
+ broadcast(step) do
99
+ step.accept(self)
100
+ end
101
+ end
102
+
103
+ def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
104
+ broadcast(keyword, step_match, multiline_arg, status, exception, source_indent, background) do
105
+ visit_step_name(keyword, step_match, status, source_indent, background)
106
+ visit_multiline_arg(multiline_arg) if multiline_arg
107
+ visit_exception(exception, status) if exception
108
+ end
109
+ end
110
+
111
+ def visit_step_name(keyword, step_match, status, source_indent, background) #:nodoc:
112
+ broadcast(keyword, step_match, status, source_indent, background)
113
+ end
114
+
115
+ def visit_multiline_arg(multiline_arg) #:nodoc:
116
+ broadcast(multiline_arg) do
117
+ multiline_arg.accept(self)
118
+ end
119
+ end
120
+
121
+ def visit_exception(exception, status) #:nodoc:
122
+ broadcast(exception, status)
123
+ end
124
+
125
+ def visit_py_string(string)
126
+ broadcast(string)
127
+ end
128
+
129
+ def visit_table_row(table_row)
130
+ broadcast(table_row) do
131
+ table_row.accept(self)
132
+ end
133
+ end
134
+
135
+ def visit_table_cell(table_cell)
136
+ broadcast(table_cell) do
137
+ table_cell.accept(self)
138
+ end
139
+ end
140
+
141
+ def visit_table_cell_value(value, status)
142
+ broadcast(value, status)
143
+ end
144
+
145
+ # Print +announcement+. This method can be called from within StepDefinitions.
146
+ def announce(announcement)
147
+ broadcast(announcement)
148
+ end
149
+
150
+ private
151
+
152
+ def broadcast(*args, &block)
153
+ message = extract_method_name_from(caller)
154
+ message.gsub!('visit_', '')
155
+
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
161
+ send_to_all(message, *args)
162
+ end
163
+ end
164
+
165
+ def send_to_all(message, *args)
166
+ @listeners.each do |listener|
167
+ if listener.respond_to?(message)
168
+ listener.__send__(message, *args)
169
+ end
170
+ end
171
+ end
172
+
173
+ def extract_method_name_from(call_stack)
174
+ call_stack[0].match(/in `(.*)'/).captures[0]
175
+ end
176
+
177
+ end
178
+ end
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