cuke_sniffer 0.0.1
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.
- data/bin/cuke_sniffer.rb +34 -0
- data/lib/cuke_sniffer.rb +13 -0
- data/lib/cuke_sniffer/cli.rb +226 -0
- data/lib/cuke_sniffer/constants.rb +35 -0
- data/lib/cuke_sniffer/feature.rb +116 -0
- data/lib/cuke_sniffer/feature_rules_evaluator.rb +69 -0
- data/lib/cuke_sniffer/report/markup.rhtml +311 -0
- data/lib/cuke_sniffer/rule_config.rb +161 -0
- data/lib/cuke_sniffer/rules_evaluator.rb +41 -0
- data/lib/cuke_sniffer/scenario.rb +163 -0
- data/lib/cuke_sniffer/step_definition.rb +116 -0
- metadata +58 -0
data/bin/cuke_sniffer.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'cuke_sniffer'
|
3
|
+
|
4
|
+
if ARGV.include? "-h" or ARGV.include? "--help"
|
5
|
+
puts HELP_CMD_TEXT
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
@cuke_sniffer = nil
|
10
|
+
if (ARGV[0] != nil and File.directory?(ARGV[0])) and (ARGV[1] != nil and File.directory?(ARGV[1]))
|
11
|
+
@cuke_sniffer = CukeSniffer::CLI.new(ARGV[0], ARGV[1])
|
12
|
+
else
|
13
|
+
@cuke_sniffer = CukeSniffer::CLI.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def print_results
|
17
|
+
puts @cuke_sniffer.output_results
|
18
|
+
end
|
19
|
+
|
20
|
+
if ARGV.include? "--out" or ARGV.include? "-o"
|
21
|
+
index = ARGV.index("--out")
|
22
|
+
index ||= ARGV.index("-o")
|
23
|
+
out_type = ARGV[index + 1]
|
24
|
+
case out_type
|
25
|
+
when "html"
|
26
|
+
@cuke_sniffer.output_html
|
27
|
+
else
|
28
|
+
print_results
|
29
|
+
end
|
30
|
+
else
|
31
|
+
print_results
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "Completed Sniffing."
|
data/lib/cuke_sniffer.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
require 'cuke_sniffer/constants'
|
3
|
+
require 'cuke_sniffer/rule_config'
|
4
|
+
require 'cuke_sniffer/rules_evaluator'
|
5
|
+
require 'cuke_sniffer/feature_rules_evaluator'
|
6
|
+
require 'cuke_sniffer/feature'
|
7
|
+
require 'cuke_sniffer/scenario'
|
8
|
+
require 'cuke_sniffer/step_definition'
|
9
|
+
require 'cuke_sniffer/cli'
|
10
|
+
|
11
|
+
module CukeSniffer
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'cuke_sniffer/constants'
|
3
|
+
|
4
|
+
module CukeSniffer
|
5
|
+
class CLI
|
6
|
+
include CukeSniffer::Constants
|
7
|
+
|
8
|
+
attr_accessor :features, :step_definitions, :summary
|
9
|
+
|
10
|
+
def initialize(features_location = Dir.getwd, step_definitions_location = Dir.getwd)
|
11
|
+
@features_location = features_location
|
12
|
+
@step_definitions_location = step_definitions_location
|
13
|
+
@features = []
|
14
|
+
@step_definitions = []
|
15
|
+
|
16
|
+
puts "\nFeatures:"
|
17
|
+
unless features_location.nil?
|
18
|
+
if File.file?(features_location)
|
19
|
+
@features = [Feature.new(features_location)]
|
20
|
+
else
|
21
|
+
build_file_list_from_folder(features_location, ".feature").each { |location|
|
22
|
+
@features << Feature.new(location)
|
23
|
+
print '.'
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
puts("\nStep Definitions:")
|
29
|
+
unless step_definitions_location.nil?
|
30
|
+
if File.file?(step_definitions_location)
|
31
|
+
@step_definitions = [build_step_definitions(step_definitions_location)]
|
32
|
+
else
|
33
|
+
build_file_list_from_folder(step_definitions_location, "steps.rb").each { |location|
|
34
|
+
@step_definitions << build_step_definitions(location)
|
35
|
+
print '.'
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@step_definitions.flatten!
|
41
|
+
@summary = {
|
42
|
+
:total_score => 0,
|
43
|
+
:features => {},
|
44
|
+
:step_definitions => {},
|
45
|
+
:improvement_list => {}
|
46
|
+
}
|
47
|
+
puts "\nCataloging Step Calls: "
|
48
|
+
catalog_step_calls
|
49
|
+
puts "\nAssessing Score: "
|
50
|
+
assess_score
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_file_list_from_folder(folder_name, extension)
|
54
|
+
list = []
|
55
|
+
Dir.entries(folder_name).each_entry do |file_name|
|
56
|
+
unless FILE_IGNORE_LIST.include?(file_name)
|
57
|
+
file_name = "#{folder_name}/#{file_name}"
|
58
|
+
if File.directory?(file_name)
|
59
|
+
list << build_file_list_from_folder(file_name, extension)
|
60
|
+
elsif file_name.include?(extension)
|
61
|
+
list << file_name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
list.flatten
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_step_definitions(file_name)
|
69
|
+
step_file_lines = []
|
70
|
+
step_file = File.open(file_name)
|
71
|
+
step_file.each_line { |line| step_file_lines << line }
|
72
|
+
step_file.close
|
73
|
+
|
74
|
+
counter = 0
|
75
|
+
step_code = []
|
76
|
+
step_definitions = []
|
77
|
+
found_first_step = false
|
78
|
+
until counter >= step_file_lines.length
|
79
|
+
if step_file_lines[counter] =~ STEP_DEFINITION_REGEX and !step_code.empty? and found_first_step
|
80
|
+
step_definitions << StepDefinition.new("#{file_name}:#{counter+1 - step_code.count}", step_code)
|
81
|
+
step_code = []
|
82
|
+
end
|
83
|
+
found_first_step = true if step_file_lines[counter] =~ STEP_DEFINITION_REGEX
|
84
|
+
step_code << step_file_lines[counter].strip
|
85
|
+
counter+=1
|
86
|
+
end
|
87
|
+
step_definitions << StepDefinition.new("#{file_name}:#{counter+1}", step_code) unless step_code.empty?
|
88
|
+
step_definitions
|
89
|
+
end
|
90
|
+
|
91
|
+
def assess_array(array)
|
92
|
+
min, max, min_file, max_file = nil
|
93
|
+
total = 0
|
94
|
+
array.each do |node|
|
95
|
+
score = node.score
|
96
|
+
@summary[:total_score] += score
|
97
|
+
node.rules_hash.each_key do |key|
|
98
|
+
@summary[:improvement_list][key] ||= 0
|
99
|
+
@summary[:improvement_list][key] += node.rules_hash[key]
|
100
|
+
end
|
101
|
+
min, min_file = score, node.location if (min.nil? or score < min)
|
102
|
+
max, max_file = score, node.location if (max.nil? or score > max)
|
103
|
+
total += score
|
104
|
+
end
|
105
|
+
{
|
106
|
+
:total => array.count,
|
107
|
+
:min => min,
|
108
|
+
:min_file => min_file,
|
109
|
+
:max => max,
|
110
|
+
:max_file => max_file,
|
111
|
+
:average => (total.to_f/array.count.to_f).round(2)
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def assess_score
|
116
|
+
@summary[:features] = assess_array(@features)
|
117
|
+
@summary[:step_definitions] = assess_array(@step_definitions) unless @step_definitions.empty?
|
118
|
+
sort_improvement_list
|
119
|
+
end
|
120
|
+
|
121
|
+
def sort_improvement_list
|
122
|
+
sorted_array = @summary[:improvement_list].sort_by { |improvement, occurrence| occurrence }
|
123
|
+
@summary[:improvement_list] = {}
|
124
|
+
sorted_array.reverse.each { |node|
|
125
|
+
@summary[:improvement_list][node[0]] = node[1]
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def output_results
|
130
|
+
feature_results = @summary[:features]
|
131
|
+
step_definition_results = @summary[:step_definitions]
|
132
|
+
#todo this string is completely dependent on the tabbing in the string
|
133
|
+
output = "Suite Summary
|
134
|
+
Total Score: #{@summary[:total_score]}
|
135
|
+
Features (#@features_location)
|
136
|
+
Min: #{feature_results[:min]} (#{feature_results[:min_file]})
|
137
|
+
Max: #{feature_results[:max]} (#{feature_results[:max_file]})
|
138
|
+
Average: #{feature_results[:average]}
|
139
|
+
Step Definitions (#@step_definitions_location)
|
140
|
+
Min: #{step_definition_results[:min]} (#{step_definition_results[:min_file]})
|
141
|
+
Max: #{step_definition_results[:max]} (#{step_definition_results[:max_file]})
|
142
|
+
Average: #{step_definition_results[:average]}
|
143
|
+
Improvements to make:"
|
144
|
+
create_improvement_list.each { |item| output << "\n #{item}" }
|
145
|
+
output
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_improvement_list
|
149
|
+
output = []
|
150
|
+
@summary[:improvement_list].each_key { |improvement| output << "(#{summary[:improvement_list][improvement]})#{improvement}" }
|
151
|
+
output
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_all_steps
|
155
|
+
steps = {}
|
156
|
+
@features.each do |feature|
|
157
|
+
feature.scenarios.each do |scenario|
|
158
|
+
counter = 1
|
159
|
+
scenario.steps.each do |step|
|
160
|
+
location = scenario.location.gsub(/:\d*/, ":#{scenario.start_line + counter}")
|
161
|
+
steps[location] = step
|
162
|
+
counter += 1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
@step_definitions.each do |definition|
|
167
|
+
definition.nested_steps.each_key do |key|
|
168
|
+
steps[key] = definition.nested_steps[key]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
steps
|
172
|
+
end
|
173
|
+
|
174
|
+
def catalog_step_calls
|
175
|
+
steps = get_all_steps
|
176
|
+
@step_definitions.each do |step_definition|
|
177
|
+
print '.'
|
178
|
+
calls = steps.find_all { |location, step| step.gsub(STEP_STYLES, "") =~ step_definition.regex }
|
179
|
+
calls.each { |call|
|
180
|
+
step_definition.add_call(call[0], call[1])
|
181
|
+
}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def update_step_definition(location, step)
|
186
|
+
@step_definitions.each do |step_definition|
|
187
|
+
if step.gsub(STEP_STYLES, "") =~ step_definition.regex
|
188
|
+
step_definition.add_call(location, step)
|
189
|
+
break
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_dead_steps
|
195
|
+
dead_steps = []
|
196
|
+
@step_definitions.each do |step_definition|
|
197
|
+
dead_steps << step_definition if step_definition.calls.empty?
|
198
|
+
end
|
199
|
+
dead_steps
|
200
|
+
end
|
201
|
+
|
202
|
+
def sort_by_score(array)
|
203
|
+
array.sort_by { |item| item.score }.reverse
|
204
|
+
end
|
205
|
+
|
206
|
+
def extract_markup
|
207
|
+
markup_location = File.join(File.dirname(__FILE__), 'report', 'markup.rhtml')
|
208
|
+
markup = ""
|
209
|
+
File.open(markup_location).lines.each do |line|
|
210
|
+
markup << line
|
211
|
+
end
|
212
|
+
markup
|
213
|
+
end
|
214
|
+
|
215
|
+
def output_html(file_name = "cuke_sniffer_results.html", cuke_sniffer = self)
|
216
|
+
@features = sort_by_score(@features)
|
217
|
+
@step_definitions = sort_by_score(@step_definitions)
|
218
|
+
|
219
|
+
markup_erb = ERB.new extract_markup
|
220
|
+
output = markup_erb.result(binding)
|
221
|
+
File.open(file_name, 'w') do |f|
|
222
|
+
f.write(output)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module CukeSniffer
|
2
|
+
module Constants
|
3
|
+
FILE_IGNORE_LIST = %w(. .. .svn)
|
4
|
+
DATE_REGEX = /(?<date>\d{2}\/\d{2}\/\d{4})/
|
5
|
+
COMMENT_REGEX = /#?.*/
|
6
|
+
|
7
|
+
FEATURE_NAME_REGEX = /Feature:\s*(?<name>.*)/
|
8
|
+
TAG_REGEX = /(?<tag>@\S*)/
|
9
|
+
SCENARIO_TITLE_STYLES = /(?<type>Background|Scenario|Scenario Outline|Scenario Template):\s*/
|
10
|
+
SCENARIO_TITLE_REGEX = /#{COMMENT_REGEX}#{SCENARIO_TITLE_STYLES}(?<name>.*)/
|
11
|
+
|
12
|
+
STEP_STYLES = /(?<style>Given|When|Then|And|Or|But|Transform|\*)\s/
|
13
|
+
STEP_REGEX = /^#{COMMENT_REGEX}#{STEP_STYLES}(?<step_string>.*)/
|
14
|
+
STEP_DEFINITION_REGEX = /^#{STEP_STYLES}\/(?<step>.+)\/\sdo\s?(\|(?<parameters>.*)\|)?$/
|
15
|
+
|
16
|
+
SIMPLE_NESTED_STEP_REGEX = /steps\s"#{STEP_STYLES}(?<step_string>.*)"/
|
17
|
+
SAME_LINE_COMPLEX_STEP_REGEX = /^steps\s%Q?{#{STEP_STYLES}(?<step_string>.*)}/
|
18
|
+
START_COMPLEX_STEP_REGEX = /steps\s%Q?\{\s*/
|
19
|
+
END_COMPLEX_STEP_REGEX = /}/
|
20
|
+
START_COMPLEX_WITH_STEP_REGEX = /steps\s%Q?\{#{STEP_STYLES}(?<step_string>.*)/
|
21
|
+
END_COMPLEX_WITH_STEP_REGEX = /#{STEP_STYLES}(?<step_string>.*)}/
|
22
|
+
|
23
|
+
HELP_CMD_TEXT = "Welcome to CukeSniffer!
|
24
|
+
Calling CukeSniffer with no arguments will run it against the current directory.
|
25
|
+
Other Options for Running include:
|
26
|
+
<feature_file_path>, <step_def_file_path> : Runs CukeSniffer against the
|
27
|
+
specified paths.
|
28
|
+
-o, --out html (name) : Runs CukeSniffer then outputs an
|
29
|
+
html file in the current
|
30
|
+
directory (with optional name).
|
31
|
+
-h, --help : You get this lovely document."
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'cuke_sniffer/constants'
|
2
|
+
require 'cuke_sniffer/rule_config'
|
3
|
+
require 'cuke_sniffer/feature_rules_evaluator'
|
4
|
+
require 'cuke_sniffer/scenario'
|
5
|
+
|
6
|
+
module CukeSniffer
|
7
|
+
class Feature < FeatureRulesEvaluator
|
8
|
+
include CukeSniffer::Constants
|
9
|
+
include CukeSniffer::RuleConfig
|
10
|
+
|
11
|
+
attr_accessor :background, :scenarios, :feature_rules_hash
|
12
|
+
|
13
|
+
def initialize(file_name)
|
14
|
+
super(file_name)
|
15
|
+
@scenarios = []
|
16
|
+
@feature_rules_hash = {}
|
17
|
+
split_feature(file_name)
|
18
|
+
evaluate_score
|
19
|
+
end
|
20
|
+
|
21
|
+
def split_feature(file_name)
|
22
|
+
feature_lines = []
|
23
|
+
|
24
|
+
feature_file = File.open(file_name)
|
25
|
+
feature_file.each_line { |line| feature_lines << line }
|
26
|
+
feature_file.close
|
27
|
+
|
28
|
+
index = 0
|
29
|
+
until feature_lines[index].match FEATURE_NAME_REGEX
|
30
|
+
update_tag_list(feature_lines[index])
|
31
|
+
index += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
until index >= feature_lines.length or feature_lines[index].match TAG_REGEX or feature_lines[index].match SCENARIO_TITLE_REGEX
|
35
|
+
create_name(feature_lines[index], "Feature:")
|
36
|
+
index += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
scenario_title_found = false
|
40
|
+
index_of_title = nil
|
41
|
+
code_block = []
|
42
|
+
until index >= feature_lines.length
|
43
|
+
if scenario_title_found and (feature_lines[index].match TAG_REGEX or feature_lines[index].match SCENARIO_TITLE_REGEX)
|
44
|
+
add_scenario_to_feature(code_block, index_of_title)
|
45
|
+
scenario_title_found = false
|
46
|
+
code_block = []
|
47
|
+
end
|
48
|
+
code_block << feature_lines[index].strip
|
49
|
+
if feature_lines[index].match SCENARIO_TITLE_REGEX
|
50
|
+
scenario_title_found = true
|
51
|
+
index_of_title = "#{file_name}:#{index + 1}"
|
52
|
+
end
|
53
|
+
index += 1
|
54
|
+
end
|
55
|
+
#TODO - Last scenario falling through above logic, needs a fix (code_block related)
|
56
|
+
add_scenario_to_feature(code_block, index_of_title) unless code_block==[]
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_scenario_to_feature(code_block, index_of_title)
|
60
|
+
scenario = CukeSniffer::Scenario.new(index_of_title, code_block)
|
61
|
+
if scenario.type == "Background"
|
62
|
+
@background = scenario
|
63
|
+
else
|
64
|
+
@scenarios << scenario
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def ==(comparison_object)
|
69
|
+
super(comparison_object)
|
70
|
+
comparison_object.scenarios == scenarios
|
71
|
+
end
|
72
|
+
|
73
|
+
def evaluate_score
|
74
|
+
super
|
75
|
+
rule_no_scenarios
|
76
|
+
rule_too_many_scenarios
|
77
|
+
rule_background_with_no_scenarios
|
78
|
+
rule_background_with_one_scenario
|
79
|
+
@feature_rules_hash = @rules_hash.clone
|
80
|
+
include_sub_scores(@background) unless @background.nil?
|
81
|
+
include_scenario_scores
|
82
|
+
end
|
83
|
+
|
84
|
+
def include_sub_scores(sub_class)
|
85
|
+
@score += sub_class.score
|
86
|
+
sub_class.rules_hash.each_key do |rule_descriptor|
|
87
|
+
rules_hash[rule_descriptor] ||= 0
|
88
|
+
rules_hash[rule_descriptor] += sub_class.rules_hash[rule_descriptor]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def include_scenario_scores
|
93
|
+
scenarios.each do |scenario|
|
94
|
+
include_sub_scores(scenario)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def rule_no_scenarios
|
99
|
+
store_rule(FEATURE_RULES[:no_scenarios]) if @scenarios.empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
def rule_too_many_scenarios
|
103
|
+
rule = FEATURE_RULES[:too_many_scenarios]
|
104
|
+
store_rule(rule) if @scenarios.size >= rule[:max]
|
105
|
+
end
|
106
|
+
|
107
|
+
def rule_background_with_no_scenarios
|
108
|
+
store_rule( FEATURE_RULES[:background_with_no_scenarios]) if @scenarios.empty? and !@background.nil?
|
109
|
+
end
|
110
|
+
|
111
|
+
def rule_background_with_one_scenario
|
112
|
+
store_rule(FEATURE_RULES[:background_with_one_scenario]) if @scenarios.size == 1 and !@background.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'cuke_sniffer/constants'
|
2
|
+
require 'cuke_sniffer/rule_config'
|
3
|
+
require 'cuke_sniffer/rules_evaluator'
|
4
|
+
|
5
|
+
module CukeSniffer
|
6
|
+
class FeatureRulesEvaluator < RulesEvaluator
|
7
|
+
include CukeSniffer::Constants
|
8
|
+
include CukeSniffer::RuleConfig
|
9
|
+
|
10
|
+
attr_accessor :tags, :name
|
11
|
+
|
12
|
+
def initialize(location)
|
13
|
+
@name = ""
|
14
|
+
@tags = []
|
15
|
+
super(location)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_name(line, filter)
|
19
|
+
line.gsub!(/#{COMMENT_REGEX}#{filter}/, "")
|
20
|
+
line.strip!
|
21
|
+
@name += " " unless @name.empty? or line.empty?
|
22
|
+
@name += line
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_tag_list(line)
|
26
|
+
if TAG_REGEX.match(line) && !is_comment?(line)
|
27
|
+
line.scan(TAG_REGEX).each { |tag| @tags << tag[0] }
|
28
|
+
else
|
29
|
+
@tags << line.strip unless line.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def evaluate_score
|
34
|
+
super
|
35
|
+
cls_name = self.class.to_s.gsub('CukeSniffer::', '')
|
36
|
+
rule_too_many_tags(cls_name)
|
37
|
+
rule_no_description(cls_name)
|
38
|
+
rule_numbers_in_name(cls_name)
|
39
|
+
rule_long_name(cls_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def rule_too_many_tags(type)
|
43
|
+
rule = SHARED_RULES[:too_many_tags]
|
44
|
+
store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if tags.size >= rule[:max]
|
45
|
+
end
|
46
|
+
|
47
|
+
def rule_no_description(type)
|
48
|
+
rule = SHARED_RULES[:no_description]
|
49
|
+
store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if name.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def rule_numbers_in_name(type)
|
53
|
+
rule = SHARED_RULES[:numbers_in_description]
|
54
|
+
store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if name =~ /\d/
|
55
|
+
end
|
56
|
+
|
57
|
+
def rule_long_name(type)
|
58
|
+
rule = SHARED_RULES[:long_name]
|
59
|
+
store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, type)) if name.size >= rule[:max]
|
60
|
+
end
|
61
|
+
|
62
|
+
def == (comparison_object)
|
63
|
+
super(comparison_object)
|
64
|
+
comparison_object.name == name
|
65
|
+
comparison_object.tags == tags
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
<script>
|
2
|
+
function toggle(item, off) {
|
3
|
+
updateDisplayStatus(item.getElementsByTagName("div")[off]);
|
4
|
+
}
|
5
|
+
function toggleById(item, link) {
|
6
|
+
updateDisplayStatus(document.getElementById(item));
|
7
|
+
toggleText(link)
|
8
|
+
}
|
9
|
+
function updateDisplayStatus(object) {
|
10
|
+
object.style.display = (object.style.display == "block") ? 'none' : "block";
|
11
|
+
}
|
12
|
+
function toggleText(link) {
|
13
|
+
var char_result = link.innerHTML.indexOf("+") > -1 ? "-" : "+";
|
14
|
+
link.innerHTML = link.innerHTML.replace(/(\+|\-)/, char_result)
|
15
|
+
}
|
16
|
+
|
17
|
+
</script>
|
18
|
+
<style>
|
19
|
+
body {
|
20
|
+
font-size: 12pt;
|
21
|
+
font-family: arial;
|
22
|
+
}
|
23
|
+
|
24
|
+
.padded {
|
25
|
+
padding-left: 3%;
|
26
|
+
}
|
27
|
+
|
28
|
+
.sections {
|
29
|
+
border: 2px solid #90ee90;
|
30
|
+
}
|
31
|
+
|
32
|
+
.shrink_section {
|
33
|
+
display: none;
|
34
|
+
}
|
35
|
+
|
36
|
+
.sub_title {
|
37
|
+
font-weight: bold;
|
38
|
+
font-size: 13pt;
|
39
|
+
padding: 1%;
|
40
|
+
}
|
41
|
+
|
42
|
+
.scenarios, .steps, {
|
43
|
+
border-top: 1px solid #bbb;
|
44
|
+
padding: 4px;
|
45
|
+
padding-left: 90px;
|
46
|
+
}
|
47
|
+
|
48
|
+
.scenarios, .steps {
|
49
|
+
border-top: 1px solid #bbb;
|
50
|
+
padding: 4px;
|
51
|
+
padding-left: 90px;
|
52
|
+
}
|
53
|
+
|
54
|
+
.title {
|
55
|
+
padding-left: 2%;
|
56
|
+
width: 25%;
|
57
|
+
background-color: green;
|
58
|
+
color: white;
|
59
|
+
border-top: 0;
|
60
|
+
border-bottom: 0;
|
61
|
+
padding-top: 4px;
|
62
|
+
font-weight: bold;
|
63
|
+
font-size: 15pt;
|
64
|
+
padding-bottom: 1%;
|
65
|
+
}
|
66
|
+
|
67
|
+
.rule {
|
68
|
+
background-color: #ffaaaa;
|
69
|
+
padding: 3px;
|
70
|
+
font-size: 13px;
|
71
|
+
}
|
72
|
+
|
73
|
+
span {
|
74
|
+
font-size: 20pt;
|
75
|
+
font-weight: bold;
|
76
|
+
width: 100%;
|
77
|
+
border-left: 2px solid #fff;
|
78
|
+
padding-bottom: 3px;
|
79
|
+
padding-left: 30px;
|
80
|
+
padding-top: 4px
|
81
|
+
}
|
82
|
+
|
83
|
+
.even {
|
84
|
+
background-color: #eeeeff;
|
85
|
+
border-bottom: 1px solid #ccc;
|
86
|
+
}
|
87
|
+
|
88
|
+
.odd {
|
89
|
+
border-bottom: 1px solid #ddd;
|
90
|
+
}
|
91
|
+
</style>
|
92
|
+
<div style="float:left; color: green; font-weight: bold; padding-left:10px;">
|
93
|
+
Cuke Sniffer
|
94
|
+
</div>
|
95
|
+
<br style="clear:both">
|
96
|
+
<div style="border-top: 2px solid lightgreen;">
|
97
|
+
<div style="float:right;border-right: 2px solid lightgreen;">
|
98
|
+
<div style="background-color:green;border-right:2px solid #fff; color:white;padding:4px; padding-left:40px;font-size:11pt;font-weight:bold;padding-right:10px;">
|
99
|
+
<%= summary[:total_score] %> Total Score
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
<br style="clear:both">
|
104
|
+
<div style="float:left;" class="title">
|
105
|
+
<a href=#summary style="color: white;font-style: normal;">
|
106
|
+
<span style="border-left-color:green;cursor: hand;" onclick="toggleById('summary',this)">- Summary</span>
|
107
|
+
</a>
|
108
|
+
</div>
|
109
|
+
<br style="clear:both">
|
110
|
+
|
111
|
+
<div class="sections">
|
112
|
+
<div name="summary" class="shrink_section" id="summary" style="display:block">
|
113
|
+
<%
|
114
|
+
feature_results = cuke_sniffer.summary[:features]
|
115
|
+
step_definition_results = cuke_sniffer.summary[:step_definitions]
|
116
|
+
%>
|
117
|
+
<br>
|
118
|
+
|
119
|
+
<div><span class="sub_title">Features</span><br></div>
|
120
|
+
<div class="padded">Min: <%= feature_results[:min] %> (<%= feature_results[:min_file] %>)<br>
|
121
|
+
Max: <%= feature_results[:max] %> (<%= feature_results[:max_file] %>)<br>
|
122
|
+
Average: <%= feature_results[:average] %>
|
123
|
+
</div>
|
124
|
+
<br>
|
125
|
+
|
126
|
+
<div><span class="sub_title">Step Definitions</span><br></div>
|
127
|
+
<div class="padded">Min: <%= step_definition_results[:min] %> (<%= step_definition_results[:min_file] %>)<br>
|
128
|
+
Max: <%= step_definition_results[:max] %> (<%= step_definition_results[:max_file] %>)<br>
|
129
|
+
Average: <%= step_definition_results[:average] %> </div>
|
130
|
+
<br>
|
131
|
+
|
132
|
+
<% list = summary[:improvement_list] %>
|
133
|
+
<div><span class="sub_title">Improvements to make: <%= list.size %></span><br></div>
|
134
|
+
<div class="padded">
|
135
|
+
<% list.each_key do |improvement| %>
|
136
|
+
(<%= list[improvement] %>) <%= improvement %>
|
137
|
+
<br>
|
138
|
+
<% end %>
|
139
|
+
<br>
|
140
|
+
</div>
|
141
|
+
<% dead_steps_list = cuke_sniffer.get_dead_steps %>
|
142
|
+
<div><span class="sub_title">Dead Steps: <%= dead_steps_list.size %></span><br></div>
|
143
|
+
<div class="padded">
|
144
|
+
<% dead_steps_list.each do |dead_step| %>
|
145
|
+
<table>
|
146
|
+
<tr>
|
147
|
+
<td style="width:300px">
|
148
|
+
/<%= dead_step.regex.to_s.match(/\(\?\-mix\:(?<regex>.*)\)/)[:regex] %>/
|
149
|
+
</td>
|
150
|
+
<td style="width:600px">
|
151
|
+
<%= dead_step.location %>
|
152
|
+
</td>
|
153
|
+
</tr>
|
154
|
+
</table>
|
155
|
+
<% end %>
|
156
|
+
<br>
|
157
|
+
</div>
|
158
|
+
</div>
|
159
|
+
</div>
|
160
|
+
<br><br>
|
161
|
+
<div style="float:left;" class="title">
|
162
|
+
<a href=#feature_details style="color: white;font-style: normal;">
|
163
|
+
<span style="border-left-color:green;cursor: hand;" onclick="toggleById('feature_details',this)">+ Features</span>
|
164
|
+
</a>
|
165
|
+
</div>
|
166
|
+
<div class="links" style="float:right"></div>
|
167
|
+
<br style="clear:both">
|
168
|
+
<div class="sections">
|
169
|
+
<div name="feature_details" class="shrink_section" id="feature_details">
|
170
|
+
<% i=0 %>
|
171
|
+
<% cuke_sniffer.features.each do |feature| %>
|
172
|
+
<% next if feature.score == 0 %>
|
173
|
+
<div style="cursor: hand;" onclick="toggle(this,3)" class="feature <%= i%2==0 ? "even" : "odd" %>">
|
174
|
+
<div style="float:left;padding:4px;font-weight:bold;color:red; width:3%"><%= feature.score %></div>
|
175
|
+
|
176
|
+
<div style=" float:left;">
|
177
|
+
<font class="featurename"><%= feature.location[feature.location.rindex("/")..-1] %></font></div>
|
178
|
+
<div style="width:60%;padding:4px; float:right">
|
179
|
+
<font class="featurepath"><%= feature.location[0..feature.location.rindex("/")] %></font></div>
|
180
|
+
<br style="clear:both;">
|
181
|
+
|
182
|
+
<div class="scenarios" style="display:none;">
|
183
|
+
|
184
|
+
<table>
|
185
|
+
<tr>
|
186
|
+
<td style="width: 600px;vertical-align:top;">
|
187
|
+
<b style="text-indent:5px">Feature: <%= feature.name %></b>
|
188
|
+
<br>
|
189
|
+
<b style="text-indent:5px">Scenarios: <%= feature.scenarios.size %></b>
|
190
|
+
</td>
|
191
|
+
<td style="width: 600px;vertical-align:top;">
|
192
|
+
<% unless feature.feature_rules_hash.empty? %>
|
193
|
+
<b>Feature Improvements:</b>
|
194
|
+
|
195
|
+
<div style="padding: 1%">
|
196
|
+
<% feature.feature_rules_hash.each_key do |rule| %>
|
197
|
+
<%= rule %>
|
198
|
+
<br>
|
199
|
+
<% end %>
|
200
|
+
</div>
|
201
|
+
<% end %>
|
202
|
+
</td>
|
203
|
+
</tr>
|
204
|
+
</table>
|
205
|
+
<br>
|
206
|
+
<% feature.scenarios = [feature.background, feature.scenarios].flatten unless feature.background.nil? %>
|
207
|
+
<table style="border-bottom:2px solid lightgreen">
|
208
|
+
<% feature.scenarios.each do |scenario| %>
|
209
|
+
<% next if scenario.rules_hash.empty? %>
|
210
|
+
<tr>
|
211
|
+
<td style="border-top:2px solid lightblue;width: 600px;vertical-align:top;">
|
212
|
+
<b style="padding:1%;"><%= scenario.type %>: <%= scenario.name %></b>
|
213
|
+
<br>
|
214
|
+
<% scenario.steps.each do |step| %>
|
215
|
+
<div style="text-indent:15px">
|
216
|
+
<%= step %>
|
217
|
+
</div>
|
218
|
+
<% end %>
|
219
|
+
<br>
|
220
|
+
</td>
|
221
|
+
<td style="border-top:2px solid lightblue;width: 600px;vertical-align:top;">
|
222
|
+
<b>Score: <%= scenario.score %></b>
|
223
|
+
<br>
|
224
|
+
<%= scenario.location %>
|
225
|
+
<br>
|
226
|
+
<b>Scenario Improvements:</b>
|
227
|
+
<br>
|
228
|
+
<% scenario.rules_hash.each_key do |rule| %>
|
229
|
+
<div style="text-indent:15px">
|
230
|
+
<%= rule %>
|
231
|
+
</div>
|
232
|
+
<% end %>
|
233
|
+
</td>
|
234
|
+
</tr>
|
235
|
+
<% end %>
|
236
|
+
</table>
|
237
|
+
</div>
|
238
|
+
</div>
|
239
|
+
<% i+=1 %>
|
240
|
+
<% end %>
|
241
|
+
</div>
|
242
|
+
</div>
|
243
|
+
<br><br>
|
244
|
+
<div style="float:left;" class="title">
|
245
|
+
<a href=#step_definitions style="color: white;font-style: normal;">
|
246
|
+
<span style="border-left-color:green;cursor: hand;" onclick="toggleById('step_definitions',this)">+ Step Definitions</span>
|
247
|
+
</a>
|
248
|
+
</div>
|
249
|
+
<div class="links" style="float:right"></div>
|
250
|
+
<br style="clear:both">
|
251
|
+
<div class="sections">
|
252
|
+
<div name="step_definitions" class="shrink_section" id="step_definitions">
|
253
|
+
<% i=0 %>
|
254
|
+
<% cuke_sniffer.step_definitions.each do |step_definition| %>
|
255
|
+
<div style="cursor: hand;" onclick="toggle(this,4)" class="feature <%= i%2==0 ? "even" : "odd" %>">
|
256
|
+
<% next if step_definition.score <= 0 and !step_definition.calls.empty? %>
|
257
|
+
<div style="float:left;padding:4px;font-weight:bold;color:red;"><%= step_definition.score %></div>
|
258
|
+
<div style="width:55%;padding:4px; float:left;">
|
259
|
+
|
260
|
+
<% parameters = "" %>
|
261
|
+
<% step_definition.parameters.each do |param| %>
|
262
|
+
<% if param == step_definition.parameters.first %>
|
263
|
+
<% parameters << "| " %>
|
264
|
+
<% end %>
|
265
|
+
<% parameters << param %>
|
266
|
+
|
267
|
+
<% if param == step_definition.parameters.last %>
|
268
|
+
<% parameters << " |" %>
|
269
|
+
<% else %>
|
270
|
+
<% parameters << ", " %>
|
271
|
+
<% end %>
|
272
|
+
<% end %>
|
273
|
+
<font class="featurename"><%= "/" + step_definition.regex.to_s.match(/\(\?\-mix\:(?<regex>.*)\)/)[:regex] + "/ do " + parameters %></font>
|
274
|
+
</div>
|
275
|
+
<% if step_definition.calls.empty? %>
|
276
|
+
<div style="float:left; width:10%;padding:4px;"><span class="rule"><b>Dead step!</b></span></div>
|
277
|
+
<% else %>
|
278
|
+
<div style="float:left; width:10%;padding:4px;"> </div>
|
279
|
+
<% end %>
|
280
|
+
<div style="width:30%;padding:4px; float:left;">
|
281
|
+
<font class="featurepath"><%= step_definition.location[step_definition.location.rindex("/")..-1] %></font>
|
282
|
+
</div>
|
283
|
+
<br style="clear:both;">
|
284
|
+
|
285
|
+
<div class="steps" style="display:none;">
|
286
|
+
<table>
|
287
|
+
<tr>
|
288
|
+
<td style="width: 600px;vertical-align:top;">
|
289
|
+
<b>Calls: <%= step_definition.calls.size %></b>
|
290
|
+
<% step_definition.calls.each_key do |call| %>
|
291
|
+
<div style="text-indent: 15px;"><%= step_definition.calls[call] %></div>
|
292
|
+
<div style="text-indent: 30px;"><%= call %></div>
|
293
|
+
<% end %>
|
294
|
+
</td>
|
295
|
+
<td style="width: 600px;vertical-align:top;">
|
296
|
+
<b>Step Definition improvements:</b>
|
297
|
+
<br>
|
298
|
+
<% step_definition.rules_hash.each_key do |improvement| %>
|
299
|
+
<div style="text-indent: 15px;">
|
300
|
+
(<%= step_definition.rules_hash[improvement] %>) <%= improvement %>
|
301
|
+
</div>
|
302
|
+
<% end %>
|
303
|
+
</td>
|
304
|
+
</tr>
|
305
|
+
</table>
|
306
|
+
</div>
|
307
|
+
</div>
|
308
|
+
<% i+=1 %>
|
309
|
+
<% end %>
|
310
|
+
</div>
|
311
|
+
</div>
|
@@ -0,0 +1,161 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module CukeSniffer
|
4
|
+
module RuleConfig
|
5
|
+
|
6
|
+
FATAL = 100 #will prevent suite from executing properly
|
7
|
+
ERROR = 25 #will cause problem with debugging
|
8
|
+
WARNING = 10 #readibility/misuse of cucumber
|
9
|
+
INFO = 1 #Small improvements that can be made
|
10
|
+
|
11
|
+
SHARED_RULES = {
|
12
|
+
:too_many_tags => {
|
13
|
+
:enabled => true,
|
14
|
+
:phrase => "{class} has too many tags.",
|
15
|
+
:score => INFO,
|
16
|
+
:max => 8
|
17
|
+
},
|
18
|
+
:no_description => {
|
19
|
+
:enabled => true,
|
20
|
+
:phrase => "{class} has no description.",
|
21
|
+
:score => ERROR,
|
22
|
+
},
|
23
|
+
:numbers_in_description => {
|
24
|
+
:enabled => true,
|
25
|
+
:phrase => "{class} has numbers in the description.",
|
26
|
+
:score => WARNING,
|
27
|
+
},
|
28
|
+
:long_name => {
|
29
|
+
:enabled => true,
|
30
|
+
:phrase => "{class} has a long description.",
|
31
|
+
:score => INFO,
|
32
|
+
:max => 180
|
33
|
+
},
|
34
|
+
:implementation_word => {
|
35
|
+
:enabled => true,
|
36
|
+
:phrase => "Implementation word used: {word}.",
|
37
|
+
:score => INFO,
|
38
|
+
:words => ["page", "site", "url", "button", "drop down", "dropdown", "select list", "click", "text box", "radio button", "check box", "xml", "window", "pop up", "pop-up", "screen"]
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
FEATURE_RULES = {
|
43
|
+
:background_with_no_scenarios => {
|
44
|
+
:enabled => true,
|
45
|
+
:phrase => "Feature has a background with no scenarios.",
|
46
|
+
:score => WARNING,
|
47
|
+
},
|
48
|
+
:background_with_one_scenario => {
|
49
|
+
:enabled => true,
|
50
|
+
:phrase => "Feature has a background with one scenario.",
|
51
|
+
:score => WARNING,
|
52
|
+
},
|
53
|
+
:no_scenarios => {
|
54
|
+
:enabled => true,
|
55
|
+
:phrase => "Feature with no scenarios.",
|
56
|
+
:score => ERROR,
|
57
|
+
},
|
58
|
+
:too_many_scenarios => {
|
59
|
+
:enabled => true,
|
60
|
+
:phrase => "Feature with too many scenarios.",
|
61
|
+
:score => INFO,
|
62
|
+
:max => 10,
|
63
|
+
},
|
64
|
+
}
|
65
|
+
|
66
|
+
SCENARIO_RULES = {
|
67
|
+
:too_many_steps => {
|
68
|
+
:enabled => true,
|
69
|
+
:phrase => "Scenario with too many steps.",
|
70
|
+
:score => WARNING,
|
71
|
+
:max => 7,
|
72
|
+
},
|
73
|
+
:out_of_order_steps => {
|
74
|
+
:enabled => true,
|
75
|
+
:phrase => "Scenario steps out of Given/When/Then order.",
|
76
|
+
:score => WARNING,
|
77
|
+
},
|
78
|
+
:invalid_first_step => {
|
79
|
+
:enabled => true,
|
80
|
+
:phrase => "Invalid first step. Began with And/But.",
|
81
|
+
:score => WARNING,
|
82
|
+
},
|
83
|
+
:asterisk_step => {
|
84
|
+
:enabled => true,
|
85
|
+
:phrase => "Step includes a * instead of Given/When/Then/And/But.",
|
86
|
+
:score => WARNING,
|
87
|
+
},
|
88
|
+
:commented_step => {
|
89
|
+
:enabled => true,
|
90
|
+
:phrase => "Commented step.",
|
91
|
+
:score => ERROR,
|
92
|
+
},
|
93
|
+
:commented_example => {
|
94
|
+
:enabled => true,
|
95
|
+
:phrase => "Commented example.",
|
96
|
+
:score => ERROR,
|
97
|
+
},
|
98
|
+
:no_examples => {
|
99
|
+
:enabled => true,
|
100
|
+
:phrase => "Scenario Outline with no examples.",
|
101
|
+
:score => FATAL,
|
102
|
+
},
|
103
|
+
:one_example => {
|
104
|
+
:enabled => true,
|
105
|
+
:phrase => "Scenario Outline with only one example.",
|
106
|
+
:score => WARNING,
|
107
|
+
},
|
108
|
+
:no_examples_table => {
|
109
|
+
:enabled => true,
|
110
|
+
:phrase => "Scenario Outline with no examples table.",
|
111
|
+
:score => FATAL,
|
112
|
+
},
|
113
|
+
:too_many_examples => {
|
114
|
+
:enabled => true,
|
115
|
+
:phrase => "Scenario Outline with too many examples.",
|
116
|
+
:score => WARNING,
|
117
|
+
:max => 10
|
118
|
+
},
|
119
|
+
:date_used => {
|
120
|
+
:enabled => true,
|
121
|
+
:phrase => "Date used.",
|
122
|
+
:score => INFO,
|
123
|
+
},
|
124
|
+
:no_steps => {
|
125
|
+
:enabled => true,
|
126
|
+
:phrase => "No steps in Scenario.",
|
127
|
+
:score => ERROR,
|
128
|
+
},
|
129
|
+
}
|
130
|
+
|
131
|
+
STEP_DEFINITION_RULES = {
|
132
|
+
:no_code => {
|
133
|
+
:enabled => true,
|
134
|
+
:phrase => "No code in Step Definition.",
|
135
|
+
:score => ERROR,
|
136
|
+
},
|
137
|
+
:too_many_parameters => {
|
138
|
+
:enabled => true,
|
139
|
+
:phrase => "Too many parameters in Step Definition.",
|
140
|
+
:score => WARNING,
|
141
|
+
:max => 4
|
142
|
+
},
|
143
|
+
:nested_step => {
|
144
|
+
:enabled => true,
|
145
|
+
:phrase => "Nested step call.",
|
146
|
+
:score => INFO,
|
147
|
+
},
|
148
|
+
:recursive_nested_step => {
|
149
|
+
:enabled => true,
|
150
|
+
:phrase => "Recursive nested step call.",
|
151
|
+
:score => FATAL,
|
152
|
+
},
|
153
|
+
:commented_code => {
|
154
|
+
:enabled => true,
|
155
|
+
:phrase => "Commented code in Step Definition.",
|
156
|
+
:score => INFO,
|
157
|
+
},
|
158
|
+
}
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CukeSniffer
|
2
|
+
class RulesEvaluator
|
3
|
+
attr_accessor :location, :score, :rules_hash
|
4
|
+
|
5
|
+
def initialize(location)
|
6
|
+
@location = location
|
7
|
+
@score = 0
|
8
|
+
@rules_hash = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate_score
|
12
|
+
end
|
13
|
+
|
14
|
+
def store_rule(rule)
|
15
|
+
if rule[:enabled]
|
16
|
+
@score += rule[:score]
|
17
|
+
@rules_hash[rule[:phrase]] ||= 0
|
18
|
+
@rules_hash[rule[:phrase]] += 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def store_updated_rule(rule, phrase)
|
23
|
+
store_rule({:enabled => rule[:enabled], :score => rule[:score], :phrase => phrase})
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def is_comment?(line)
|
28
|
+
if line =~ /^\#.*$/
|
29
|
+
true
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def == (comparison_object)
|
36
|
+
comparison_object.location == location
|
37
|
+
comparison_object.score == score
|
38
|
+
comparison_object.rules_hash == rules_hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'cuke_sniffer/constants'
|
2
|
+
require 'cuke_sniffer/rule_config'
|
3
|
+
|
4
|
+
module CukeSniffer
|
5
|
+
class Scenario < FeatureRulesEvaluator
|
6
|
+
include CukeSniffer::Constants
|
7
|
+
include CukeSniffer::RuleConfig
|
8
|
+
|
9
|
+
attr_accessor :start_line, :type, :steps, :inline_tables, :examples_table
|
10
|
+
|
11
|
+
def initialize(location, scenario)
|
12
|
+
super(location)
|
13
|
+
@start_line = location.match(/:(?<line>\d*)$/)[:line].to_i
|
14
|
+
@steps = []
|
15
|
+
@inline_tables = {}
|
16
|
+
@examples_table = []
|
17
|
+
split_scenario(scenario)
|
18
|
+
evaluate_score
|
19
|
+
end
|
20
|
+
|
21
|
+
def split_scenario(scenario)
|
22
|
+
index = 0
|
23
|
+
until index >= scenario.length or scenario[index] =~ SCENARIO_TITLE_STYLES
|
24
|
+
update_tag_list(scenario[index])
|
25
|
+
index += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
until index >= scenario.length or scenario[index].match STEP_REGEX or scenario[index].include?("Examples:")
|
29
|
+
match = scenario[index].match(SCENARIO_TITLE_STYLES)
|
30
|
+
@type = match[:type] unless match.nil?
|
31
|
+
create_name(scenario[index], SCENARIO_TITLE_STYLES)
|
32
|
+
index += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
until index >= scenario.length or scenario[index].include?("Examples:")
|
36
|
+
if scenario[index] =~ /\|.*\|/
|
37
|
+
step = scenario[index - 1]
|
38
|
+
@inline_tables[step] = []
|
39
|
+
until index >= scenario.length or scenario[index] =~ STEP_REGEX
|
40
|
+
@inline_tables[step] << scenario[index]
|
41
|
+
index += 1
|
42
|
+
end
|
43
|
+
index += 1
|
44
|
+
else
|
45
|
+
@steps << scenario[index] if scenario[index] =~ STEP_REGEX
|
46
|
+
index += 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if index < scenario.length and scenario[index].include?("Examples:")
|
51
|
+
index += 1
|
52
|
+
until index >= scenario.length
|
53
|
+
@examples_table << scenario[index] unless scenario[index].empty?
|
54
|
+
index += 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def ==(comparison_object)
|
60
|
+
super(comparison_object)
|
61
|
+
comparison_object.steps == steps
|
62
|
+
comparison_object.examples_table == examples_table
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_step_order
|
66
|
+
order = []
|
67
|
+
@steps.each { |line|
|
68
|
+
match = line.match(STEP_REGEX)
|
69
|
+
order << match[:style] unless match.nil?
|
70
|
+
}
|
71
|
+
order
|
72
|
+
end
|
73
|
+
|
74
|
+
def evaluate_score
|
75
|
+
super
|
76
|
+
rule_empty_scenario
|
77
|
+
rule_too_many_steps
|
78
|
+
rule_step_order
|
79
|
+
rule_invalid_first_step
|
80
|
+
rule_asterisk_step
|
81
|
+
rule_commented_step
|
82
|
+
rule_implementation_words
|
83
|
+
rule_date_used_in_step
|
84
|
+
evaluate_outline_scores if type == "Scenario Outline"
|
85
|
+
end
|
86
|
+
|
87
|
+
def evaluate_outline_scores
|
88
|
+
rule_no_examples_table
|
89
|
+
rule_no_examples
|
90
|
+
rule_one_example
|
91
|
+
rule_too_many_examples
|
92
|
+
rule_commented_example
|
93
|
+
end
|
94
|
+
|
95
|
+
def rule_empty_scenario
|
96
|
+
store_rule(SCENARIO_RULES[:no_steps]) if @steps.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
def rule_too_many_steps
|
100
|
+
rule = SCENARIO_RULES[:too_many_steps]
|
101
|
+
store_rule(rule) if @steps.size >= rule[:max]
|
102
|
+
end
|
103
|
+
|
104
|
+
def rule_step_order
|
105
|
+
step_order = get_step_order.uniq
|
106
|
+
%w(But * And).each { |type| step_order.delete(type) }
|
107
|
+
store_rule(SCENARIO_RULES[:out_of_order_steps]) unless step_order == %w(Given When Then) or step_order == %w(When Then)
|
108
|
+
end
|
109
|
+
|
110
|
+
def rule_invalid_first_step
|
111
|
+
first_step = get_step_order.first
|
112
|
+
store_rule(SCENARIO_RULES[:invalid_first_step]) if %w(And But).include?(first_step)
|
113
|
+
end
|
114
|
+
|
115
|
+
def rule_asterisk_step
|
116
|
+
get_step_order.count('*').times { store_rule(SCENARIO_RULES[:asterisk_step]) }
|
117
|
+
end
|
118
|
+
|
119
|
+
def rule_commented_step
|
120
|
+
@steps.each do |step|
|
121
|
+
store_rule(SCENARIO_RULES[:commented_step]) if is_comment?(step)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def rule_implementation_words
|
126
|
+
rule = SHARED_RULES[:implementation_word]
|
127
|
+
@steps.each do |step|
|
128
|
+
rule[:words].each do |word|
|
129
|
+
store_updated_rule(rule, rule[:phrase].gsub(/{.*}/, word)) if step.include?(word)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def rule_date_used_in_step
|
135
|
+
@steps.each do |step|
|
136
|
+
store_rule(SCENARIO_RULES[:date_used]) if step =~ DATE_REGEX
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def rule_no_examples_table
|
141
|
+
store_rule(SCENARIO_RULES[:no_examples_table]) if @examples_table.empty?
|
142
|
+
end
|
143
|
+
|
144
|
+
def rule_no_examples
|
145
|
+
store_rule(SCENARIO_RULES[:no_examples]) if @examples_table.size == 1
|
146
|
+
end
|
147
|
+
|
148
|
+
def rule_one_example
|
149
|
+
store_rule(SCENARIO_RULES[:one_example]) if @examples_table.size == 2 and !is_comment?(@examples_table[1])
|
150
|
+
end
|
151
|
+
|
152
|
+
def rule_too_many_examples
|
153
|
+
store_rule(SCENARIO_RULES[:too_many_examples]) if (@examples_table.size - 1) >= 8
|
154
|
+
end
|
155
|
+
|
156
|
+
def rule_commented_example
|
157
|
+
@examples_table.each do |example|
|
158
|
+
store_rule(SCENARIO_RULES[:commented_example]) if is_comment?(example)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'cuke_sniffer/constants'
|
2
|
+
require 'cuke_sniffer/rule_config'
|
3
|
+
|
4
|
+
module CukeSniffer
|
5
|
+
class StepDefinition < RulesEvaluator
|
6
|
+
include CukeSniffer::Constants
|
7
|
+
include CukeSniffer::RuleConfig
|
8
|
+
|
9
|
+
attr_accessor :start_line, :regex, :code, :parameters, :calls, :nested_steps
|
10
|
+
|
11
|
+
def initialize(location, raw_code)
|
12
|
+
super(location)
|
13
|
+
|
14
|
+
@parameters = []
|
15
|
+
@calls = {}
|
16
|
+
@nested_steps = {}
|
17
|
+
@start_line = location.match(/:(?<line>\d*)/)[:line].to_i
|
18
|
+
|
19
|
+
end_match_index = (raw_code.size - 1) - raw_code.reverse.index("end")
|
20
|
+
@code = raw_code[1...end_match_index]
|
21
|
+
|
22
|
+
raw_code.each do |line|
|
23
|
+
if line =~ STEP_DEFINITION_REGEX
|
24
|
+
matches = STEP_DEFINITION_REGEX.match(line)
|
25
|
+
@regex = Regexp.new(matches[:step])
|
26
|
+
@parameters = matches[:parameters].split(",") unless matches[:parameters].nil?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
detect_nested_steps
|
31
|
+
evaluate_score
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_call(location, step_string)
|
35
|
+
@calls[location] = step_string
|
36
|
+
end
|
37
|
+
|
38
|
+
def detect_nested_steps
|
39
|
+
multi_line_step_flag = false
|
40
|
+
counter = 1
|
41
|
+
@code.each do |line|
|
42
|
+
regex = nil
|
43
|
+
case line
|
44
|
+
when SIMPLE_NESTED_STEP_REGEX
|
45
|
+
regex = SIMPLE_NESTED_STEP_REGEX
|
46
|
+
when SAME_LINE_COMPLEX_STEP_REGEX
|
47
|
+
regex = SAME_LINE_COMPLEX_STEP_REGEX
|
48
|
+
when START_COMPLEX_WITH_STEP_REGEX
|
49
|
+
multi_line_step_flag = true
|
50
|
+
regex = START_COMPLEX_WITH_STEP_REGEX
|
51
|
+
when START_COMPLEX_STEP_REGEX
|
52
|
+
multi_line_step_flag = true
|
53
|
+
when END_COMPLEX_WITH_STEP_REGEX
|
54
|
+
regex = END_COMPLEX_WITH_STEP_REGEX
|
55
|
+
multi_line_step_flag = false
|
56
|
+
when STEP_REGEX
|
57
|
+
regex = STEP_REGEX if multi_line_step_flag
|
58
|
+
when END_COMPLEX_STEP_REGEX
|
59
|
+
multi_line_step_flag = false
|
60
|
+
else
|
61
|
+
end
|
62
|
+
|
63
|
+
if regex
|
64
|
+
match = regex.match(line)
|
65
|
+
nested_step_line = (@start_line + counter)
|
66
|
+
@nested_steps[location.gsub(/:\d*/, ":" + nested_step_line.to_s)] = match[:step_string]
|
67
|
+
end
|
68
|
+
counter += 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ==(comparison_object)
|
73
|
+
super(comparison_object)
|
74
|
+
comparison_object.regex == regex
|
75
|
+
comparison_object.code == code
|
76
|
+
comparison_object.parameters == parameters
|
77
|
+
comparison_object.calls == calls
|
78
|
+
comparison_object.nested_steps == nested_steps
|
79
|
+
end
|
80
|
+
|
81
|
+
def evaluate_score
|
82
|
+
super
|
83
|
+
rule_no_code
|
84
|
+
rule_too_many_parameters
|
85
|
+
rule_nested_steps
|
86
|
+
rule_recursive_nested_step
|
87
|
+
rule_commented_code
|
88
|
+
end
|
89
|
+
|
90
|
+
def rule_no_code
|
91
|
+
store_rule(STEP_DEFINITION_RULES[:no_code]) if code.empty?
|
92
|
+
end
|
93
|
+
|
94
|
+
def rule_too_many_parameters
|
95
|
+
rule = STEP_DEFINITION_RULES[:too_many_parameters]
|
96
|
+
store_rule(rule) if parameters.size >= rule[:max]
|
97
|
+
end
|
98
|
+
|
99
|
+
def rule_nested_steps
|
100
|
+
store_rule(STEP_DEFINITION_RULES[:nested_step]) unless nested_steps.empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
def rule_recursive_nested_step
|
104
|
+
nested_steps.each_value do |nested_step|
|
105
|
+
store_rule(STEP_DEFINITION_RULES[:recursive_nested_step]) if nested_step =~ regex
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def rule_commented_code
|
110
|
+
code.each do |line|
|
111
|
+
store_rule(STEP_DEFINITION_RULES[:commented_code]) if is_comment?(line)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuke_sniffer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Robert Cochran
|
9
|
+
- Chris Vaughn
|
10
|
+
- Robert Anderson
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-01-15 00:00:00.000000000 Z
|
15
|
+
dependencies: []
|
16
|
+
description: A ruby library used to root out smells in your cukes.
|
17
|
+
email:
|
18
|
+
executables:
|
19
|
+
- cuke_sniffer.rb
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- lib/cuke_sniffer.rb
|
24
|
+
- lib/cuke_sniffer/constants.rb
|
25
|
+
- lib/cuke_sniffer/feature.rb
|
26
|
+
- lib/cuke_sniffer/feature_rules_evaluator.rb
|
27
|
+
- lib/cuke_sniffer/rules_evaluator.rb
|
28
|
+
- lib/cuke_sniffer/rule_config.rb
|
29
|
+
- lib/cuke_sniffer/scenario.rb
|
30
|
+
- lib/cuke_sniffer/step_definition.rb
|
31
|
+
- lib/cuke_sniffer/report/markup.rhtml
|
32
|
+
- lib/cuke_sniffer/cli.rb
|
33
|
+
- bin/cuke_sniffer.rb
|
34
|
+
homepage: https://github.com/r-cochran/cuke_sniffer
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.23
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: CukeSniffer
|
58
|
+
test_files: []
|