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