cuke_sniffer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|