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
data/bin/cuke_sniffer
CHANGED
@@ -1,63 +1,85 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
|
-
require 'cuke_sniffer'
|
5
|
-
|
6
|
-
help_cmd_txt = "Welcome to CukeSniffer!
|
7
|
-
Calling CukeSniffer with no arguments will run it against the current directory.
|
8
|
-
Other Options for Running include:
|
9
|
-
|
10
|
-
|
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
|
-
if
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
file_name =
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'cuke_sniffer'
|
5
|
+
|
6
|
+
help_cmd_txt = "Welcome to CukeSniffer!
|
7
|
+
Calling CukeSniffer with no arguments will run it against the current directory.
|
8
|
+
Other Options for Running include:
|
9
|
+
-o, --out <type> (name) : Where <type> is 'html', 'min_html' or 'xml'.
|
10
|
+
Runs CukeSniffer then outputs an
|
11
|
+
html/xml file in the current
|
12
|
+
directory (with optional name).
|
13
|
+
-h, --help : You get this lovely document.
|
14
|
+
-p, --project <project_root> : Root directory of project.
|
15
|
+
-f, --features <feature_path> : Path to features directory.
|
16
|
+
-s, --step_definitions <step_def_path> : Path to step definitions directory.
|
17
|
+
-hk, --hooks <hooks_path> : Path to support directory. "
|
18
|
+
|
19
|
+
def find_index_by_regex_match(list, regex)
|
20
|
+
list.each do |item|
|
21
|
+
return list.index(item) if item =~ regex
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_cli_hash(argv)
|
27
|
+
cucumber_hash ={
|
28
|
+
:hooks_location => /-hk|--hooks/,
|
29
|
+
:features_location => /-f|--features/,
|
30
|
+
:step_definitions_location => /-s|--step_definitions/,
|
31
|
+
:project_location => /-p|--project/}
|
32
|
+
cli_hash = {}
|
33
|
+
cucumber_hash.each do |symbol, regex|
|
34
|
+
index_of_key = find_index_by_regex_match(argv, regex)
|
35
|
+
if !index_of_key.nil?
|
36
|
+
cli_hash[symbol] = argv[index_of_key + 1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
cli_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_output(argv)
|
43
|
+
if find_index_by_regex_match(argv, /-o|--out/)
|
44
|
+
output_type_hash = {
|
45
|
+
:html => "html",
|
46
|
+
:min_html => "min_html",
|
47
|
+
:xml => "xml",
|
48
|
+
}
|
49
|
+
output_type_hash.each_value do |value|
|
50
|
+
index_of_key = argv.index(value)
|
51
|
+
if !index_of_key.nil?
|
52
|
+
file_type = argv[index_of_key]
|
53
|
+
file_name = argv[index_of_key+1]
|
54
|
+
call_output(file_type, file_name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
puts @cuke_sniffer.output_results
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def call_output(file_type, file_name)
|
63
|
+
case file_type
|
64
|
+
when "xml"
|
65
|
+
file_name.nil? ? @cuke_sniffer.output_xml : @cuke_sniffer.output_xml(file_name)
|
66
|
+
when "html"
|
67
|
+
file_name.nil? ? @cuke_sniffer.output_html : @cuke_sniffer.output_html(file_name)
|
68
|
+
when "min_html"
|
69
|
+
file_name.nil? ? @cuke_sniffer.output_min_html : @cuke_sniffer.output_min_html(file_name)
|
70
|
+
else
|
71
|
+
puts "#{file_type} is not a supported version of cuke_sniffer output."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if ARGV.include? "-h" or ARGV.include? "--help"
|
76
|
+
puts help_cmd_txt
|
77
|
+
exit
|
78
|
+
end
|
79
|
+
|
80
|
+
arguments_hash = build_cli_hash(ARGV)
|
81
|
+
@cuke_sniffer = CukeSniffer::CLI.new(arguments_hash)
|
82
|
+
handle_output(ARGV)
|
83
|
+
|
84
|
+
|
85
|
+
puts "Completed Sniffing."
|
data/lib/cuke_sniffer.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
require 'roxml'
|
3
|
-
|
4
|
-
require 'cuke_sniffer/constants'
|
5
|
-
require 'cuke_sniffer/rule_config'
|
6
|
-
require 'cuke_sniffer/
|
7
|
-
require 'cuke_sniffer/feature_rules_evaluator'
|
8
|
-
require 'cuke_sniffer/feature'
|
9
|
-
require 'cuke_sniffer/scenario'
|
10
|
-
require 'cuke_sniffer/step_definition'
|
11
|
-
require 'cuke_sniffer/hook'
|
12
|
-
require 'cuke_sniffer/
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'roxml'
|
3
|
+
|
4
|
+
require 'cuke_sniffer/constants'
|
5
|
+
require 'cuke_sniffer/rule_config'
|
6
|
+
require 'cuke_sniffer/rule_target'
|
7
|
+
require 'cuke_sniffer/feature_rules_evaluator'
|
8
|
+
require 'cuke_sniffer/feature'
|
9
|
+
require 'cuke_sniffer/scenario'
|
10
|
+
require 'cuke_sniffer/step_definition'
|
11
|
+
require 'cuke_sniffer/hook'
|
12
|
+
require 'cuke_sniffer/rule'
|
13
|
+
require 'cuke_sniffer/summary_node'
|
14
|
+
require 'cuke_sniffer/rules_evaluator'
|
15
|
+
require 'cuke_sniffer/cuke_sniffer_helper'
|
16
|
+
require 'cuke_sniffer/summary_helper'
|
17
|
+
require 'cuke_sniffer/dead_steps_helper'
|
18
|
+
require 'cuke_sniffer/cli'
|
19
|
+
require 'cuke_sniffer/formatter'
|
20
|
+
|
21
|
+
module CukeSniffer
|
22
|
+
|
23
|
+
end
|
data/lib/cuke_sniffer/cli.rb
CHANGED
@@ -1,523 +1,269 @@
|
|
1
|
-
require 'erb'
|
2
|
-
require 'roxml'
|
3
|
-
|
4
|
-
module CukeSniffer
|
5
|
-
|
6
|
-
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
7
|
-
# Copyright:: Copyright (C) 2013 Robert Cochran
|
8
|
-
# License:: Distributes under the MIT License
|
9
|
-
# Mixins: CukeSniffer::Constants, ROXML
|
10
|
-
class CLI
|
11
|
-
include CukeSniffer::Constants
|
12
|
-
include
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
attr_accessor :
|
37
|
-
|
38
|
-
#
|
39
|
-
attr_accessor :
|
40
|
-
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# cuke_sniffer = CukeSniffer::CLI.new
|
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
|
-
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
end
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
end
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
end
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
@summary[:hooks] = assess_array(@hooks, "Hook")
|
271
|
-
sort_improvement_list
|
272
|
-
end
|
273
|
-
|
274
|
-
def get_all_steps
|
275
|
-
feature_steps = extract_steps_from_features
|
276
|
-
step_definition_steps = extract_steps_from_step_definitions
|
277
|
-
feature_steps.merge step_definition_steps
|
278
|
-
end
|
279
|
-
|
280
|
-
def get_steps_with_expressions(steps)
|
281
|
-
steps_with_expressions = {}
|
282
|
-
steps.each do |step_location, step_value|
|
283
|
-
if step_value =~ /\#{.*}/
|
284
|
-
steps_with_expressions[step_location] = step_value
|
285
|
-
end
|
286
|
-
end
|
287
|
-
steps_with_expressions
|
288
|
-
end
|
289
|
-
|
290
|
-
def catalog_possible_dead_steps(steps_with_expressions)
|
291
|
-
@step_definitions.each do |step_definition|
|
292
|
-
next unless step_definition.calls.empty?
|
293
|
-
regex_as_string = step_definition.regex.to_s.gsub(/\(\?-mix:\^?/, "").gsub(/\$\)$/, "")
|
294
|
-
steps_with_expressions.each do |step_location, step_value|
|
295
|
-
if regex_as_string =~ step_value
|
296
|
-
step_definition.add_call(step_location, step_value)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
def convert_steps_with_expressions(steps_with_expressions)
|
303
|
-
step_regexs = {}
|
304
|
-
steps_with_expressions.each do |step_location, step_value|
|
305
|
-
modified_step = step_value.gsub(/\#{[^}]*}/, '.*')
|
306
|
-
step_regexs[step_location] = Regexp.new('^' + modified_step + '$')
|
307
|
-
end
|
308
|
-
step_regexs
|
309
|
-
end
|
310
|
-
|
311
|
-
def load_summary_data(summary_hash)
|
312
|
-
summary_node = SummaryNode.new
|
313
|
-
summary_node.count = summary_hash[:total]
|
314
|
-
summary_node.score = summary_hash[:total_score]
|
315
|
-
summary_node.average = summary_hash[:average]
|
316
|
-
summary_node.threshold = summary_hash[:threshold]
|
317
|
-
summary_node.good = summary_hash[:good]
|
318
|
-
summary_node.bad = summary_hash[:bad]
|
319
|
-
summary_node
|
320
|
-
end
|
321
|
-
|
322
|
-
def build_file_list_from_folder(folder_name, extension)
|
323
|
-
list = []
|
324
|
-
Dir.entries(folder_name).each_entry do |file_name|
|
325
|
-
unless FILE_IGNORE_LIST.include?(file_name)
|
326
|
-
file_name = "#{folder_name}/#{file_name}"
|
327
|
-
if File.directory?(file_name)
|
328
|
-
list << build_file_list_from_folder(file_name, extension)
|
329
|
-
elsif file_name.downcase.include?(extension)
|
330
|
-
list << file_name
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
334
|
-
list.flatten
|
335
|
-
end
|
336
|
-
|
337
|
-
def build_step_definitions(file_name)
|
338
|
-
step_file_lines = []
|
339
|
-
step_file = File.open(file_name)
|
340
|
-
step_file.each_line { |line| step_file_lines << line }
|
341
|
-
step_file.close
|
342
|
-
|
343
|
-
counter = 0
|
344
|
-
step_code = []
|
345
|
-
step_definitions = []
|
346
|
-
found_first_step = false
|
347
|
-
until counter >= step_file_lines.length
|
348
|
-
if step_file_lines[counter] =~ STEP_DEFINITION_REGEX and !step_code.empty? and found_first_step
|
349
|
-
step_definitions << CukeSniffer::StepDefinition.new("#{file_name}:#{counter+1 - step_code.count}", step_code)
|
350
|
-
step_code = []
|
351
|
-
end
|
352
|
-
found_first_step = true if step_file_lines[counter] =~ STEP_DEFINITION_REGEX
|
353
|
-
step_code << step_file_lines[counter].strip
|
354
|
-
counter+=1
|
355
|
-
end
|
356
|
-
step_definitions << CukeSniffer::StepDefinition.new("#{file_name}:#{counter+1 -step_code.count}", step_code) unless step_code.empty? or !found_first_step
|
357
|
-
step_definitions
|
358
|
-
end
|
359
|
-
|
360
|
-
def assess_array(array, type)
|
361
|
-
min, max, min_file, max_file = nil
|
362
|
-
total = 0
|
363
|
-
good = 0
|
364
|
-
bad = 0
|
365
|
-
total_score = 0
|
366
|
-
unless array.empty?
|
367
|
-
array.each do |node|
|
368
|
-
score = node.score
|
369
|
-
@summary[:total_score] += score
|
370
|
-
total_score += score
|
371
|
-
node.rules_hash.each_key do |key|
|
372
|
-
@summary[:improvement_list][key] ||= 0
|
373
|
-
@summary[:improvement_list][key] += node.rules_hash[key]
|
374
|
-
end
|
375
|
-
min, min_file = score, node.location if (min.nil? or score < min)
|
376
|
-
max, max_file = score, node.location if (max.nil? or score > max)
|
377
|
-
if node.good?
|
378
|
-
good += 1
|
379
|
-
else
|
380
|
-
bad += 1
|
381
|
-
end
|
382
|
-
total += score
|
383
|
-
end
|
384
|
-
end
|
385
|
-
{
|
386
|
-
:total => array.count,
|
387
|
-
:total_score => total_score,
|
388
|
-
:min => min,
|
389
|
-
:min_file => min_file,
|
390
|
-
:max => max,
|
391
|
-
:max_file => max_file,
|
392
|
-
:average => (total.to_f/array.count.to_f).round(2),
|
393
|
-
:threshold => THRESHOLDS[type],
|
394
|
-
:good => good,
|
395
|
-
:bad => bad,
|
396
|
-
}
|
397
|
-
end
|
398
|
-
|
399
|
-
def get_all_scenarios(features)
|
400
|
-
scenarios = []
|
401
|
-
features.each do |feature|
|
402
|
-
scenarios << feature.background unless feature.background.nil?
|
403
|
-
scenarios << feature.scenarios
|
404
|
-
end
|
405
|
-
scenarios.flatten
|
406
|
-
end
|
407
|
-
|
408
|
-
def sort_improvement_list
|
409
|
-
sorted_array = @summary[:improvement_list].sort_by { |improvement, occurrence| occurrence }
|
410
|
-
@summary[:improvement_list] = {}
|
411
|
-
sorted_array.reverse.each { |node|
|
412
|
-
@summary[:improvement_list][node[0]] = node[1]
|
413
|
-
}
|
414
|
-
end
|
415
|
-
|
416
|
-
def create_improvement_list
|
417
|
-
output = []
|
418
|
-
@summary[:improvement_list].each_key { |improvement| output << "(#{summary[:improvement_list][improvement]})#{improvement}" }
|
419
|
-
output
|
420
|
-
end
|
421
|
-
|
422
|
-
def extract_steps_from_features
|
423
|
-
steps = {}
|
424
|
-
@features.each do |feature|
|
425
|
-
steps.merge! extract_scenario_steps(feature.background) unless feature.background.nil?
|
426
|
-
feature.scenarios.each do |scenario|
|
427
|
-
if scenario.type == "Scenario Outline"
|
428
|
-
steps.merge! extract_scenario_outline_steps(scenario)
|
429
|
-
else
|
430
|
-
steps.merge! extract_scenario_steps(scenario)
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
434
|
-
steps
|
435
|
-
end
|
436
|
-
|
437
|
-
def extract_scenario_steps(scenario)
|
438
|
-
steps_hash = {}
|
439
|
-
counter = 1
|
440
|
-
scenario.steps.each do |step|
|
441
|
-
location = scenario.location.gsub(/:\d*$/, ":#{scenario.start_line + counter}")
|
442
|
-
steps_hash[location] = step
|
443
|
-
counter += 1
|
444
|
-
end
|
445
|
-
steps_hash
|
446
|
-
end
|
447
|
-
|
448
|
-
def extract_scenario_outline_steps(scenario)
|
449
|
-
steps = {}
|
450
|
-
examples = scenario.examples_table
|
451
|
-
return {} if examples.empty?
|
452
|
-
variable_list = extract_variables_from_example(examples.first)
|
453
|
-
(1...examples.size).each do |example_counter|
|
454
|
-
#TODO Abstraction needed for this regex matcher (constants?)
|
455
|
-
next if examples[example_counter] =~ /^\#.*$/
|
456
|
-
row_variables = extract_variables_from_example(examples[example_counter])
|
457
|
-
step_counter = 1
|
458
|
-
scenario.steps.each do |step|
|
459
|
-
step_line = scenario.start_line + step_counter
|
460
|
-
location = "#{scenario.location.gsub(/\d+$/, step_line.to_s)}(Example #{example_counter})"
|
461
|
-
steps[location] = build_updated_step_from_example(step, variable_list, row_variables)
|
462
|
-
step_counter += 1
|
463
|
-
end
|
464
|
-
end
|
465
|
-
steps
|
466
|
-
end
|
467
|
-
|
468
|
-
def build_updated_step_from_example(step, variable_list, row_variables)
|
469
|
-
new_step = step.dup
|
470
|
-
variable_list.each do |variable|
|
471
|
-
if step.include? variable
|
472
|
-
table_variable_to_insert = row_variables[variable_list.index(variable)]
|
473
|
-
table_variable_to_insert ||= ""
|
474
|
-
new_step.gsub!("<#{variable}>", table_variable_to_insert)
|
475
|
-
end
|
476
|
-
end
|
477
|
-
new_step
|
478
|
-
end
|
479
|
-
|
480
|
-
def extract_steps_from_step_definitions
|
481
|
-
steps = {}
|
482
|
-
@step_definitions.each do |definition|
|
483
|
-
definition.nested_steps.each_key do |key|
|
484
|
-
steps[key] = definition.nested_steps[key]
|
485
|
-
end
|
486
|
-
end
|
487
|
-
steps
|
488
|
-
end
|
489
|
-
|
490
|
-
def extract_markup
|
491
|
-
markup_location = File.join(File.dirname(__FILE__), 'report', 'markup.rhtml')
|
492
|
-
markup = ""
|
493
|
-
File.open(markup_location).lines.each do |line|
|
494
|
-
markup << line
|
495
|
-
end
|
496
|
-
markup
|
497
|
-
end
|
498
|
-
|
499
|
-
def build_hooks(file_name)
|
500
|
-
hooks_file_lines = []
|
501
|
-
hooks_file = File.open(file_name)
|
502
|
-
hooks_file.each_line { |line| hooks_file_lines << line }
|
503
|
-
hooks_file.close
|
504
|
-
|
505
|
-
counter = 0
|
506
|
-
hooks_code = []
|
507
|
-
hooks = []
|
508
|
-
found_first_hook = false
|
509
|
-
until counter >= hooks_file_lines.length
|
510
|
-
if hooks_file_lines[counter] =~ HOOK_REGEX and !hooks_code.empty? and found_first_hook
|
511
|
-
hooks << CukeSniffer::Hook.new("#{file_name}:#{counter+1 - hooks_code.count}", hooks_code)
|
512
|
-
hooks_code = []
|
513
|
-
end
|
514
|
-
found_first_hook = true if hooks_file_lines[counter] =~ HOOK_REGEX
|
515
|
-
hooks_code << hooks_file_lines[counter].strip
|
516
|
-
counter+=1
|
517
|
-
end
|
518
|
-
hooks << CukeSniffer::Hook.new("#{file_name}:#{counter+1 -hooks_code.count}", hooks_code) unless hooks_code.empty? or !found_first_hook
|
519
|
-
hooks
|
520
|
-
end
|
521
|
-
|
522
|
-
end
|
523
|
-
end
|
1
|
+
require 'erb'
|
2
|
+
require 'roxml'
|
3
|
+
|
4
|
+
module CukeSniffer
|
5
|
+
|
6
|
+
# Author:: Robert Cochran (mailto:cochrarj@miamioh.edu)
|
7
|
+
# Copyright:: Copyright (C) 2013 Robert Cochran
|
8
|
+
# License:: Distributes under the MIT License
|
9
|
+
# Mixins: CukeSniffer::Constants, ROXML
|
10
|
+
class CLI
|
11
|
+
include CukeSniffer::Constants
|
12
|
+
include CukeSniffer::RuleConfig
|
13
|
+
include ROXML
|
14
|
+
|
15
|
+
xml_name "cuke_sniffer"
|
16
|
+
xml_accessor :rules, :as => [Rule], :in => "rules"
|
17
|
+
xml_accessor :features_summary, :as => CukeSniffer::SummaryNode
|
18
|
+
xml_accessor :scenarios_summary, :as => CukeSniffer::SummaryNode
|
19
|
+
xml_accessor :step_definitions_summary, :as => CukeSniffer::SummaryNode
|
20
|
+
xml_accessor :hooks_summary, :as => CukeSniffer::SummaryNode
|
21
|
+
xml_accessor :improvement_list, :as => {:key => "rule", :value => "total"}, :in => "improvement_list", :from => "improvement"
|
22
|
+
xml_accessor :features, :as => [CukeSniffer::Feature], :in => "features"
|
23
|
+
xml_accessor :step_definitions, :as => [CukeSniffer::StepDefinition], :in => "step_definitions"
|
24
|
+
xml_accessor :hooks, :as => [CukeSniffer::Hook], :in => "hooks"
|
25
|
+
|
26
|
+
|
27
|
+
# Feature array: All Features gathered from the specified folder
|
28
|
+
attr_accessor :features
|
29
|
+
|
30
|
+
# StepDefinition Array: All StepDefinitions objects gathered from the specified folder
|
31
|
+
attr_accessor :step_definitions
|
32
|
+
|
33
|
+
# Hash: Summary objects and improvement lists
|
34
|
+
# * Key: symbol, :total_score, :features, :step_definitions, :improvement_list
|
35
|
+
# * Value: hash or array
|
36
|
+
attr_accessor :summary
|
37
|
+
|
38
|
+
# string: Location of the feature file or root folder that was searched in
|
39
|
+
attr_accessor :features_location
|
40
|
+
|
41
|
+
# string: Location of the step definition file or root folder that was searched in
|
42
|
+
attr_accessor :step_definitions_location
|
43
|
+
|
44
|
+
# string: Location of the hook file or root folder that was searched in
|
45
|
+
attr_accessor :hooks_location
|
46
|
+
|
47
|
+
# Scenario array: All Scenarios found in the features from the specified folder
|
48
|
+
attr_accessor :scenarios
|
49
|
+
|
50
|
+
# Hook array: All Hooks found in the current directory
|
51
|
+
attr_accessor :hooks
|
52
|
+
|
53
|
+
# Rules hash: All the rules that exist at runtime and their corresponding data
|
54
|
+
attr_accessor :rules
|
55
|
+
|
56
|
+
|
57
|
+
# Does analysis against the passed features and step definition locations
|
58
|
+
#
|
59
|
+
# Can be called in several ways.
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# No argument(assumes current directory is the project)
|
63
|
+
# cuke_sniffer = CukeSniffer::CLI.new
|
64
|
+
#
|
65
|
+
# Against single files
|
66
|
+
# cuke_sniffer = CukeSniffer::CLI.new({:features_location =>"my_feature.feature"})
|
67
|
+
# Or
|
68
|
+
# cuke_sniffer = CukeSniffer::CLI.new({:step_definitions_location =>"my_steps.rb"})
|
69
|
+
# Or
|
70
|
+
# cuke_sniffer = CukeSniffer::CLI.new({:hooks_location =>"my_hooks.rb"})
|
71
|
+
#
|
72
|
+
#
|
73
|
+
# Against folders
|
74
|
+
# cuke_sniffer = CukeSniffer::CLI.new({:features_location =>"my_features_directory\", :step_definitions_location =>"my_steps_directory\"})
|
75
|
+
#
|
76
|
+
# You can mix and match all of the above examples
|
77
|
+
#
|
78
|
+
# Displays the sequence and a . indicator for each new loop in that process.
|
79
|
+
# Handles creation of all Feature and StepDefinition objects
|
80
|
+
# Then catalogs all step definition calls to be used for rules and identification
|
81
|
+
# of dead steps.
|
82
|
+
def initialize(parameters = {})
|
83
|
+
initialize_rule_targets(parameters)
|
84
|
+
evaluate_rules
|
85
|
+
catalog_step_calls
|
86
|
+
assess_score
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the status of the overall project based on a comparison of the score to the threshold score
|
90
|
+
def good?
|
91
|
+
@summary[:total_score] <= Constants::THRESHOLDS["Project"]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Calculates the score to threshold percentage of an object
|
95
|
+
# Return: Float
|
96
|
+
def problem_percentage
|
97
|
+
@summary[:total_score].to_f / Constants::THRESHOLDS["Project"].to_f
|
98
|
+
end
|
99
|
+
|
100
|
+
# Prints out a summary of the results and the list of improvements to be made
|
101
|
+
def output_results
|
102
|
+
CukeSniffer::Formatter.output_console(self)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Creates a html file with the collected project details
|
106
|
+
# file_name defaults to "cuke_sniffer_results.html" unless specified
|
107
|
+
# Second parameter used for passing into the markup.
|
108
|
+
# cuke_sniffer.output_html
|
109
|
+
# Or
|
110
|
+
# cuke_sniffer.output_html("results01-01-0001.html")
|
111
|
+
def output_html(file_name = DEFAULT_OUTPUT_FILE_NAME + ".html")
|
112
|
+
CukeSniffer::Formatter.output_html(self, file_name)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates a html file with minimum information: Summary, Rules, Improvement List.
|
116
|
+
# file_name defaults to "cuke_sniffer_results.html" unless specified
|
117
|
+
# Second parameter used for passing into the markup.
|
118
|
+
# cuke_sniffer.output_min_html
|
119
|
+
# Or
|
120
|
+
# cuke_sniffer.output_min_html("results01-01-0001.html")
|
121
|
+
def output_min_html(file_name = DEFAULT_OUTPUT_FILE_NAME + ".html")
|
122
|
+
CukeSniffer::Formatter.output_min_html(self, file_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Creates a xml file with the collected project details
|
126
|
+
# file_name defaults to "cuke_sniffer.xml" unless specified
|
127
|
+
# cuke_sniffer.output_xml
|
128
|
+
# Or
|
129
|
+
# cuke_sniffer.output_xml("cuke_sniffer01-01-0001.xml")
|
130
|
+
def output_xml(file_name = DEFAULT_OUTPUT_FILE_NAME + ".xml")
|
131
|
+
CukeSniffer::Formatter.output_xml(self, file_name)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Gathers all StepDefinitions that have no calls
|
135
|
+
# Returns a hash that has two different types of records
|
136
|
+
# 1: String of the file with a dead step with an array of the line and regex of each dead step
|
137
|
+
# 2: Symbol of :total with an integer that is the total number of dead steps
|
138
|
+
def get_dead_steps
|
139
|
+
CukeSniffer::DeadStepsHelper::build_dead_steps_hash(@step_definitions)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Determines all normal and nested step calls and assigns them to the corresponding step definition.
|
143
|
+
# Does direct and fuzzy matching
|
144
|
+
def catalog_step_calls
|
145
|
+
puts "\nCataloging Step Calls: "
|
146
|
+
steps = CukeSniffer::CukeSnifferHelper.get_all_steps(@features, @step_definitions)
|
147
|
+
@step_definitions.each do |step_definition|
|
148
|
+
print '.'
|
149
|
+
calls = steps.find_all { |location, step| step.gsub(STEP_STYLES, "") =~ step_definition.regex }
|
150
|
+
calls.each { |call| step_definition.add_call(call[0], call[1].gsub(STEP_STYLES, "")) }
|
151
|
+
end
|
152
|
+
|
153
|
+
steps_with_expressions = CukeSniffer::CukeSnifferHelper.get_steps_with_expressions(steps)
|
154
|
+
converted_steps = CukeSniffer::CukeSnifferHelper.convert_steps_with_expressions(steps_with_expressions)
|
155
|
+
CukeSniffer::CukeSnifferHelper.catalog_possible_dead_steps(@step_definitions, converted_steps)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def initialize_rule_targets(parameters)
|
161
|
+
initialize_locations(parameters)
|
162
|
+
initialize_feature_objects
|
163
|
+
|
164
|
+
puts("\nStep Definitions: ")
|
165
|
+
@step_definitions = build_objects_for_extension_from_location(@step_definitions_location, "rb") { |location| build_step_definitions(location) }
|
166
|
+
|
167
|
+
puts("\nHooks:")
|
168
|
+
@hooks = build_objects_for_extension_from_location(@hooks_location, "rb") { |location| build_hooks(location) }
|
169
|
+
end
|
170
|
+
|
171
|
+
def initialize_locations(parameters)
|
172
|
+
@features_location = parameters[:features_location] ? parameters[:features_location] : Dir.getwd
|
173
|
+
@step_definitions_location = parameters[:step_definitions_location] ? parameters[:step_definitions_location] : Dir.getwd
|
174
|
+
@hooks_location = parameters[:hooks_location] ? parameters[:hooks_location] : Dir.getwd
|
175
|
+
end
|
176
|
+
|
177
|
+
def initialize_feature_objects
|
178
|
+
puts "\nFeatures:"
|
179
|
+
@features = build_objects_for_extension_from_location(features_location, "feature") { |location| CukeSniffer::Feature.new(location) }
|
180
|
+
@scenarios = CukeSniffer::CukeSnifferHelper.get_all_scenarios(@features)
|
181
|
+
end
|
182
|
+
|
183
|
+
def evaluate_rules
|
184
|
+
@rules = CukeSniffer::CukeSnifferHelper.build_rules(RULES)
|
185
|
+
CukeSniffer::RulesEvaluator.new(self, @rules)
|
186
|
+
end
|
187
|
+
|
188
|
+
def initialize_summary
|
189
|
+
@summary = {
|
190
|
+
:total_score => 0,
|
191
|
+
:improvement_list => {}
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
def assess_score
|
196
|
+
puts "\nAssessing Score: "
|
197
|
+
initialize_summary
|
198
|
+
summarize(:features, @features, "Feature", @features_summary)
|
199
|
+
summarize(:scenarios, @scenarios, "Scenario", @scenarios_summary)
|
200
|
+
summarize(:step_definitions, @step_definitions, "StepDefinition", @step_definitions_summary)
|
201
|
+
summarize(:hooks, @hooks, "Hook", @hooks_summary)
|
202
|
+
@summary[:improvement_list] = CukeSniffer::SummaryHelper.sort_improvement_list(@summary[:improvement_list])
|
203
|
+
@improvement_list = @summary[:improvement_list]
|
204
|
+
end
|
205
|
+
|
206
|
+
def summarize(symbol, list, name, summary_object)
|
207
|
+
@summary[symbol] = CukeSniffer::SummaryHelper.assess_rule_target_list(list, name)
|
208
|
+
@summary[:total_score] = @summary[symbol][:total_score]
|
209
|
+
@summary[symbol][:improvement_list].each do |phrase, count|
|
210
|
+
@summary[:improvement_list][phrase] ||= 0
|
211
|
+
@summary[:improvement_list][phrase] += @summary[symbol][:improvement_list][phrase]
|
212
|
+
end
|
213
|
+
summary_object = CukeSniffer::SummaryHelper::load_summary_data(@summary[symbol])
|
214
|
+
end
|
215
|
+
|
216
|
+
def build_step_definitions(file_name)
|
217
|
+
build_object_for_extension_from_file(file_name, STEP_DEFINITION_REGEX, CukeSniffer::StepDefinition)
|
218
|
+
end
|
219
|
+
|
220
|
+
def build_hooks(file_name)
|
221
|
+
build_object_for_extension_from_file(file_name, HOOK_REGEX, CukeSniffer::Hook)
|
222
|
+
end
|
223
|
+
|
224
|
+
def build_file_list_for_extension_from_location(pattern_location, extension)
|
225
|
+
list = []
|
226
|
+
unless pattern_location.nil?
|
227
|
+
if File.file?(pattern_location)
|
228
|
+
[pattern_location]
|
229
|
+
else
|
230
|
+
Dir["#{pattern_location}/**/*.#{extension}"]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def build_objects_for_extension_from_location(pattern_location, extension, &block)
|
236
|
+
file_list = build_file_list_for_extension_from_location(pattern_location, extension)
|
237
|
+
list = []
|
238
|
+
file_list.each {|file_name|
|
239
|
+
print '.'
|
240
|
+
list << block.call(file_name)
|
241
|
+
}
|
242
|
+
list.flatten
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
def build_object_for_extension_from_file(file_name, regex, cuke_sniffer_class)
|
247
|
+
file_lines = IO.readlines(file_name)
|
248
|
+
|
249
|
+
counter = 0
|
250
|
+
code = []
|
251
|
+
object_list = []
|
252
|
+
found_first_object = false
|
253
|
+
until counter >= file_lines.length
|
254
|
+
if file_lines[counter] =~ regex and !code.empty? and found_first_object
|
255
|
+
location = "#{file_name}:#{counter+1 - code.count}"
|
256
|
+
object_list << cuke_sniffer_class.new(location, code)
|
257
|
+
code = []
|
258
|
+
end
|
259
|
+
found_first_object = true if file_lines[counter] =~ regex
|
260
|
+
code << file_lines[counter].strip
|
261
|
+
counter+=1
|
262
|
+
end
|
263
|
+
location = "#{file_name}:#{counter+1 -code.count}"
|
264
|
+
object_list << cuke_sniffer_class.new(location, code) unless code.empty? or !found_first_object
|
265
|
+
object_list
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
end
|