cuke_slicer 1.0.0 → 2.2.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.
@@ -0,0 +1,23 @@
1
+ # Internal helper module that is not part of the public API. Subject to change at any time.
2
+ # :nodoc: all
3
+ module CukeSlicer
4
+ module Helpers
5
+
6
+ def str_regex?(parameter)
7
+ parameter.is_a?(String) or parameter.is_a?(Regexp)
8
+ end
9
+
10
+ def str_regex_arr?(parameter)
11
+ parameter.is_a?(String) or parameter.is_a?(Regexp) or parameter.is_a?(Array)
12
+ end
13
+
14
+ def is_tag?(parameter)
15
+ parameter.to_s =~ /tag/
16
+ end
17
+
18
+ def is_path?(parameter)
19
+ parameter.to_s =~ /path/
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ # Internal helper module that is not part of the public API. Subject to change at any time.
2
+ # :nodoc: all
3
+ module CukeSlicer
4
+ module MatchingHelpers
5
+
6
+ def matching_tag?(element, filters)
7
+ filters.each do |filter|
8
+ if filter.is_a?(Array)
9
+ filter_match = or_filter_match(element, filter)
10
+ else
11
+ filter_match = and_filter_match(element, filter)
12
+ end
13
+
14
+ return false unless filter_match
15
+ end
16
+
17
+ true
18
+ end
19
+
20
+ def and_filter_match(element, filter)
21
+ filter_match(element, filter)
22
+ end
23
+
24
+ def or_filter_match(element, filters)
25
+ filters.any? do |filter|
26
+ filter_match(element, filter)
27
+ end
28
+ end
29
+
30
+ def filter_match(element, filter)
31
+ tag_values = element.all_tags
32
+ tag_values = tag_values.collect { |tag| tag.name } unless Gem.loaded_specs['cuke_modeler'].version.version[/^[0]/]
33
+
34
+ if filter.is_a?(Regexp)
35
+ tag_values.any? { |tag| tag =~ filter }
36
+ else
37
+ tag_values.include?(filter)
38
+ end
39
+ end
40
+
41
+ def matching_path?(element, filters)
42
+ filters.any? do |filtered_path|
43
+ if filtered_path.is_a?(Regexp)
44
+ element.get_ancestor(:feature_file).path =~ filtered_path
45
+ else
46
+ element.get_ancestor(:feature_file).path == filtered_path
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -1,3 +1,8 @@
1
+ require "cuke_slicer/extractors/directory_extractor"
2
+ require "cuke_slicer/extractors/file_extractor"
3
+ require "cuke_slicer/filters/filter_set"
4
+
5
+
1
6
  module CukeSlicer
2
7
 
3
8
  # The object responsible for slicing up a Cucumber test suite into discrete test cases.
@@ -13,23 +18,32 @@ module CukeSlicer
13
18
  # exposes the underlying modeling objects and knowledge of how they work is then required to make good use of the
14
19
  # filter.
15
20
  #
21
+ # Finally, the test cases can be provided as a collection of file:line strings or as a collection of the object
22
+ # types used to represent test cases by the underlying modeling library.
23
+ #
16
24
  # @param target [String] the location that will be sliced up
17
25
  # @param filters [Hash] the filters that will be applied to the sliced test cases
18
- def slice(target, filters = {}, &block)
26
+ # @param format [Symbol] the type of output: :file_line or :test_object
27
+ def slice(target, filters = {}, format, &block)
19
28
  validate_target(target)
20
29
  validate_filters(filters)
21
-
30
+ validate_format(format)
22
31
 
23
32
  begin
24
33
  target = File.directory?(target) ? CukeModeler::Directory.new(target) : CukeModeler::FeatureFile.new(target)
25
- rescue Gherkin::Lexer::LexingError
26
- raise(ArgumentError, "A syntax or lexing problem was encountered while trying to parse #{target}")
34
+ rescue => e
35
+ if e.message =~ /lexing|parsing/i
36
+ raise(ArgumentError, "A syntax or lexing problem was encountered while trying to parse #{target}")
37
+ else
38
+ raise e
39
+ end
40
+
27
41
  end
28
42
 
29
43
  if target.is_a?(CukeModeler::Directory)
30
- sliced_tests = extract_test_cases_from_directory(target, filters, &block)
44
+ sliced_tests = DirectoryExtractor.new.extract(target, filters, format, &block)
31
45
  else
32
- sliced_tests = extract_test_cases_from_file(target, filters, &block)
46
+ sliced_tests = FileExtractor.new.extract(target, filters, format, &block)
33
47
  end
34
48
 
35
49
  sliced_tests
@@ -51,198 +65,13 @@ module CukeSlicer
51
65
  raise(ArgumentError, "File or directory '#{target}' does not exist") unless File.exists?(target.to_s)
52
66
  end
53
67
 
54
- def validate_filters(filter_sets)
55
- filter_sets.each do |filter_type, filter_value|
56
- raise(ArgumentError, "Unknown filter '#{filter_type}'") unless self.class.known_filters.include?(filter_type)
57
- raise(ArgumentError, "Invalid filter '#{filter_value}'. Must be a String, Regexp, or Array thereof. Got #{filter_value.class}") unless filter_value.is_a?(String) or filter_value.is_a?(Regexp) or filter_value.is_a?(Array)
58
-
59
- if filter_value.is_a?(Array)
60
- validate_tag_collection(filter_value) if filter_type.to_s =~ /tag/
61
- validate_path_collection(filter_value) if filter_type.to_s =~ /path/
62
- end
63
- end
64
- end
65
-
66
- def validate_tag_collection(filter_collection)
67
- filter_collection.each do |filter|
68
- raise(ArgumentError, "Filter '#{filter}' must be a String, Regexp, or Array. Got #{filter.class}") unless filter.is_a?(String) or filter.is_a?(Regexp) or filter.is_a?(Array)
69
-
70
- validate_nested_tag_collection(filter) if filter.is_a?(Array)
71
- end
72
- end
73
-
74
- def validate_nested_tag_collection(filter_collection)
75
- filter_collection.each do |filter|
76
- raise(ArgumentError, "Tag filters cannot be nested more than one level deep.") if filter.is_a?(Array)
77
- raise(ArgumentError, "Filter '#{filter}' must be a String or Regexp. Got #{filter.class}") unless filter.is_a?(String) or filter.is_a?(Regexp)
78
- end
79
- end
80
-
81
- def validate_path_collection(filter_collection)
82
- filter_collection.each do |filter|
83
- raise(ArgumentError, "Filter '#{filter}' must be a String or Regexp. Got #{filter.class}") unless filter.is_a?(String) or filter.is_a?(Regexp)
84
- end
85
- end
86
-
87
- def extract_test_cases_from_directory(target, filters, &block)
88
- entries = Dir.entries(target.path)
89
- entries.delete '.'
90
- entries.delete '..'
91
-
92
- Array.new.tap do |test_cases|
93
- entries.each do |entry|
94
- entry = "#{target.path}/#{entry}"
95
-
96
- case
97
- when File.directory?(entry)
98
- test_cases.concat(extract_test_cases_from_directory(CukeModeler::Directory.new(entry), filters, &block))
99
- when entry =~ /\.feature$/
100
- test_cases.concat(extract_test_cases_from_file(CukeModeler::FeatureFile.new(entry), filters, &block))
101
- else
102
- # Non-feature files are ignored
103
- end
104
- end
105
- end
68
+ def validate_filters(filters)
69
+ filter_sets = filters.map { |filter_type, value| FilterSet.new(filter_type, value) }
70
+ filter_sets.each(&:validate)
106
71
  end
107
72
 
108
- def extract_test_cases_from_file(target, filters, &block)
109
- Array.new.tap do |test_cases|
110
- unless target.feature.nil?
111
- tests = target.feature.tests
112
-
113
- runnable_elements = extract_runnable_elements(extract_runnable_block_elements(tests, filters))
114
-
115
- apply_custom_filter(runnable_elements, &block)
116
-
117
- runnable_elements.each do |element|
118
- test_cases << "#{element.get_ancestor(:feature_file).path}:#{element.source_line}"
119
- end
120
- end
121
- end
122
- end
123
-
124
- def extract_runnable_block_elements(things, filters)
125
- Array.new.tap do |elements|
126
- things.each do |thing|
127
- if thing.is_a?(CukeModeler::Outline)
128
- elements.concat(thing.examples)
129
- else
130
- elements << thing
131
- end
132
- end
133
-
134
- filter_excluded_paths(elements, filters[:excluded_paths])
135
- filter_included_paths(elements, filters[:included_paths])
136
- filter_excluded_tags(elements, filters[:excluded_tags])
137
- filter_included_tags(elements, filters[:included_tags])
138
- end
139
- end
140
-
141
- def extract_runnable_elements(things)
142
- Array.new.tap do |elements|
143
- things.each do |thing|
144
- if thing.is_a?(CukeModeler::Example)
145
- # Slicing in order to remove the parameter row element
146
- elements.concat(thing.row_elements.slice(1, thing.row_elements.count - 1))
147
- else
148
- elements << thing
149
- end
150
- end
151
- end
152
- end
153
-
154
- def apply_custom_filter(elements, &block)
155
- if block
156
- elements.reject! do |element|
157
- block.call(element)
158
- end
159
- end
160
- end
161
-
162
- def filter_excluded_tags(elements, filters)
163
- if filters
164
- filters = [filters] unless filters.is_a?(Array)
165
-
166
- unless filters.empty?
167
- elements.reject! do |element|
168
- matching_tag?(element, filters)
169
- end
170
- end
171
- end
172
- end
173
-
174
- def filter_included_tags(elements, filters)
175
- if filters
176
- filters = [filters] unless filters.is_a?(Array)
177
-
178
- elements.keep_if do |element|
179
- matching_tag?(element, filters)
180
- end
181
- end
182
- end
183
-
184
- def filter_excluded_paths(elements, filters)
185
- if filters
186
- filters = [filters] unless filters.is_a?(Array)
187
-
188
- elements.reject! do |element|
189
- matching_path?(element, filters)
190
- end
191
- end
192
- end
193
-
194
- def filter_included_paths(elements, filters)
195
- if filters
196
- filters = [filters] unless filters.is_a?(Array)
197
-
198
- unless filters.empty?
199
- elements.keep_if do |element|
200
- matching_path?(element, filters)
201
- end
202
- end
203
- end
204
- end
205
-
206
- def matching_tag?(element, filters)
207
- filters.each do |filter|
208
- if filter.is_a?(Array)
209
- filter_match = or_filter_match(element, filter)
210
- else
211
- filter_match = and_filter_match(element, filter)
212
- end
213
-
214
- return false unless filter_match
215
- end
216
-
217
- true
218
- end
219
-
220
- def and_filter_match(element, filter)
221
- filter_match(element, filter)
222
- end
223
-
224
- def or_filter_match(element, filters)
225
- filters.any? do |filter|
226
- filter_match(element, filter)
227
- end
228
- end
229
-
230
- def filter_match(element, filter)
231
- if filter.is_a?(Regexp)
232
- element.all_tags.any? { |tag| tag =~ filter }
233
- else
234
- element.all_tags.include?(filter)
235
- end
236
- end
237
-
238
- def matching_path?(element, filters)
239
- filters.any? do |filtered_path|
240
- if filtered_path.is_a?(Regexp)
241
- element.get_ancestor(:feature_file).path =~ filtered_path
242
- else
243
- element.get_ancestor(:feature_file).path == filtered_path
244
- end
245
- end
73
+ def validate_format(format)
74
+ raise(ArgumentError, "Invalid Output Format: #{format}") unless [:test_object, :file_line].include?(format)
246
75
  end
247
76
 
248
77
  end
@@ -1,4 +1,4 @@
1
1
  module CukeSlicer
2
2
  # The current version for the gem.
3
- VERSION = "1.0.0"
3
+ VERSION = "2.2.0"
4
4
  end
@@ -1,4 +1,6 @@
1
1
  require 'spec_helper'
2
+ require "#{File.dirname(__FILE__)}/spec_helper"
3
+ require 'rubygems/mock_gem_ui'
2
4
 
3
5
 
4
6
  describe 'CukeSlicer, Unit' do
@@ -10,4 +12,17 @@ describe 'CukeSlicer, Unit' do
10
12
  expect(Kernel.const_defined?(:CukeSlicer)).to be true
11
13
  end
12
14
 
15
+ describe 'the gem' do
16
+
17
+ let(:gemspec) { eval(File.read "#{File.dirname(__FILE__)}/../cuke_slicer.gemspec") }
18
+
19
+ it 'validates cleanly' do
20
+ mock_ui = Gem::MockGemUi.new
21
+ Gem::DefaultUserInteraction.use_ui(mock_ui) { gemspec.validate }
22
+
23
+ expect(mock_ui.error).to_not match(/warn/i)
24
+ end
25
+
26
+ end
27
+
13
28
  end
@@ -3,74 +3,130 @@ require 'spec_helper'
3
3
 
4
4
  describe 'Slicer, Integration' do
5
5
 
6
- let!(:clazz) { CukeSlicer::Slicer }
6
+ let(:clazz) { CukeSlicer::Slicer }
7
7
  let(:slicer) { clazz.new }
8
8
  let(:test_file) { "#{@default_file_directory}/a_test.feature" }
9
+ let(:test_file_text) { "Feature: Test feature
9
10
 
10
- before(:each) do
11
- file_text = "Feature: Test feature
12
-
13
- @tag
14
- Scenario: Test scenario
15
- * some step"
11
+ @tag
12
+ Scenario: Test scenario
13
+ * some step" }
16
14
 
17
- File.open(test_file, 'w') { |file| file.write(file_text) }
15
+ before(:each) do
16
+ File.write(test_file, test_file_text)
18
17
  end
19
18
 
20
19
 
21
20
  describe 'slicing' do
22
21
 
23
- it 'slicing returns a collection of test cases' do
24
- slice_output = slicer.slice(test_file)
22
+ describe 'output' do
23
+
24
+ it 'slicing returns a collection of test source lines' do
25
+ slice_output = slicer.slice(test_file, :file_line)
26
+
27
+ expect(slice_output).to be_an(Array)
28
+ expect(slice_output).to_not be_empty
29
+
30
+ slice_output.each do |test_case|
31
+ # Test cases come in 'file_path:line_number' format
32
+ expect(test_case).to match(/^.+:\d+$/)
33
+ end
34
+ end
35
+
36
+ it 'slicing returns a collection of test objects' do
37
+ slice_output = slicer.slice(test_file, :test_object)
38
+
39
+ expect(slice_output).to be_an(Array)
40
+ expect(slice_output).to_not be_empty
25
41
 
26
- expect(slice_output).to be_an(Array)
27
- expect(slice_output).to_not be_empty
42
+ slice_output.each do |test_case|
43
+ # Test cases come as test objects
44
+ expect(test_case).to be_a(CukeModeler::Scenario).or be_a(CukeModeler::Row)
45
+ end
46
+ end
28
47
 
29
- slice_output.each do |test_case|
30
- # Test cases come in 'file_path:line_number' format
31
- expect(test_case).to match(/^.+:\d+$/)
48
+ it 'complains if told to provide output in an unknown format' do
49
+ expect { slicer.slice(test_file, :bad_option) }.to raise_error(ArgumentError, /Invalid Output Format/)
32
50
  end
51
+
33
52
  end
34
53
 
35
54
  it 'can slice without being provided filters' do
36
- expect { slicer.slice(test_file) }.to_not raise_error
55
+ expect { slicer.slice(test_file, :file_line) }.to_not raise_error
37
56
  end
38
57
 
39
58
  it 'uses the custom filter, if provided' do
40
- expect { |test_block| slicer.slice(@default_file_directory, &test_block) }.to yield_control
41
- expect { slicer.slice(@default_file_directory) }.to_not raise_error
59
+ expect { |test_block| slicer.slice(@default_file_directory, :file_line, &test_block) }.to yield_control
60
+ expect { slicer.slice(@default_file_directory, :file_line) }.to_not raise_error
42
61
  end
43
62
 
44
63
  it 'can slice an empty feature file' do
45
64
  File.open(test_file, 'w') { |file| file.write('') }
46
65
 
47
- expect { slicer.slice(test_file) }.to_not raise_error
66
+ expect { slicer.slice(test_file, :file_line) }.to_not raise_error
48
67
  end
49
68
 
50
69
  it 'can slice a feature that has no tests' do
51
70
  File.open(test_file, 'w') { |file| file.write('Feature: Empty feature') }
52
71
 
53
- expect { slicer.slice(test_file) }.to_not raise_error
72
+ expect { slicer.slice(test_file, :file_line) }.to_not raise_error
54
73
  end
55
74
 
56
75
  it 'can slice a directory that contains non-feature files' do
57
76
  File.open("#{@default_file_directory}/not_a_feature.file", 'w') { |file| file.write('foobar') }
58
77
 
59
- expect { slicer.slice(@default_file_directory) }.to_not raise_error
78
+ expect { slicer.slice(@default_file_directory, :file_line) }.to_not raise_error
60
79
  end
61
80
 
62
81
 
63
82
  describe 'target validation' do
64
83
 
65
84
  it 'complains if told to slice a non-existent location' do
66
- expect { slicer.slice('does/not/exist') }.to raise_error(ArgumentError, /does not exist/)
67
- expect { slicer.slice(nil) }.to raise_error(ArgumentError, /does not exist/)
85
+ expect { slicer.slice('does/not/exist', :file_line) }.to raise_error(ArgumentError, /does not exist/)
86
+ expect { slicer.slice(nil, :file_line) }.to raise_error(ArgumentError, /does not exist/)
68
87
  end
69
88
 
70
89
  it 'complains if told to slice an incorrectly formatted feature file' do
71
90
  File.open(test_file, 'w') { |file| file.write('foobar') }
72
91
 
73
- expect { slicer.slice(test_file) }.to raise_error(ArgumentError, /syntax.*lexing problem.*#{test_file}/i)
92
+ expect { slicer.slice(test_file, :file_line) }.to raise_error(ArgumentError, /syntax.*lexing problem.*#{test_file}/i)
93
+ end
94
+
95
+ it 'does not swallow unexpected exceptions while slicing' do
96
+ begin
97
+ $old_method = CukeModeler::Parsing.method(:parse_text)
98
+
99
+ # Custom error type in order to ensure that we are throwing the correct thing
100
+ module CukeSlicer
101
+ class TestError < StandardError
102
+ end
103
+ end
104
+
105
+ # Monkey patch the parsing method to throw the error that we need for testing
106
+ module CukeModeler
107
+ module Parsing
108
+ class << self
109
+ def parse_text(*args)
110
+ raise(CukeSlicer::TestError, 'something went wrong')
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+ File.write(test_file, 'junk text')
118
+
119
+ expect { slicer.slice(test_file, :file_line) }.to raise_error(CukeSlicer::TestError, 'something went wrong')
120
+ ensure
121
+ # Making sure that our changes don't escape a test and ruin the rest of the suite
122
+ module CukeModeler
123
+ module Parsing
124
+ class << self
125
+ define_method(:parse_text, $old_method)
126
+ end
127
+ end
128
+ end
129
+ end
74
130
  end
75
131
 
76
132
  end
@@ -84,13 +140,13 @@ describe 'Slicer, Integration' do
84
140
  filters = clazz.known_filters
85
141
 
86
142
  filters.each do |filter|
87
- not_provided = slicer.slice(test_file)
143
+ not_provided = slicer.slice(test_file, :file_line)
88
144
 
89
145
  case
90
146
  when filter.to_s =~ /path/
91
- nothing_provided = slicer.slice(test_file, filter => [])
147
+ nothing_provided = slicer.slice(test_file, {filter => []}, :file_line)
92
148
  when filter.to_s =~ /tag/
93
- nothing_provided = slicer.slice(test_file, filter => [])
149
+ nothing_provided = slicer.slice(test_file, {filter => []}, :file_line)
94
150
  else
95
151
  raise(ArgumentError, "Unknown filter '#{filter}'")
96
152
  end
@@ -114,7 +170,7 @@ describe 'Slicer, Integration' do
114
170
  expect(applied_filters.keys).to match_array(filters)
115
171
 
116
172
 
117
- expect { @slice_output = slicer.slice(@default_file_directory, applied_filters, &block_filter) }.to_not raise_error
173
+ expect { @slice_output = slicer.slice(@default_file_directory, applied_filters, :file_line, &block_filter) }.to_not raise_error
118
174
  expect(@slice_output).to be_an(Array)
119
175
  expect(@slice_output).to_not be_empty
120
176
  end
@@ -126,11 +182,11 @@ describe 'Slicer, Integration' do
126
182
  path_filter_types = clazz.known_filters.select { |filter| filter.to_s =~ /path/ }
127
183
 
128
184
  path_filter_types.each do |filter|
129
- expect { slicer.slice(@default_file_directory, filter => '@some_value') }.to_not raise_error
130
- expect { slicer.slice(@default_file_directory, filter => /some_pattern/) }.to_not raise_error
131
- expect { slicer.slice(@default_file_directory, filter => ['@some_value', /some_pattern/]) }.to_not raise_error
132
- expect { slicer.slice(@default_file_directory, filter => :something_else) }.to raise_error(ArgumentError, /must be a/i)
133
- expect { slicer.slice(@default_file_directory, filter => [:something_else]) }.to raise_error(ArgumentError, /must be a/i)
185
+ expect { slicer.slice(@default_file_directory, {filter => '@some_value'}, :file_line) }.to_not raise_error
186
+ expect { slicer.slice(@default_file_directory, {filter => /some_pattern/}, :file_line)}.to_not raise_error
187
+ expect { slicer.slice(@default_file_directory, {filter => ['@some_value', /some_pattern/]}, :file_line)}.to_not raise_error
188
+ expect { slicer.slice(@default_file_directory, {filter => :something_else}, :file_line) }.to raise_error(ArgumentError, /must be a/i)
189
+ expect { slicer.slice(@default_file_directory, {filter => [:something_else]}, :file_line) }.to raise_error(ArgumentError, /must be a/i)
134
190
  end
135
191
  end
136
192
 
@@ -138,13 +194,13 @@ describe 'Slicer, Integration' do
138
194
  tag_filter_types = clazz.known_filters.select { |filter| filter.to_s =~ /tag/ }
139
195
 
140
196
  tag_filter_types.each do |filter|
141
- expect { slicer.slice(@default_file_directory, filter => '@some_value') }.to_not raise_error
142
- expect { slicer.slice(@default_file_directory, filter => /some_pattern/) }.to_not raise_error
143
- expect { slicer.slice(@default_file_directory, filter => ['@some_value', /some_pattern/]) }.to_not raise_error
144
- expect { slicer.slice(@default_file_directory, filter => ['@some_value', [/nested_pattern/]]) }.to_not raise_error
145
- expect { slicer.slice(@default_file_directory, filter => ['@some_value', [/nested_pattern/, :bad_value]]) }.to raise_error(ArgumentError, /must be a/i)
146
- expect { slicer.slice(@default_file_directory, filter => :something_else) }.to raise_error(ArgumentError, /must be a/i)
147
- expect { slicer.slice(@default_file_directory, filter => [:something_else]) }.to raise_error(ArgumentError, /must be a/i)
197
+ expect { slicer.slice(@default_file_directory, {filter => '@some_value'}, :file_line) }.to_not raise_error
198
+ expect { slicer.slice(@default_file_directory, {filter => /some_pattern/}, :file_line) }.to_not raise_error
199
+ expect { slicer.slice(@default_file_directory, {filter => ['@some_value', /some_pattern/]}, :file_line) }.to_not raise_error
200
+ expect { slicer.slice(@default_file_directory, {filter => ['@some_value', [/nested_pattern/]]}, :file_line) }.to_not raise_error
201
+ expect { slicer.slice(@default_file_directory, {filter => ['@some_value', [/nested_pattern/, :bad_value]]}, :file_line) }.to raise_error(ArgumentError, /must be a/i)
202
+ expect { slicer.slice(@default_file_directory, {filter => :something_else}, :file_line) }.to raise_error(ArgumentError, /must be a/i)
203
+ expect { slicer.slice(@default_file_directory, {filter => [:something_else]}, :file_line) }.to raise_error(ArgumentError, /must be a/i)
148
204
  end
149
205
  end
150
206
 
@@ -152,8 +208,8 @@ describe 'Slicer, Integration' do
152
208
  tag_filter_types = clazz.known_filters.select { |filter| filter.to_s =~ /tag/ }
153
209
 
154
210
  tag_filter_types.each do |filter|
155
- expect { slicer.slice(@default_file_directory, filter => ['@some_value', [/nested_pattern/]]) }.to_not raise_error
156
- expect { slicer.slice(@default_file_directory, filter => ['@some_value', [/nested_pattern/, ['way_too_nested']]]) }.to raise_error(ArgumentError, /cannot.* nested/i)
211
+ expect { slicer.slice(@default_file_directory, {filter => ['@some_value', [/nested_pattern/]]}, :file_line) }.to_not raise_error
212
+ expect { slicer.slice(@default_file_directory, {filter => ['@some_value', [/nested_pattern/, ['way_too_nested']]]}, :file_line) }.to raise_error(ArgumentError, /cannot.* nested/i)
157
213
  end
158
214
  end
159
215
 
@@ -161,11 +217,63 @@ describe 'Slicer, Integration' do
161
217
  unknown_filter_type = :unknown_filter
162
218
  options = {unknown_filter_type => 'foo'}
163
219
 
164
- expect { slicer.slice(@default_file_directory, options) }.to raise_error(ArgumentError, /unknown filter.*#{unknown_filter_type}/i)
220
+ expect { slicer.slice(@default_file_directory, options, :file_line) }.to raise_error(ArgumentError, /unknown filter.*#{unknown_filter_type}/i)
165
221
  end
166
222
 
167
223
  end
168
224
 
169
225
  end
170
226
 
227
+ describe "bugs that we don't want to happen again" do
228
+
229
+
230
+ # As a nested directory structure was being traversed for slicing, the extraction algorithm was mangling the
231
+ # current file path such that it would sometimes attempt to search non-existent locations. Sometimes this
232
+ # resulted in an exception and sometimes this resulted in files getting silently skipped over.
233
+
234
+
235
+ it 'can handle a realistically nested directory structure' do
236
+ root_directory = @default_file_directory
237
+
238
+ # Outer 'before' hook already makes a root level feature file
239
+ expected_tests = ["#{test_file}:4"]
240
+
241
+ # Adding a nested directory
242
+ nested_directory_1 = "#{root_directory}/nested_directory_1"
243
+ FileUtils.mkpath(nested_directory_1)
244
+ test_file = "#{nested_directory_1}/nested_file_1.feature"
245
+ File.write(test_file, test_file_text)
246
+ expected_tests << "#{test_file}:4"
247
+ test_file = "#{nested_directory_1}/nested_file_2.feature"
248
+ File.write(test_file, test_file_text)
249
+ expected_tests << "#{test_file}:4"
250
+
251
+ # And another one
252
+ nested_directory_2 = "#{root_directory}/nested_directory_2"
253
+ FileUtils.mkpath(nested_directory_2)
254
+ test_file = "#{nested_directory_2}/nested_file_1.feature"
255
+ File.write(test_file, test_file_text)
256
+ expected_tests << "#{test_file}:4"
257
+ test_file = "#{nested_directory_2}/nested_file_2.feature"
258
+ File.write(test_file, test_file_text)
259
+ expected_tests << "#{test_file}:4"
260
+
261
+ # And one of them has another directory inside of it
262
+ doubly_nested_directory = "#{nested_directory_1}/doubly_nested_directory"
263
+ FileUtils.mkpath(doubly_nested_directory)
264
+ test_file = "#{doubly_nested_directory}/doubly_nested_file_1.feature"
265
+ File.write(test_file, test_file_text)
266
+ expected_tests << "#{test_file}:4"
267
+ test_file = "#{doubly_nested_directory}/doubly_nested_file_2.feature"
268
+ File.write(test_file, test_file_text)
269
+ expected_tests << "#{test_file}:4"
270
+
271
+
272
+ # No problems, no missed files
273
+ expect { @slice_output = slicer.slice(root_directory, :file_line) }.to_not raise_error
274
+ expect(@slice_output).to match_array(expected_tests)
275
+ end
276
+
277
+ end
278
+
171
279
  end