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
@@ -0,0 +1,62 @@
|
|
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
|
+
# Parent class for all objects that have rules executed against it
|
8
|
+
# Mixins: CukeSniffer::Constants, CukeSniffer::RuleConfig, ROXML
|
9
|
+
class RuleTarget
|
10
|
+
include CukeSniffer::Constants
|
11
|
+
include CukeSniffer::RuleConfig
|
12
|
+
include ROXML
|
13
|
+
|
14
|
+
xml_accessor :score, :location
|
15
|
+
xml_accessor :rules_hash, :as => {:key => "phrase", :value => "score"}, :in => "rules", :from => "rule"
|
16
|
+
|
17
|
+
# int: Sum of the rules fired
|
18
|
+
attr_accessor :score
|
19
|
+
|
20
|
+
# string: Location in which the object was found
|
21
|
+
attr_accessor :location
|
22
|
+
|
23
|
+
# hash: Contains the phrase every rule fired against the object and times it fired
|
24
|
+
# * Key: string
|
25
|
+
# * Value: int
|
26
|
+
attr_accessor :rules_hash
|
27
|
+
|
28
|
+
# Location must be in the format of "file_path\file_name.rb:line_number"
|
29
|
+
def initialize(location)
|
30
|
+
@location = location
|
31
|
+
@score = 0
|
32
|
+
@rules_hash = {}
|
33
|
+
@class_type = self.class.to_s.gsub(/.*::/, "")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Compares the score against the objects threshold
|
37
|
+
# If a score is below the threshold it is good and returns true
|
38
|
+
# Return: Boolean
|
39
|
+
def good?
|
40
|
+
score <= Constants::THRESHOLDS[@class_type]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calculates the score to threshold percentage of an object
|
44
|
+
# Return: Float
|
45
|
+
def problem_percentage
|
46
|
+
score.to_f / Constants::THRESHOLDS[@class_type].to_f
|
47
|
+
end
|
48
|
+
|
49
|
+
def == (comparison_object) # :nodoc:
|
50
|
+
comparison_object.location == location &&
|
51
|
+
comparison_object.score == score &&
|
52
|
+
comparison_object.rules_hash == rules_hash
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
#TODO Abstraction needed for this regex matcher (constants?)
|
58
|
+
def is_comment?(line)
|
59
|
+
true if line =~ /^\#.*$/
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,70 +1,65 @@
|
|
1
|
-
require 'roxml'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
class RulesEvaluator
|
10
|
-
include CukeSniffer::Constants
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def store_rule(rule, phrase = rule
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
def is_comment?(line)
|
67
|
-
true if line =~ /^\#.*$/
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
1
|
+
require 'roxml'
|
2
|
+
|
3
|
+
module CukeSniffer
|
4
|
+
|
5
|
+
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
6
|
+
# Copyright:: Copyright (C) 2013 Robert Cochran
|
7
|
+
# License:: Distributes under the MIT License
|
8
|
+
# Evaluates all cucumber components found in CukeSniffer with the passed rules
|
9
|
+
class RulesEvaluator
|
10
|
+
include CukeSniffer::Constants
|
11
|
+
attr_accessor :rules
|
12
|
+
|
13
|
+
def initialize(cli, rules)
|
14
|
+
raise "A CLI must be provided for evaluation." if cli.nil?
|
15
|
+
raise "Rules must be provided for evaluation." if rules.nil? or rules.empty?
|
16
|
+
@rules = rules
|
17
|
+
judge_features(cli.features)
|
18
|
+
judge_objects(cli.step_definitions, "StepDefinition")
|
19
|
+
judge_objects(cli.hooks, "Hook")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def judge_features(features)
|
25
|
+
features.each do |feature|
|
26
|
+
judge_feature(feature)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def judge_feature(feature)
|
31
|
+
judge_object(feature, "Feature")
|
32
|
+
judge_object(feature.background, "Background") unless feature.background.nil?
|
33
|
+
judge_objects(feature.scenarios, "Scenario")
|
34
|
+
feature.total_score += feature.update_score
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def judge_objects(objects, type)
|
39
|
+
objects.each do | object |
|
40
|
+
judge_object(object, type)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def judge_object(object, type)
|
45
|
+
@rules.each do |rule|
|
46
|
+
fail "No targets for rule: #{rule.phrase}" if rule.targets.nil? or rule.targets.empty?
|
47
|
+
next unless rule.targets.include? type and rule.enabled
|
48
|
+
if eval(rule.reason) == true
|
49
|
+
phrase = rule.phrase.gsub("{class}", type)
|
50
|
+
store_rule(object, rule, phrase)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def store_rule(object, rule, phrase = rule.phrase)
|
56
|
+
object.score += rule.score
|
57
|
+
object.rules_hash[phrase] ||= 0
|
58
|
+
object.rules_hash[phrase] += 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_comment?(line)
|
62
|
+
true if line =~ /^\#.*$/
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,238 +1,102 @@
|
|
1
|
-
module CukeSniffer
|
2
|
-
|
3
|
-
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
4
|
-
# Copyright:: Copyright (C) 2013 Robert Cochran
|
5
|
-
# License:: Distributes under the MIT License
|
6
|
-
# This class is a representation of the cucumber objects
|
7
|
-
# Background, Scenario, Scenario Outline
|
8
|
-
#
|
9
|
-
# Extends CukeSniffer::FeatureRulesEvaluator
|
10
|
-
class Scenario <
|
11
|
-
|
12
|
-
xml_accessor :start_line
|
13
|
-
xml_accessor :steps, :as => [], :in => "steps"
|
14
|
-
xml_accessor :examples_table, :as => [], :in => "examples"
|
15
|
-
|
16
|
-
# int: Line on which the scenario begins
|
17
|
-
attr_accessor :start_line
|
18
|
-
|
19
|
-
# string: The type of scenario
|
20
|
-
# Background, Scenario, Scenario Outline
|
21
|
-
attr_accessor :type
|
22
|
-
|
23
|
-
# string array: List of each step call in a scenario
|
24
|
-
attr_accessor :steps
|
25
|
-
|
26
|
-
# hash: Keeps each location and content of an inline table
|
27
|
-
# * Key: Step string the inline table is attached to
|
28
|
-
# * Value: Array of all of the lines in the table
|
29
|
-
attr_accessor :inline_tables
|
30
|
-
|
31
|
-
# string array: List of each example row in a scenario outline
|
32
|
-
attr_accessor :examples_table
|
33
|
-
|
34
|
-
# Location must be in the format of "file_path\file_name.rb:line_number"
|
35
|
-
# Scenario must be a string array containing everything from the first tag to the last example table
|
36
|
-
# where applicable.
|
37
|
-
def initialize(location, scenario)
|
38
|
-
super(location)
|
39
|
-
@start_line = location.match(/:(?<line>\d*)$/)[:line].to_i
|
40
|
-
@steps = []
|
41
|
-
@inline_tables = {}
|
42
|
-
@examples_table = []
|
43
|
-
split_scenario(scenario)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
comparison_object.
|
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
|
-
index += 1
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
def evaluate_score
|
104
|
-
if type == "Background"
|
105
|
-
rule_numbers_in_name(type)
|
106
|
-
rule_long_name(type)
|
107
|
-
rule_tagged_background(type)
|
108
|
-
else
|
109
|
-
super
|
110
|
-
rule_step_order
|
111
|
-
end
|
112
|
-
|
113
|
-
rule_empty_scenario
|
114
|
-
rule_too_many_steps
|
115
|
-
rule_invalid_first_step
|
116
|
-
rule_asterisk_step
|
117
|
-
rule_commented_step
|
118
|
-
rule_implementation_words
|
119
|
-
rule_date_used_in_step
|
120
|
-
rule_one_word_step
|
121
|
-
rule_multiple_given_when_then
|
122
|
-
evaluate_outline_scores if type == "Scenario Outline"
|
123
|
-
end
|
124
|
-
|
125
|
-
def evaluate_outline_scores
|
126
|
-
rule_no_examples_table
|
127
|
-
rule_no_examples
|
128
|
-
rule_one_example
|
129
|
-
rule_too_many_examples
|
130
|
-
rule_commented_example
|
131
|
-
end
|
132
|
-
|
133
|
-
def rule_multiple_given_when_then
|
134
|
-
step_order = get_step_order
|
135
|
-
rule = RULES[:multiple_given_when_then]
|
136
|
-
phrase = rule[:phrase].gsub(/{.*}/, type)
|
137
|
-
["Given", "When", "Then"].each { |step| store_rule(rule, phrase) if step_order.count(step) > 1 }
|
138
|
-
end
|
139
|
-
|
140
|
-
def rule_one_word_step
|
141
|
-
@steps.each do |step|
|
142
|
-
rule = RULES[:one_word_step]
|
143
|
-
store_rule(rule) if step.split.count == 2
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def rule_step_order
|
148
|
-
step_order = get_step_order.uniq
|
149
|
-
["But", "*", "And"].each { |type| step_order.delete(type) }
|
150
|
-
rule = RULES[:out_of_order_steps]
|
151
|
-
store_rule(rule) unless step_order == %w(Given When Then) or step_order == %w(When Then)
|
152
|
-
end
|
153
|
-
|
154
|
-
def rule_asterisk_step
|
155
|
-
get_step_order.count('*').times do
|
156
|
-
rule = RULES[:asterisk_step]
|
157
|
-
store_rule(rule)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def rule_commented_step
|
162
|
-
@steps.each do |step|
|
163
|
-
rule = RULES[:commented_step]
|
164
|
-
store_rule(rule) if is_comment?(step)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def rule_date_used_in_step
|
169
|
-
@steps.each do |step|
|
170
|
-
rule = RULES[:date_used]
|
171
|
-
store_rule(rule) if step =~ DATE_REGEX
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def rule_no_examples_table
|
176
|
-
rule = RULES[:no_examples_table]
|
177
|
-
store_rule(rule) if @examples_table.empty?
|
178
|
-
end
|
179
|
-
|
180
|
-
def rule_no_examples
|
181
|
-
rule = RULES[:no_examples]
|
182
|
-
store_rule(rule) if @examples_table.size == 1
|
183
|
-
end
|
184
|
-
|
185
|
-
def rule_one_example
|
186
|
-
rule = RULES[:one_example]
|
187
|
-
store_rule(rule) if @examples_table.size == 2 and !is_comment?(@examples_table[1])
|
188
|
-
end
|
189
|
-
|
190
|
-
def rule_too_many_examples
|
191
|
-
rule = RULES[:too_many_examples]
|
192
|
-
store_rule(rule) if (@examples_table.size - 1) >= 8
|
193
|
-
end
|
194
|
-
|
195
|
-
def rule_commented_example
|
196
|
-
@examples_table.each do |example|
|
197
|
-
rule = RULES[:commented_example]
|
198
|
-
store_rule(rule) if is_comment?(example)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def rule_implementation_words
|
203
|
-
rule = RULES[:implementation_word]
|
204
|
-
@steps.each do |step|
|
205
|
-
next if is_comment?(step)
|
206
|
-
rule[:words].each do |word|
|
207
|
-
rule_phrase = rule[:phrase].gsub(/{.*}/, word)
|
208
|
-
store_rule(rule, rule_phrase) if step.include?(word)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def rule_tagged_background(type)
|
214
|
-
rule = RULES[:background_with_tag]
|
215
|
-
rule_phrase = rule[:phrase].gsub(/{.*}/, type)
|
216
|
-
store_rule(rule, rule_phrase) if tags.size > 0
|
217
|
-
end
|
218
|
-
|
219
|
-
def rule_invalid_first_step
|
220
|
-
first_step = get_step_order.first
|
221
|
-
rule = RULES[:invalid_first_step]
|
222
|
-
rule_phrase = rule[:phrase].gsub(/{.*}/, type)
|
223
|
-
store_rule(rule, rule_phrase) if %w(And But).include?(first_step)
|
224
|
-
end
|
225
|
-
|
226
|
-
def rule_empty_scenario
|
227
|
-
rule = RULES[:no_steps]
|
228
|
-
rule_phrase = rule[:phrase].gsub(/{.*}/, type)
|
229
|
-
store_rule(rule, rule_phrase) if @steps.empty?
|
230
|
-
end
|
231
|
-
|
232
|
-
def rule_too_many_steps
|
233
|
-
rule = RULES[:too_many_steps]
|
234
|
-
rule_phrase = rule[:phrase].gsub(/{.*}/, type)
|
235
|
-
store_rule(rule, rule_phrase) if @steps.size >= rule[:max]
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
1
|
+
module CukeSniffer
|
2
|
+
|
3
|
+
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
4
|
+
# Copyright:: Copyright (C) 2013 Robert Cochran
|
5
|
+
# License:: Distributes under the MIT License
|
6
|
+
# This class is a representation of the cucumber objects
|
7
|
+
# Background, Scenario, Scenario Outline
|
8
|
+
#
|
9
|
+
# Extends CukeSniffer::FeatureRulesEvaluator
|
10
|
+
class Scenario < FeatureRuleTarget
|
11
|
+
|
12
|
+
xml_accessor :start_line
|
13
|
+
xml_accessor :steps, :as => [], :in => "steps"
|
14
|
+
xml_accessor :examples_table, :as => [], :in => "examples"
|
15
|
+
|
16
|
+
# int: Line on which the scenario begins
|
17
|
+
attr_accessor :start_line
|
18
|
+
|
19
|
+
# string: The type of scenario
|
20
|
+
# Background, Scenario, Scenario Outline
|
21
|
+
attr_accessor :type
|
22
|
+
|
23
|
+
# string array: List of each step call in a scenario
|
24
|
+
attr_accessor :steps
|
25
|
+
|
26
|
+
# hash: Keeps each location and content of an inline table
|
27
|
+
# * Key: Step string the inline table is attached to
|
28
|
+
# * Value: Array of all of the lines in the table
|
29
|
+
attr_accessor :inline_tables
|
30
|
+
|
31
|
+
# string array: List of each example row in a scenario outline
|
32
|
+
attr_accessor :examples_table
|
33
|
+
|
34
|
+
# Location must be in the format of "file_path\file_name.rb:line_number"
|
35
|
+
# Scenario must be a string array containing everything from the first tag to the last example table
|
36
|
+
# where applicable.
|
37
|
+
def initialize(location, scenario)
|
38
|
+
super(location)
|
39
|
+
@start_line = location.match(/:(?<line>\d*)$/)[:line].to_i
|
40
|
+
@steps = []
|
41
|
+
@inline_tables = {}
|
42
|
+
@examples_table = []
|
43
|
+
split_scenario(scenario)
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(comparison_object) # :nodoc:
|
47
|
+
super(comparison_object) &&
|
48
|
+
comparison_object.steps == steps &&
|
49
|
+
comparison_object.examples_table == examples_table
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_step_order
|
53
|
+
order = []
|
54
|
+
@steps.each do |line|
|
55
|
+
next if is_comment?(line)
|
56
|
+
match = line.match(STEP_REGEX)
|
57
|
+
order << match[:style] unless match.nil?
|
58
|
+
end
|
59
|
+
order
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def split_scenario(scenario)
|
65
|
+
index = 0
|
66
|
+
until index >= scenario.length or scenario[index] =~ SCENARIO_TITLE_STYLES
|
67
|
+
update_tag_list(scenario[index])
|
68
|
+
index += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
until index >= scenario.length or scenario[index].match STEP_REGEX or scenario[index].include?("Examples:")
|
72
|
+
match = scenario[index].match(SCENARIO_TITLE_STYLES)
|
73
|
+
@type = match[:type] unless match.nil?
|
74
|
+
create_name(scenario[index], SCENARIO_TITLE_STYLES)
|
75
|
+
index += 1
|
76
|
+
end
|
77
|
+
|
78
|
+
until index >= scenario.length or scenario[index].include?("Examples:")
|
79
|
+
if scenario[index] =~ /^\|.*\|/
|
80
|
+
step = scenario[index - 1]
|
81
|
+
@inline_tables[step] = []
|
82
|
+
until index >= scenario.length or scenario[index] =~ /(#{STEP_REGEX}|^\s*Examples:)/
|
83
|
+
@inline_tables[step] << scenario[index]
|
84
|
+
index += 1
|
85
|
+
end
|
86
|
+
else
|
87
|
+
@steps << scenario[index] if scenario[index] =~ STEP_REGEX
|
88
|
+
index += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if index < scenario.length and scenario[index].include?("Examples:")
|
93
|
+
index += 1
|
94
|
+
until index >= scenario.length
|
95
|
+
index += 2 if scenario[index].include?("Examples:")
|
96
|
+
@examples_table << scenario[index] if scenario[index] =~ /#{COMMENT_REGEX}\|.*\|/
|
97
|
+
index += 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|