cuke_cataloger 1.0.0
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/.gitignore +18 -0
- data/.simplecov +8 -0
- data/.travis.yml +9 -0
- data/Gemfile +17 -0
- data/History.md +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +67 -0
- data/Rakefile +26 -0
- data/cuke_cataloger.gemspec +32 -0
- data/features/formatting.feature +191 -0
- data/features/step_definitions/action_steps.rb +28 -0
- data/features/step_definitions/setup_steps.rb +56 -0
- data/features/step_definitions/verification_steps.rb +162 -0
- data/features/support/env.rb +22 -0
- data/features/support/transforms.rb +3 -0
- data/features/tag_indexing.feature +362 -0
- data/features/test_case_scanning.feature +59 -0
- data/features/test_case_scanning_payload.feature +32 -0
- data/features/test_case_tagging.feature +193 -0
- data/features/test_case_validation.feature +368 -0
- data/lib/cuke_cataloger/unique_test_case_tagger.rb +555 -0
- data/lib/cuke_cataloger/version.rb +3 -0
- data/lib/cuke_cataloger.rb +60 -0
- data/lib/extensions/cucumber_analytics_extensions.rb +44 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unique_test_case_tagger_integration_spec.rb +85 -0
- data/spec/unique_test_case_tagger_unit_spec.rb +74 -0
- metadata +255 -0
@@ -0,0 +1,555 @@
|
|
1
|
+
module CukeCataloger
|
2
|
+
class UniqueTestCaseTagger
|
3
|
+
|
4
|
+
SUB_ID_PATTERN = /^\d+\-\d+$/
|
5
|
+
SUB_ID_MATCH_PATTERN = /^\d+\-(\d+)$/
|
6
|
+
|
7
|
+
|
8
|
+
attr_accessor :tag_location
|
9
|
+
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@file_line_increases = Hash.new(0)
|
13
|
+
@tag_location = :adjacent
|
14
|
+
end
|
15
|
+
|
16
|
+
def tag_tests(feature_directory, tag_prefix, explicit_indexes = {})
|
17
|
+
warn("This script will potentially rewrite all of your feature files. Please be patient and remember to tip your source control system.")
|
18
|
+
|
19
|
+
@known_id_tags = {}
|
20
|
+
|
21
|
+
set_id_tag(tag_prefix)
|
22
|
+
set_test_suite_model(feature_directory)
|
23
|
+
|
24
|
+
@start_indexes = merge_indexes(default_start_indexes(determine_known_ids(feature_directory, tag_prefix)), explicit_indexes)
|
25
|
+
@next_index = @start_indexes[:primary]
|
26
|
+
|
27
|
+
# Analysis and output
|
28
|
+
@tests.each do |test|
|
29
|
+
case
|
30
|
+
when test.is_a?(CukeModeler::Scenario)
|
31
|
+
process_scenario(test)
|
32
|
+
when test.is_a?(CukeModeler::Outline)
|
33
|
+
process_outline(test)
|
34
|
+
else
|
35
|
+
raise("Unknown test type: #{test.class.to_s}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def scan_for_tagged_tests(feature_directory, tag_prefix)
|
41
|
+
@results = []
|
42
|
+
@known_id_tags = {}
|
43
|
+
|
44
|
+
set_id_tag(tag_prefix)
|
45
|
+
set_test_suite_model(feature_directory)
|
46
|
+
|
47
|
+
@tests.each do |test|
|
48
|
+
add_to_results(test) if has_id_tag?(test)
|
49
|
+
|
50
|
+
if test.is_a?(CukeModeler::Outline)
|
51
|
+
test.examples.each do |example|
|
52
|
+
if has_id_parameter?(example)
|
53
|
+
example_rows_for(example).each do |row|
|
54
|
+
add_to_results(row) if has_row_id?(row)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@results
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate_test_ids(feature_directory, tag_prefix)
|
65
|
+
@results = []
|
66
|
+
@known_id_tags = {}
|
67
|
+
|
68
|
+
set_id_tag(tag_prefix)
|
69
|
+
set_test_suite_model(feature_directory)
|
70
|
+
|
71
|
+
@features.each { |feature| validate_feature(feature) }
|
72
|
+
@tests.each { |test| validate_test(test) }
|
73
|
+
|
74
|
+
@results
|
75
|
+
end
|
76
|
+
|
77
|
+
def determine_known_ids(feature_directory, tag_prefix)
|
78
|
+
known_ids = []
|
79
|
+
|
80
|
+
found_tagged_objects = scan_for_tagged_tests(feature_directory, tag_prefix).collect { |result| result[:object] }
|
81
|
+
|
82
|
+
found_tagged_objects.each do |element|
|
83
|
+
if element.is_a?(CukeModeler::Row)
|
84
|
+
row_id = row_id_for(element)
|
85
|
+
known_ids << row_id if well_formed_sub_id?(row_id)
|
86
|
+
else
|
87
|
+
known_ids << test_id_for(element)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
known_ids
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
|
98
|
+
def set_id_tag(tag_prefix)
|
99
|
+
@tag_prefix = tag_prefix
|
100
|
+
#todo -should probably escape these characters
|
101
|
+
@tag_pattern = Regexp.new("#{@tag_prefix}\\d+")
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_test_suite_model(feature_directory)
|
105
|
+
@directory = CukeModeler::Directory.new(feature_directory)
|
106
|
+
@model_repo = CQL::Repository.new(@directory)
|
107
|
+
|
108
|
+
@tests = @model_repo.query do
|
109
|
+
select :self
|
110
|
+
from scenarios, outlines
|
111
|
+
end.collect { |result| result[:self] }
|
112
|
+
|
113
|
+
@features = @model_repo.query do
|
114
|
+
select :self
|
115
|
+
from features
|
116
|
+
end.collect { |result| result[:self] }
|
117
|
+
end
|
118
|
+
|
119
|
+
def validate_feature(feature)
|
120
|
+
check_for_feature_level_test_tag(feature)
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_test(test)
|
124
|
+
check_for_missing_test_tag(test)
|
125
|
+
check_for_multiple_test_id_tags(test)
|
126
|
+
check_for_duplicated_test_id_tags(test)
|
127
|
+
|
128
|
+
if test.is_a?(CukeModeler::Outline)
|
129
|
+
check_for_missing_id_columns(test)
|
130
|
+
check_for_missing_row_tags(test)
|
131
|
+
check_for_duplicated_row_tags(test)
|
132
|
+
check_for_mismatched_row_tags(test)
|
133
|
+
check_for_malformed_row_tags(test)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def check_for_feature_level_test_tag(feature)
|
138
|
+
add_to_results(feature, :feature_test_tag) if has_id_tag?(feature)
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_for_duplicated_test_id_tags(test)
|
142
|
+
@existing_tags ||= @model_repo.query do
|
143
|
+
select tags
|
144
|
+
from features, scenarios, outlines, examples
|
145
|
+
end.collect { |result| result['tags'] }.flatten
|
146
|
+
|
147
|
+
test_id_tag = static_id_tag_for(test)
|
148
|
+
|
149
|
+
matching_tags = @existing_tags.select { |tag| tag == test_id_tag }
|
150
|
+
|
151
|
+
add_to_results(test, :duplicate_id_tag) if matching_tags.count > 1
|
152
|
+
end
|
153
|
+
|
154
|
+
def check_for_multiple_test_id_tags(test)
|
155
|
+
id_tags_found = test.tags.select { |tag| tag =~ @tag_pattern }
|
156
|
+
|
157
|
+
add_to_results(test, :multiple_tags) if id_tags_found.count > 1
|
158
|
+
end
|
159
|
+
|
160
|
+
def check_for_missing_test_tag(test)
|
161
|
+
add_to_results(test, :missing_tag) unless has_id_tag?(test)
|
162
|
+
end
|
163
|
+
|
164
|
+
def check_for_missing_id_columns(test)
|
165
|
+
test.examples.each do |example|
|
166
|
+
add_to_results(example, :missing_id_column) unless has_id_column?(example)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def check_for_duplicated_row_tags(test)
|
171
|
+
validate_rows(test, :duplicate_row_id, false, :has_duplicate_row_id?)
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_for_missing_row_tags(test)
|
175
|
+
validate_rows(test, :missing_row_id, true, :has_row_id?)
|
176
|
+
end
|
177
|
+
|
178
|
+
def check_for_mismatched_row_tags(test)
|
179
|
+
validate_rows(test, :mismatched_row_id, true, :has_matching_id?)
|
180
|
+
end
|
181
|
+
|
182
|
+
def check_for_malformed_row_tags(test)
|
183
|
+
test.examples.each do |example|
|
184
|
+
if has_id_column?(example)
|
185
|
+
example_rows_for(example).each do |row|
|
186
|
+
add_to_results(row, :malformed_sub_id) if (has_row_id?(row) && !well_formed_sub_id?(row_id_for(row)))
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def validate_rows(test, rule, desired, row_check)
|
193
|
+
test.examples.each do |example|
|
194
|
+
if has_id_column?(example)
|
195
|
+
example_rows_for(example).each do |row|
|
196
|
+
if desired
|
197
|
+
add_to_results(row, rule) unless self.send(row_check, row)
|
198
|
+
else
|
199
|
+
add_to_results(row, rule) if self.send(row_check, row)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def process_scenario(test)
|
207
|
+
apply_tag_if_needed(test)
|
208
|
+
end
|
209
|
+
|
210
|
+
def process_outline(test)
|
211
|
+
apply_tag_if_needed(test)
|
212
|
+
update_parameters_if_needed(test)
|
213
|
+
update_rows_if_needed(test, determine_next_sub_id(test))
|
214
|
+
end
|
215
|
+
|
216
|
+
def apply_tag_if_needed(test)
|
217
|
+
unless has_id_tag?(test)
|
218
|
+
tag = "#{@tag_prefix}#{@next_index}"
|
219
|
+
@next_index += 1
|
220
|
+
|
221
|
+
tag_test(test, tag, (' ' * determine_test_indentation(test)))
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def has_id_tag?(test)
|
226
|
+
!!fast_id_tag_for(test)
|
227
|
+
end
|
228
|
+
|
229
|
+
def has_id_column?(example)
|
230
|
+
example.parameters.any? { |param| param =~ /test_case_id/ }
|
231
|
+
end
|
232
|
+
|
233
|
+
def row_id_for(row)
|
234
|
+
id_index = determine_row_id_cell_index(row)
|
235
|
+
|
236
|
+
id_index && row.cells[id_index] != '' ? row.cells[id_index] : nil
|
237
|
+
end
|
238
|
+
|
239
|
+
def has_row_id?(row)
|
240
|
+
!!row_id_for(row)
|
241
|
+
end
|
242
|
+
|
243
|
+
def well_formed_sub_id?(id)
|
244
|
+
!!(id =~ SUB_ID_PATTERN)
|
245
|
+
end
|
246
|
+
|
247
|
+
def has_matching_id?(row)
|
248
|
+
row_id = row_id_for(row)
|
249
|
+
|
250
|
+
# A lack of id counts as 'matching'
|
251
|
+
return true if row_id.nil?
|
252
|
+
|
253
|
+
parent_tag = static_id_tag_for(row.get_ancestor(:test))
|
254
|
+
|
255
|
+
if parent_tag
|
256
|
+
parent_id = parent_tag.sub(@tag_prefix, '')
|
257
|
+
|
258
|
+
row_id =~ /#{parent_id}-/
|
259
|
+
else
|
260
|
+
row_id.nil?
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def has_duplicate_row_id?(row)
|
265
|
+
row_id = row_id_for(row)
|
266
|
+
|
267
|
+
return false unless row_id && well_formed_sub_id?(row_id)
|
268
|
+
|
269
|
+
existing_ids = determine_used_sub_ids(row.get_ancestor(:test))
|
270
|
+
matching_ids = existing_ids.select { |id| id == row_id[/\d+$/] }
|
271
|
+
|
272
|
+
matching_ids.count > 1
|
273
|
+
end
|
274
|
+
|
275
|
+
def determine_next_sub_id(test)
|
276
|
+
parent = test_id_for(test)
|
277
|
+
explicit_index = @start_indexes[:sub][parent]
|
278
|
+
|
279
|
+
explicit_index ? explicit_index : 1
|
280
|
+
end
|
281
|
+
|
282
|
+
def determine_used_sub_ids(test)
|
283
|
+
ids = test.examples.collect do |example|
|
284
|
+
if has_id_parameter?(example)
|
285
|
+
example_rows_for(example).collect do |row|
|
286
|
+
row_id_for(row)
|
287
|
+
end
|
288
|
+
else
|
289
|
+
[]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
ids.flatten!
|
294
|
+
ids.delete_if { |id| !id.to_s.match(SUB_ID_PATTERN) }
|
295
|
+
|
296
|
+
ids.collect! { |id| id.match(SUB_ID_MATCH_PATTERN)[1] }
|
297
|
+
|
298
|
+
ids
|
299
|
+
end
|
300
|
+
|
301
|
+
def determine_row_id_cell_index(row)
|
302
|
+
row.get_ancestor(:example).parameters.index { |param| param =~ /test_case_id/ }
|
303
|
+
end
|
304
|
+
|
305
|
+
def tag_test(test, tag, padding_string = ' ')
|
306
|
+
feature_file = test.get_ancestor(:feature_file)
|
307
|
+
file_path = feature_file.path
|
308
|
+
|
309
|
+
index_adjustment = @file_line_increases[file_path]
|
310
|
+
tag_index = source_line_to_use(test) + index_adjustment
|
311
|
+
|
312
|
+
file_lines = File.readlines(file_path)
|
313
|
+
file_lines.insert(tag_index, "#{padding_string}#{tag}\n")
|
314
|
+
|
315
|
+
File.open(file_path, 'w') { |file| file.print file_lines.join }
|
316
|
+
@file_line_increases[file_path] += 1
|
317
|
+
test.tags << tag
|
318
|
+
end
|
319
|
+
|
320
|
+
def update_parameters_if_needed(test)
|
321
|
+
feature_file = test.get_ancestor(:feature_file)
|
322
|
+
file_path = feature_file.path
|
323
|
+
index_adjustment = @file_line_increases[file_path]
|
324
|
+
|
325
|
+
test.examples.each do |example|
|
326
|
+
unless has_id_parameter?(example)
|
327
|
+
parameter_line_index = (example.row_elements.first.source_line - 1) + index_adjustment
|
328
|
+
|
329
|
+
file_lines = File.readlines(file_path)
|
330
|
+
|
331
|
+
new_parameter = 'test_case_id'.ljust(parameter_spacing(example))
|
332
|
+
update_parameter_row(file_lines, parameter_line_index, new_parameter)
|
333
|
+
File.open(file_path, 'w') { |file| file.print file_lines.join }
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def update_rows_if_needed(test, sub_id)
|
339
|
+
feature_file = test.get_ancestor(:feature_file)
|
340
|
+
file_path = feature_file.path
|
341
|
+
index_adjustment = @file_line_increases[file_path]
|
342
|
+
|
343
|
+
tag_index = fast_id_tag_for(test)[/\d+/]
|
344
|
+
|
345
|
+
file_lines = File.readlines(file_path)
|
346
|
+
|
347
|
+
test.examples.each do |example|
|
348
|
+
example.row_elements[1..(example.row_elements.count - 1)].each do |row|
|
349
|
+
unless has_row_id?(row)
|
350
|
+
row_id = "#{tag_index}-#{sub_id}".ljust(parameter_spacing(example))
|
351
|
+
|
352
|
+
row_line_index = (row.source_line - 1) + index_adjustment
|
353
|
+
|
354
|
+
update_value_row(file_lines, row_line_index, row, row_id)
|
355
|
+
sub_id += 1
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
File.open(file_path, 'w') { |file| file.print file_lines.join }
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
# Slowest way to get the id tag. Will check the object every time.
|
365
|
+
def current_id_tag_for(thing)
|
366
|
+
id_tag_for(thing)
|
367
|
+
end
|
368
|
+
|
369
|
+
# Faster way to get the id tag. Will skip checking the object if an id for it is already known.
|
370
|
+
def fast_id_tag_for(thing)
|
371
|
+
@known_id_tags ||= {}
|
372
|
+
|
373
|
+
id = @known_id_tags[thing.object_id]
|
374
|
+
|
375
|
+
unless id
|
376
|
+
id = current_id_tag_for(thing)
|
377
|
+
@known_id_tags[thing.object_id] = id
|
378
|
+
end
|
379
|
+
|
380
|
+
id
|
381
|
+
end
|
382
|
+
|
383
|
+
# Fastest way to get the id tag. Will skip checking the object if it has been checked before, even if no id was found.
|
384
|
+
def static_id_tag_for(thing)
|
385
|
+
@known_id_tags ||= {}
|
386
|
+
id_key = thing.object_id
|
387
|
+
|
388
|
+
return @known_id_tags[id_key] if @known_id_tags.has_key?(id_key)
|
389
|
+
|
390
|
+
id = current_id_tag_for(thing)
|
391
|
+
@known_id_tags[id_key] = id
|
392
|
+
|
393
|
+
id
|
394
|
+
end
|
395
|
+
|
396
|
+
def id_tag_for(thing)
|
397
|
+
thing.tags.select { |tag| tag =~ @tag_pattern }.first
|
398
|
+
end
|
399
|
+
|
400
|
+
def test_id_for(test)
|
401
|
+
#todo - should probably be escaping these in case regex characters used in prefix...
|
402
|
+
fast_id_tag_for(test).match(/#{@tag_prefix}(.*)/)[1]
|
403
|
+
end
|
404
|
+
|
405
|
+
def has_id_parameter?(example)
|
406
|
+
#todo - make the id column name configurable
|
407
|
+
example.parameters.any? { |parameter| parameter == 'test_case_id' }
|
408
|
+
end
|
409
|
+
|
410
|
+
def update_parameter_row(file_lines, line_index, parameter)
|
411
|
+
append_row!(file_lines, line_index, " #{parameter} |")
|
412
|
+
end
|
413
|
+
|
414
|
+
def update_value_row(file_lines, line_index, row, row_id)
|
415
|
+
case
|
416
|
+
when needs_adding?(row)
|
417
|
+
append_row!(file_lines, line_index, " #{row_id} |")
|
418
|
+
when needs_filled_in?(row)
|
419
|
+
fill_in_row(file_lines, line_index, row, row_id)
|
420
|
+
else
|
421
|
+
raise("Don't know how to update row")
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def needs_adding?(row)
|
426
|
+
!has_id_parameter?(row.get_ancestor(:example))
|
427
|
+
end
|
428
|
+
|
429
|
+
def needs_filled_in?(row)
|
430
|
+
has_id_parameter?(row.get_ancestor(:example))
|
431
|
+
end
|
432
|
+
|
433
|
+
def replace_row!(file_lines, line_index, new_line)
|
434
|
+
file_lines[line_index] = new_line
|
435
|
+
end
|
436
|
+
|
437
|
+
def prepend_row!(file_lines, line_index, string)
|
438
|
+
old_row = file_lines[line_index]
|
439
|
+
new_row = string + old_row.lstrip
|
440
|
+
file_lines[line_index] = new_row
|
441
|
+
end
|
442
|
+
|
443
|
+
def append_row!(file_lines, line_index, string)
|
444
|
+
old_row = file_lines[line_index]
|
445
|
+
trailing_bits = old_row[/\s*$/]
|
446
|
+
new_row = old_row.rstrip + string + trailing_bits
|
447
|
+
|
448
|
+
file_lines[line_index] = new_row
|
449
|
+
end
|
450
|
+
|
451
|
+
def example_rows_for(example)
|
452
|
+
rows = example.row_elements.dup
|
453
|
+
rows.shift
|
454
|
+
|
455
|
+
rows
|
456
|
+
end
|
457
|
+
|
458
|
+
def add_to_results(item, issue = nil)
|
459
|
+
result = {:test => "#{item.get_ancestor(:feature_file).path}:#{item.source_line}", :object => item}
|
460
|
+
result.merge!({:problem => issue}) if issue
|
461
|
+
|
462
|
+
@results << result
|
463
|
+
end
|
464
|
+
|
465
|
+
def default_start_indexes(known_ids)
|
466
|
+
primary_ids = known_ids.select { |id| id =~ /^\d+$/ }
|
467
|
+
sub_ids = known_ids.select { |id| id =~ /^\d+-\d+$/ }
|
468
|
+
|
469
|
+
max_primary_id = primary_ids.collect { |id| id.to_i }.max || 0
|
470
|
+
default_indexes = {:primary => max_primary_id + 1,
|
471
|
+
:sub => {}}
|
472
|
+
|
473
|
+
sub_primaries = sub_ids.collect { |sub_id| sub_id[/^\d+/] }
|
474
|
+
|
475
|
+
sub_primaries.each do |primary|
|
476
|
+
default_indexes[:sub][primary] = sub_ids.select { |sub_id| sub_id[/^\d+/] == primary }.collect { |sub_id| sub_id[/\d+$/].to_i }.max + 1
|
477
|
+
end
|
478
|
+
|
479
|
+
default_indexes
|
480
|
+
end
|
481
|
+
|
482
|
+
def merge_indexes(set1, set2)
|
483
|
+
set1.merge(set2) { |key, set1_value, set2_value|
|
484
|
+
key == :sub ? set1_value.merge(set2_value) : set2_value
|
485
|
+
}
|
486
|
+
end
|
487
|
+
|
488
|
+
def parameter_spacing(example)
|
489
|
+
test = example.get_ancestor(:test)
|
490
|
+
test_id = fast_id_tag_for(test)[/\d+$/]
|
491
|
+
row_count = test.examples.reduce(0) { |sum, example| sum += example.rows.count }
|
492
|
+
|
493
|
+
max_id_length = test_id.length + 1 + row_count.to_s.length
|
494
|
+
param_length = 'test_case_id'.length
|
495
|
+
|
496
|
+
[param_length, max_id_length].max
|
497
|
+
end
|
498
|
+
|
499
|
+
def determine_test_indentation(test)
|
500
|
+
#todo - replace with 'get_most_recent_file_text'
|
501
|
+
feature_file = test.get_ancestor(:feature_file)
|
502
|
+
file_path = feature_file.path
|
503
|
+
|
504
|
+
index_adjustment = @file_line_increases[file_path]
|
505
|
+
test_index = (test.source_line - 1) + index_adjustment
|
506
|
+
|
507
|
+
file_lines = File.readlines(file_path)
|
508
|
+
indentation = file_lines[test_index][/^\s*/].length
|
509
|
+
|
510
|
+
indentation
|
511
|
+
end
|
512
|
+
|
513
|
+
def fill_in_row(file_lines, line_index, row, row_id)
|
514
|
+
old_row = file_lines[line_index]
|
515
|
+
sections = file_lines[line_index].split('|', -1)
|
516
|
+
|
517
|
+
replacement_index = determine_row_id_cell_index(row)
|
518
|
+
sections[replacement_index + 1] = " #{row_id} "
|
519
|
+
|
520
|
+
new_row = sections.join('|')
|
521
|
+
|
522
|
+
replace_row!(file_lines, line_index, new_row)
|
523
|
+
end
|
524
|
+
|
525
|
+
def source_line_to_use(test)
|
526
|
+
case @tag_location
|
527
|
+
when :above
|
528
|
+
determine_highest_tag_line(test)
|
529
|
+
when :below
|
530
|
+
determine_lowest_tag_line(test)
|
531
|
+
when :adjacent
|
532
|
+
adjacent_tag_line(test)
|
533
|
+
else
|
534
|
+
raise(ArgumentError, "Don't know where #{@tag_location} is.")
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def determine_highest_tag_line(test)
|
539
|
+
return adjacent_tag_line(test) if test.tags.empty?
|
540
|
+
|
541
|
+
test.tag_elements.collect { |tag_element| tag_element.source_line }.min - 1
|
542
|
+
end
|
543
|
+
|
544
|
+
def determine_lowest_tag_line(test)
|
545
|
+
return adjacent_tag_line(test) if test.tags.empty?
|
546
|
+
|
547
|
+
test.tag_elements.collect { |tag_element| tag_element.source_line }.max
|
548
|
+
end
|
549
|
+
|
550
|
+
def adjacent_tag_line(test)
|
551
|
+
(test.source_line - 1)
|
552
|
+
end
|
553
|
+
|
554
|
+
end
|
555
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'cuke_modeler'
|
3
|
+
require 'cql'
|
4
|
+
|
5
|
+
require 'extensions/cucumber_analytics_extensions'
|
6
|
+
require 'cuke_cataloger/version'
|
7
|
+
require 'cuke_cataloger/unique_test_case_tagger'
|
8
|
+
|
9
|
+
module CukeCataloger
|
10
|
+
|
11
|
+
extend Rake::DSL
|
12
|
+
|
13
|
+
|
14
|
+
def self.create_tasks
|
15
|
+
|
16
|
+
desc 'Add unique id tags to tests in the given directory'
|
17
|
+
task 'tag_tests', [:directory, :prefix] do |t, args|
|
18
|
+
puts "Tagging tests in '#{args[:directory]}' with tag '#{args[:prefix]}'\n"
|
19
|
+
|
20
|
+
tagger = CukeCataloger::UniqueTestCaseTagger.new
|
21
|
+
tagger.tag_tests(args[:directory], args[:prefix])
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Scan tests in the given directory for id problems'
|
25
|
+
task 'validate_tests', [:directory, :prefix, :out_file] do |t, args|
|
26
|
+
puts "Validating tests in '#{args[:directory]}' with tag '#{args[:prefix]}'\n"
|
27
|
+
|
28
|
+
results = CukeCataloger::UniqueTestCaseTagger.new.validate_test_ids(args[:directory], args[:prefix])
|
29
|
+
report_text = "Validation Results\nProblems found: #{results.count}\n\n"
|
30
|
+
|
31
|
+
|
32
|
+
results_by_category = Hash.new { |hash, key| hash[key] = [] }
|
33
|
+
|
34
|
+
results.each do |result|
|
35
|
+
results_by_category[result[:problem]] << result
|
36
|
+
end
|
37
|
+
|
38
|
+
results_by_category.keys.each do |problem_category|
|
39
|
+
report_text << "#{problem_category} problems: #{results_by_category[problem_category].count}\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
results_by_category.keys.each do |problem_category|
|
43
|
+
report_text << "\n\n#{problem_category} problems (#{results_by_category[problem_category].count}):\n"
|
44
|
+
|
45
|
+
results_by_category[problem_category].each do |result|
|
46
|
+
report_text << "#{result[:test]}\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if args[:out_file]
|
51
|
+
puts "Problems found: #{results.count}"
|
52
|
+
File.write(args[:out_file], report_text)
|
53
|
+
else
|
54
|
+
puts report_text
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CukeModeler
|
2
|
+
module Parsing
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def parse_text(source_text, file_name = nil)
|
7
|
+
raise(ArgumentError, "Cannot parse #{source_text.class} objects. Strings only.") unless source_text.is_a?(String)
|
8
|
+
|
9
|
+
file_name ||= 'fake_file.txt'
|
10
|
+
|
11
|
+
io = StringIO.new
|
12
|
+
formatter = Gherkin::Formatter::JSONFormatter.new(io)
|
13
|
+
parser = Gherkin::Parser::Parser.new(formatter)
|
14
|
+
begin
|
15
|
+
parser.parse(source_text, file_name, 0)
|
16
|
+
|
17
|
+
formatter.done
|
18
|
+
rescue => e
|
19
|
+
raise("Error encountered while parsing file #{file_name}: #{e.message}")
|
20
|
+
end
|
21
|
+
MultiJson.load(io.string)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
module CukeModeler
|
31
|
+
class FeatureFile
|
32
|
+
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
|
37
|
+
def parse_file(file_to_parse)
|
38
|
+
source_text = IO.read(file_to_parse)
|
39
|
+
|
40
|
+
Parsing::parse_text(source_text, file_to_parse)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
unless RUBY_VERSION.to_s < '1.9.0'
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.command_name('cuke_cataloger-cucumber')
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
require 'cuke_cataloger'
|
8
|
+
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.before(:all) do
|
12
|
+
here = File.dirname(__FILE__)
|
13
|
+
@default_file_directory = "#{here}/temp_files"
|
14
|
+
@default_test_file_directory = "#{here}/test_files"
|
15
|
+
end
|
16
|
+
|
17
|
+
config.before(:each) do
|
18
|
+
FileUtils.mkpath(@default_file_directory)
|
19
|
+
end
|
20
|
+
|
21
|
+
config.after(:each) do
|
22
|
+
FileUtils.remove_dir(@default_file_directory, true)
|
23
|
+
end
|
24
|
+
end
|