cuke_sniffer 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|