cuke_sniffer 0.0.5 → 0.0.6
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 +85 -63
- data/lib/cuke_sniffer.rb +23 -16
- data/lib/cuke_sniffer/cli.rb +269 -523
- data/lib/cuke_sniffer/constants.rb +5 -2
- data/lib/cuke_sniffer/cuke_sniffer_helper.rb +165 -0
- data/lib/cuke_sniffer/dead_steps_helper.rb +54 -0
- data/lib/cuke_sniffer/feature.rb +13 -81
- data/lib/cuke_sniffer/feature_rules_evaluator.rb +56 -115
- data/lib/cuke_sniffer/formatter.rb +142 -0
- data/lib/cuke_sniffer/hook.rb +77 -160
- data/lib/cuke_sniffer/report/dead_steps.html.erb +36 -0
- data/lib/cuke_sniffer/report/features.html.erb +60 -0
- data/lib/cuke_sniffer/report/hooks.html.erb +49 -0
- data/lib/cuke_sniffer/report/improvement_list.html.erb +18 -0
- data/lib/cuke_sniffer/report/legend.html.erb +167 -0
- data/lib/cuke_sniffer/report/min_template.html.erb +125 -0
- data/lib/cuke_sniffer/report/rules.html.erb +9 -0
- data/lib/cuke_sniffer/report/standard_template.html.erb +137 -0
- data/lib/cuke_sniffer/report/step_definitions.html.erb +49 -0
- data/lib/cuke_sniffer/report/sub_rules.html.erb +24 -0
- data/lib/cuke_sniffer/report/summary.html.erb +71 -0
- data/lib/cuke_sniffer/rule.rb +28 -0
- data/lib/cuke_sniffer/rule_config.rb +241 -48
- data/lib/cuke_sniffer/rule_target.rb +62 -0
- data/lib/cuke_sniffer/rules_evaluator.rb +65 -70
- data/lib/cuke_sniffer/scenario.rb +102 -238
- data/lib/cuke_sniffer/step_definition.rb +163 -239
- data/lib/cuke_sniffer/summary_helper.rb +91 -0
- data/lib/cuke_sniffer/summary_node.rb +19 -0
- metadata +23 -5
- data/lib/cuke_sniffer/report/markup.rhtml +0 -353
@@ -1,239 +1,163 @@
|
|
1
|
-
require 'roxml'
|
2
|
-
module CukeSniffer
|
3
|
-
|
4
|
-
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
5
|
-
# Copyright:: Copyright (C) 2013 Robert Cochran
|
6
|
-
# License:: Distributes under the MIT License
|
7
|
-
# Translates and evaluates Cucumber step definitions
|
8
|
-
# Extends CukeSniffer::RulesEvaluator
|
9
|
-
class StepDefinition <
|
10
|
-
|
11
|
-
xml_accessor :start_line
|
12
|
-
xml_accessor :regex
|
13
|
-
xml_accessor :parameters, :as => [], :in => "parameters"
|
14
|
-
xml_accessor :nested_steps, :as => {:key => 'location', :value => 'call'}, :in => "nested_steps"
|
15
|
-
xml_accessor :calls, :as => {:key => 'location', :value => 'call'}, :in => "calls"
|
16
|
-
xml_accessor :code, :as => [], :in => "code"
|
17
|
-
|
18
|
-
# int: Line on which a step definition starts
|
19
|
-
attr_accessor :start_line
|
20
|
-
|
21
|
-
# Regex: Regex that cucumber uses to match step calls
|
22
|
-
attr_accessor :regex
|
23
|
-
|
24
|
-
# string array: List of the parameters a step definition has
|
25
|
-
attr_accessor :parameters
|
26
|
-
|
27
|
-
# hash: Contains each nested step call a step definition has
|
28
|
-
# * Key: location:line of the nested step
|
29
|
-
# * Value: The step call that appears on the line
|
30
|
-
attr_accessor :nested_steps
|
31
|
-
|
32
|
-
# hash: Contains each call that is made to a step definition
|
33
|
-
# * Key: Location in which the step definition is called from
|
34
|
-
# * Value: The step string that matched the regex
|
35
|
-
# In the case of a fuzzy match it will be a regex of the
|
36
|
-
# step call that was the inverse match of the regex translated
|
37
|
-
# into a string.
|
38
|
-
attr_accessor :calls
|
39
|
-
|
40
|
-
# string array: List of all of the content between the regex and the end of the step definition.
|
41
|
-
attr_accessor :code
|
42
|
-
|
43
|
-
# location must be in the format of "file_path\file_name.rb:line_number"
|
44
|
-
#
|
45
|
-
# must contain the regex line and its pairing end
|
46
|
-
def initialize(location,
|
47
|
-
|
48
|
-
|
49
|
-
@
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
|
166
|
-
def rule_todo
|
167
|
-
code.each do |line|
|
168
|
-
rule = RULES[:todo]
|
169
|
-
store_rule(rule) if line =~ /\#(TODO|todo)/
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def sleep_rules
|
174
|
-
code.each do |line|
|
175
|
-
match_data = line.match /^\s*sleep(\s|\()(?<sleep_time>.*)\)?/
|
176
|
-
if match_data
|
177
|
-
sleep_value = match_data[:sleep_time].to_f
|
178
|
-
rule_small_sleep(sleep_value)
|
179
|
-
rule_large_sleep(sleep_value)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def rule_large_sleep(sleep_value)
|
185
|
-
rule = RULES[:large_sleep]
|
186
|
-
store_rule(rule) if sleep_value > rule[:min]
|
187
|
-
end
|
188
|
-
|
189
|
-
def rule_small_sleep(sleep_value)
|
190
|
-
rule = RULES[:small_sleep]
|
191
|
-
store_rule(rule) if sleep_value <= rule[:max]
|
192
|
-
end
|
193
|
-
|
194
|
-
def rule_pending
|
195
|
-
rule = RULES[:pending]
|
196
|
-
code.each do |line|
|
197
|
-
store_rule(rule) if line =~ /^\s*pending\s*$/
|
198
|
-
return
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def rule_lazy_debugging
|
203
|
-
rule = RULES[:lazy_debugging]
|
204
|
-
code.each do |line|
|
205
|
-
next if is_comment?(line)
|
206
|
-
store_rule(rule) if line.strip =~ /^(p|puts)( |\()('|"|%(q|Q)?\{)/
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def rule_no_code
|
211
|
-
rule = RULES[:no_code]
|
212
|
-
store_rule(rule) if code.empty?
|
213
|
-
end
|
214
|
-
|
215
|
-
def rule_too_many_parameters
|
216
|
-
rule = RULES[:too_many_parameters]
|
217
|
-
store_rule(rule) if parameters.size >= rule[:max]
|
218
|
-
end
|
219
|
-
|
220
|
-
def rule_nested_steps
|
221
|
-
rule = RULES[:nested_step]
|
222
|
-
store_rule(rule) unless nested_steps.empty?
|
223
|
-
end
|
224
|
-
|
225
|
-
def rule_recursive_nested_step
|
226
|
-
rule = RULES[:recursive_nested_step]
|
227
|
-
nested_steps.each_value do |nested_step|
|
228
|
-
store_rule(rule) if nested_step =~ regex
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def rule_commented_code
|
233
|
-
rule = RULES[:commented_code]
|
234
|
-
code.each do |line|
|
235
|
-
store_rule(rule) if is_comment?(line)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
1
|
+
require 'roxml'
|
2
|
+
module CukeSniffer
|
3
|
+
|
4
|
+
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
5
|
+
# Copyright:: Copyright (C) 2013 Robert Cochran
|
6
|
+
# License:: Distributes under the MIT License
|
7
|
+
# Translates and evaluates Cucumber step definitions
|
8
|
+
# Extends CukeSniffer::RulesEvaluator
|
9
|
+
class StepDefinition < RuleTarget
|
10
|
+
|
11
|
+
xml_accessor :start_line
|
12
|
+
xml_accessor :regex
|
13
|
+
xml_accessor :parameters, :as => [], :in => "parameters"
|
14
|
+
xml_accessor :nested_steps, :as => {:key => 'location', :value => 'call'}, :in => "nested_steps"
|
15
|
+
xml_accessor :calls, :as => {:key => 'location', :value => 'call'}, :in => "calls"
|
16
|
+
xml_accessor :code, :as => [], :in => "code"
|
17
|
+
|
18
|
+
# int: Line on which a step definition starts
|
19
|
+
attr_accessor :start_line
|
20
|
+
|
21
|
+
# Regex: Regex that cucumber uses to match step calls
|
22
|
+
attr_accessor :regex
|
23
|
+
|
24
|
+
# string array: List of the parameters a step definition has
|
25
|
+
attr_accessor :parameters
|
26
|
+
|
27
|
+
# hash: Contains each nested step call a step definition has
|
28
|
+
# * Key: location:line of the nested step
|
29
|
+
# * Value: The step call that appears on the line
|
30
|
+
attr_accessor :nested_steps
|
31
|
+
|
32
|
+
# hash: Contains each call that is made to a step definition
|
33
|
+
# * Key: Location in which the step definition is called from
|
34
|
+
# * Value: The step string that matched the regex
|
35
|
+
# In the case of a fuzzy match it will be a regex of the
|
36
|
+
# step call that was the inverse match of the regex translated
|
37
|
+
# into a string.
|
38
|
+
attr_accessor :calls
|
39
|
+
|
40
|
+
# string array: List of all of the content between the regex and the end of the step definition.
|
41
|
+
attr_accessor :code
|
42
|
+
|
43
|
+
# location must be in the format of "file_path\file_name.rb:line_number"
|
44
|
+
# step_definition_block is an array of strings that represents the step definition
|
45
|
+
# must contain the regex line and its pairing end
|
46
|
+
def initialize(location, step_definition_block)
|
47
|
+
@parameters = []
|
48
|
+
@calls = {}
|
49
|
+
@nested_steps = {}
|
50
|
+
super(location)
|
51
|
+
extract_start_line(location)
|
52
|
+
extract_code(step_definition_block)
|
53
|
+
extract_step_definition_signature(step_definition_block)
|
54
|
+
|
55
|
+
detect_nested_steps
|
56
|
+
end
|
57
|
+
|
58
|
+
# Adds new location => step_string pairs to the calls hash
|
59
|
+
def add_call(location, step_string)
|
60
|
+
@calls[location] = step_string
|
61
|
+
end
|
62
|
+
|
63
|
+
def ==(comparison_object) # :nodoc:
|
64
|
+
comparison_object.regex == regex && comparison_object.parameters == parameters
|
65
|
+
end
|
66
|
+
|
67
|
+
def condensed_call_list
|
68
|
+
condensed_list = {}
|
69
|
+
@calls.each do |call, step_string|
|
70
|
+
condensed_list[step_string] ||= []
|
71
|
+
condensed_list[step_string] << call
|
72
|
+
end
|
73
|
+
condensed_list
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
SIMPLE_NESTED_STEP_REGEX = /steps?\s"#{STEP_STYLES}(?<step_string>.*)"$/ # :nodoc:
|
79
|
+
START_COMPLEX_STEP_REGEX = /^steps?\s%(q|Q)?\{\s*/ # :nodoc:
|
80
|
+
SAME_LINE_COMPLEX_STEP_REGEX = /#{START_COMPLEX_STEP_REGEX}#{STEP_STYLES}(?<step_string>.*)}$/ # :nodoc:
|
81
|
+
END_COMPLEX_STEP_REGEX = /}$/ # :nodoc:
|
82
|
+
START_COMPLEX_WITH_STEP_REGEX = /#{START_COMPLEX_STEP_REGEX}#{STEP_STYLES}(?<step_string>.*)$/ # :nodoc:
|
83
|
+
END_COMPLEX_WITH_STEP_REGEX = /#{STEP_STYLES}(?<step_string>.*)}$/ # :nodoc:
|
84
|
+
|
85
|
+
def detect_nested_steps
|
86
|
+
multi_line_step_flag = false
|
87
|
+
counter = 1
|
88
|
+
@code.each do |line|
|
89
|
+
regex = nil
|
90
|
+
case line
|
91
|
+
when SIMPLE_NESTED_STEP_REGEX
|
92
|
+
regex = SIMPLE_NESTED_STEP_REGEX
|
93
|
+
when SAME_LINE_COMPLEX_STEP_REGEX
|
94
|
+
regex = SAME_LINE_COMPLEX_STEP_REGEX
|
95
|
+
when START_COMPLEX_WITH_STEP_REGEX
|
96
|
+
if line =~ /\}$/
|
97
|
+
if line.include?('#{')
|
98
|
+
reversed_line = line.reverse
|
99
|
+
last_capture = reversed_line[0..reversed_line.index('#')].reverse
|
100
|
+
if last_capture =~ /{.*}$/
|
101
|
+
multi_line_step_flag = true
|
102
|
+
regex = START_COMPLEX_WITH_STEP_REGEX
|
103
|
+
else
|
104
|
+
regex = SAME_LINE_COMPLEX_STEP_REGEX
|
105
|
+
end
|
106
|
+
else
|
107
|
+
regex = SAME_LINE_COMPLEX_STEP_REGEX
|
108
|
+
end
|
109
|
+
else
|
110
|
+
multi_line_step_flag = true
|
111
|
+
regex = START_COMPLEX_WITH_STEP_REGEX
|
112
|
+
end
|
113
|
+
when END_COMPLEX_WITH_STEP_REGEX
|
114
|
+
if line =~ /[#]{.*}$/ && multi_line_step_flag
|
115
|
+
regex = STEP_REGEX
|
116
|
+
else
|
117
|
+
regex = END_COMPLEX_WITH_STEP_REGEX
|
118
|
+
multi_line_step_flag = false
|
119
|
+
end
|
120
|
+
when START_COMPLEX_STEP_REGEX
|
121
|
+
multi_line_step_flag = true
|
122
|
+
when STEP_REGEX
|
123
|
+
regex = STEP_REGEX if multi_line_step_flag
|
124
|
+
when END_COMPLEX_STEP_REGEX
|
125
|
+
multi_line_step_flag = false
|
126
|
+
else
|
127
|
+
end
|
128
|
+
|
129
|
+
if regex and !is_comment?(line)
|
130
|
+
match = regex.match(line)
|
131
|
+
nested_step_line = (@start_line + counter)
|
132
|
+
@nested_steps[location.gsub(/:\d*$/, ":" + nested_step_line.to_s)] = match[:step_string].gsub("\\", "")
|
133
|
+
end
|
134
|
+
counter += 1
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def extract_step_definition_signature(step_definition_block)
|
139
|
+
regex_line = find_regex_line(step_definition_block)
|
140
|
+
unless regex_line.nil?
|
141
|
+
matches = STEP_DEFINITION_REGEX.match(regex_line)
|
142
|
+
@regex = Regexp.new(matches[:step])
|
143
|
+
@parameters = matches[:parameters].split(/,\s*/).collect { |param| param.strip } if matches[:parameters]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def extract_start_line(location)
|
148
|
+
@start_line = location.match(/:(?<line>\d*)$/)[:line].to_i
|
149
|
+
end
|
150
|
+
|
151
|
+
def extract_code(step_definition_block)
|
152
|
+
end_match_index = (step_definition_block.size - 1) - step_definition_block.reverse.index("end")
|
153
|
+
@code = step_definition_block[1...end_match_index]
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_regex_line(step_definition_block)
|
157
|
+
step_definition_block.each do |line|
|
158
|
+
return line if line =~ STEP_DEFINITION_REGEX
|
159
|
+
end
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module CukeSniffer
|
2
|
+
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
3
|
+
# Copyright:: Copyright (C) 2013 Robert Cochran
|
4
|
+
# License:: Distributes under the MIT License
|
5
|
+
# Mixins: CukeSniffer::Constants
|
6
|
+
# A static class used to help with handling summary data for CukeSniffer::CLI
|
7
|
+
class SummaryHelper
|
8
|
+
include CukeSniffer::Constants
|
9
|
+
|
10
|
+
# Sorts the list of improvements in descending order of times found.
|
11
|
+
def self.sort_improvement_list(improvement_list)
|
12
|
+
sorted_array = improvement_list.sort_by { |improvement, occurrence| occurrence }.reverse
|
13
|
+
sorted_improvement_list = {}
|
14
|
+
sorted_array.each { |node|
|
15
|
+
sorted_improvement_list[node[0]] = node[1]
|
16
|
+
}
|
17
|
+
sorted_improvement_list
|
18
|
+
end
|
19
|
+
|
20
|
+
# Builds a default assessment hash.
|
21
|
+
def self.make_assessment_hash
|
22
|
+
{
|
23
|
+
:total => 0,
|
24
|
+
:total_score => 0,
|
25
|
+
:min => nil,
|
26
|
+
:min_file => nil,
|
27
|
+
:max => nil,
|
28
|
+
:max_file => nil,
|
29
|
+
:average => 0,
|
30
|
+
:threshold => nil,
|
31
|
+
:good => 0,
|
32
|
+
:bad => 0,
|
33
|
+
:improvement_list => {}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Initializes an assessment hash for the rule target list
|
38
|
+
def self.initialize_assessment_hash(rule_target_list, type)
|
39
|
+
assessment_hash = make_assessment_hash
|
40
|
+
assessment_hash[:total] = rule_target_list.count
|
41
|
+
assessment_hash[:threshold] = THRESHOLDS[type]
|
42
|
+
|
43
|
+
unless rule_target_list.empty?
|
44
|
+
score = rule_target_list.first.score
|
45
|
+
location = rule_target_list.first.location
|
46
|
+
assessment_hash[:min] = score
|
47
|
+
assessment_hash[:min_file] = location
|
48
|
+
assessment_hash[:max] = score
|
49
|
+
assessment_hash[:max_file] = location
|
50
|
+
end
|
51
|
+
assessment_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a summary hash for the rule_target_list
|
55
|
+
def self.assess_rule_target_list(rule_target_list, type)
|
56
|
+
assessment_hash = initialize_assessment_hash(rule_target_list, type)
|
57
|
+
rule_target_list.each do |rule_target|
|
58
|
+
score = rule_target.score
|
59
|
+
assessment_hash[:total_score] += score
|
60
|
+
assessment_hash[rule_target.good? ? :good : :bad] += 1
|
61
|
+
if score < assessment_hash[:min]
|
62
|
+
assessment_hash[:min] = score
|
63
|
+
assessment_hash[:min_file] = rule_target.location
|
64
|
+
end
|
65
|
+
if score > assessment_hash[:max]
|
66
|
+
assessment_hash[:max] = score
|
67
|
+
assessment_hash[:max_file] = rule_target.location
|
68
|
+
end
|
69
|
+
rule_target.rules_hash.each_key do |key|
|
70
|
+
assessment_hash[:improvement_list][key] ||= 0
|
71
|
+
assessment_hash[:improvement_list][key] += rule_target.rules_hash[key]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
assessment_hash[:average] = (assessment_hash[:total_score].to_f/rule_target_list.count.to_f).round(2)
|
75
|
+
assessment_hash
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a CukeSniffer::SummaryNode object for the passed hash
|
79
|
+
def self.load_summary_data(summary_hash)
|
80
|
+
summary_node = SummaryNode.new
|
81
|
+
summary_node.count = summary_hash[:total]
|
82
|
+
summary_node.score = summary_hash[:total_score]
|
83
|
+
summary_node.average = summary_hash[:average]
|
84
|
+
summary_node.threshold = summary_hash[:threshold]
|
85
|
+
summary_node.good = summary_hash[:good]
|
86
|
+
summary_node.bad = summary_hash[:bad]
|
87
|
+
summary_node
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|