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.
- data/History.txt +27 -2
- data/Manifest.txt +10 -4
- data/examples/ramaze/README.textile +7 -0
- data/examples/ramaze/Rakefile +6 -0
- data/examples/ramaze/app.rb +21 -0
- data/examples/ramaze/features/add.feature +11 -0
- data/examples/ramaze/features/step_definitions/add_steps.rb +15 -0
- data/examples/ramaze/features/support/env.rb +32 -0
- data/examples/ramaze/layout/default.html.erb +8 -0
- data/examples/ramaze/view/index.html.erb +5 -0
- data/examples/sinatra/features/support/env.rb +1 -1
- data/features/cucumber_cli.feature +5 -5
- data/features/usage_and_stepdefs_formatter.feature +169 -0
- data/lib/cucumber/ast/step_invocation.rb +7 -0
- data/lib/cucumber/ast/tags.rb +6 -1
- data/lib/cucumber/ast/tree_walker.rb +179 -0
- data/lib/cucumber/cli/options.rb +20 -11
- data/lib/cucumber/formatter/html.rb +0 -2
- data/lib/cucumber/formatter/stepdefs.rb +14 -0
- data/lib/cucumber/formatter/usage.rb +106 -50
- data/lib/cucumber/language_support/language_methods.rb +6 -9
- data/lib/cucumber/rb_support/rb_language.rb +16 -3
- data/lib/cucumber/rb_support/rb_step_definition.rb +7 -1
- data/lib/cucumber/step_match.rb +4 -0
- data/lib/cucumber/step_mother.rb +8 -37
- data/lib/cucumber/version.rb +1 -1
- data/spec/cucumber/ast/background_spec.rb +0 -6
- data/spec/cucumber/ast/tree_walker_spec.rb +11 -0
- data/spec/cucumber/cli/options_spec.rb +12 -0
- data/spec/cucumber/formatter/html_spec.rb +0 -1
- data/spec/cucumber/rb_support/rb_step_definition_spec.rb +0 -9
- data/spec/cucumber/step_mother_spec.rb +13 -34
- metadata +14 -6
- data/features/steps_formatter.feature +0 -26
- data/features/usage.feature +0 -126
- data/lib/cucumber/formatter/profile.rb +0 -78
- 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
|
data/lib/cucumber/cli/options.rb
CHANGED
@@ -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',
|
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 --
|
178
|
+
"Implies --no-snippets.") do
|
175
179
|
@options[:dry_run] = true
|
176
|
-
@
|
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
|
348
|
+
@options[:formats] += other_options[:formats]
|
349
|
+
@options[:formats] = stdout_formats[0..0] + non_stdout_formats
|
341
350
|
end
|
342
351
|
|
343
352
|
self
|
@@ -2,82 +2,138 @@ require 'cucumber/formatter/progress'
|
|
2
2
|
|
3
3
|
module Cucumber
|
4
4
|
module Formatter
|
5
|
-
|
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
|
-
@
|
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
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
|
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
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|