cucumber-core 4.0.0 → 5.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.
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+ require 'cucumber/core/test/location'
3
+ require 'cucumber/core/test/doc_string'
4
+ require 'unindent'
5
+
6
+ module Cucumber
7
+ module Core
8
+ module Test
9
+ describe DocString do
10
+ let(:location) { double }
11
+ let(:doc_string) { DocString.new(content, content_type, location) }
12
+
13
+ describe "#data_table?" do
14
+ let(:doc_string) { DocString.new("test", "text/plain" , location) }
15
+
16
+ it "returns false" do
17
+ expect(doc_string).not_to be_data_table
18
+ end
19
+ end
20
+
21
+ describe "#doc_string" do
22
+ let(:doc_string) { DocString.new("test", "text/plain" , location) }
23
+
24
+ it "returns true" do
25
+ expect(doc_string).to be_doc_string
26
+ end
27
+ end
28
+
29
+ context '#map' do
30
+ let(:content) { 'original content' }
31
+ let(:content_type) { double }
32
+
33
+ it 'yields with the content' do
34
+ expect { |b| doc_string.map(&b) }.to yield_with_args(content)
35
+ end
36
+
37
+ it 'returns a new docstring with new content' do
38
+ expect( doc_string.map { 'foo' }.content ).to eq 'foo'
39
+ end
40
+
41
+ it 'raises an error if no block is given' do
42
+ expect { doc_string.map }.to raise_error ArgumentError
43
+ end
44
+ end
45
+
46
+ context 'equality' do
47
+ let(:content) { 'foo' }
48
+ let(:content_type) { 'text/plain' }
49
+
50
+ it 'is equal to another DocString with the same content and content_type' do
51
+ expect( doc_string ).to eq DocString.new(content, content_type, location)
52
+ end
53
+
54
+ it 'is not equal to another DocString with different content' do
55
+ expect( doc_string ).not_to eq DocString.new('bar', content_type, location)
56
+ end
57
+
58
+ it 'is not equal to another DocString with different content_type' do
59
+ expect( doc_string ).not_to eq DocString.new(content, 'text/html', location)
60
+ end
61
+
62
+ it 'is equal to a string with the same content' do
63
+ expect( doc_string ).to eq 'foo'
64
+ end
65
+
66
+ it 'returns false when compared with something odd' do
67
+ expect( doc_string ).not_to eq 5
68
+ end
69
+ end
70
+
71
+ context 'quacking like a String' do
72
+ let(:content) { String.new('content') }
73
+ let(:content_type) { 'text/plain' }
74
+
75
+ it 'delegates #encoding to the content string' do
76
+ content.force_encoding('us-ascii')
77
+ expect( doc_string.encoding ).to eq Encoding.find('US-ASCII')
78
+ end
79
+
80
+ it 'allows implicit conversion to a String' do
81
+ expect( 'expected content' ).to include(doc_string)
82
+ end
83
+
84
+ it 'allows explicit conversion to a String' do
85
+ expect( doc_string.to_s ).to eq 'content'
86
+ end
87
+
88
+ it 'delegates #gsub to the content string' do
89
+ expect( doc_string.gsub(/n/, '_') ).to eq 'co_te_t'
90
+ end
91
+
92
+ it 'delegates #split to the content string' do
93
+ expect(doc_string.split('n')).to eq ['co', 'te', 't']
94
+ end
95
+ end
96
+ end
97
+
98
+ context "inspect" do
99
+ let(:location) { Test::Location.new("features/feature.feature", 8) }
100
+ let(:content_type) { 'text/plain' }
101
+
102
+ it "provides a useful inspect method" do
103
+ doc_string = DocString.new("some text", content_type, location)
104
+ expect(doc_string.inspect).to eq <<-END.chomp.unindent
105
+ #<Cucumber::Core::Test::DocString (features/feature.feature:8)
106
+ """text/plain
107
+ some text
108
+ """>
109
+ END
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+ require 'cucumber/core/test/result'
4
+ require 'rspec/expectations'
5
+
6
+ module Cucumber::Core::Test
7
+ RSpec::Matchers.define :be_duration do |expected|
8
+ match do |actual|
9
+ actual.tap { |duration| @nanoseconds = duration.nanoseconds }
10
+ @nanoseconds == expected
11
+ end
12
+ end
13
+
14
+ RSpec::Matchers.define :an_unknown_duration do
15
+ match do |actual|
16
+ actual.tap { raise "#tap block was executed, not an UnknownDuration" }
17
+ expect(actual).to respond_to(:nanoseconds)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'cucumber/core/test/location'
3
+ require 'cucumber/core/test/empty_multiline_argument'
4
+
5
+ module Cucumber
6
+ module Core
7
+ module Test
8
+ describe EmptyMultilineArgument do
9
+
10
+ let(:location) { double }
11
+ let(:arg) { EmptyMultilineArgument.new }
12
+
13
+ describe "#data_table?" do
14
+ it "returns false" do
15
+ expect(arg).not_to be_data_table
16
+ end
17
+ end
18
+
19
+ describe "#doc_string" do
20
+ it "returns false" do
21
+ expect(arg).not_to be_doc_string
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,271 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'cucumber/core/gherkin/writer'
4
+ require 'cucumber/core'
5
+ require 'cucumber/core/test/filters/locations_filter'
6
+ require 'timeout'
7
+ require 'cucumber/core/test/location'
8
+
9
+ module Cucumber::Core
10
+ describe Test::LocationsFilter do
11
+ include Cucumber::Core::Gherkin::Writer
12
+ include Cucumber::Core
13
+
14
+ let(:receiver) { SpyReceiver.new }
15
+
16
+ let(:doc) do
17
+ gherkin('features/test.feature') do
18
+ feature do
19
+ scenario 'x' do
20
+ step 'a step'
21
+ end
22
+
23
+ scenario 'y' do
24
+ step 'a step'
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ it "sorts by the given locations" do
31
+ locations = [
32
+ Test::Location.new('features/test.feature', 6),
33
+ Test::Location.new('features/test.feature', 3)
34
+ ]
35
+ filter = Test::LocationsFilter.new(locations)
36
+ compile [doc], receiver, [filter]
37
+ expect(receiver.test_case_locations).to eq locations
38
+ end
39
+
40
+ it "works with wildcard locations" do
41
+ locations = [
42
+ Test::Location.new('features/test.feature')
43
+ ]
44
+ filter = Test::LocationsFilter.new(locations)
45
+ compile [doc], receiver, [filter]
46
+ expect(receiver.test_case_locations).to eq [
47
+ Test::Location.new('features/test.feature', 3),
48
+ Test::Location.new('features/test.feature', 6)
49
+ ]
50
+ end
51
+
52
+ it "filters out scenarios that don't match" do
53
+ locations = [
54
+ Test::Location.new('features/test.feature', 3)
55
+ ]
56
+ filter = Test::LocationsFilter.new(locations)
57
+ compile [doc], receiver, [filter]
58
+ expect(receiver.test_case_locations).to eq locations
59
+ end
60
+
61
+ describe "matching location" do
62
+ let(:file) { 'features/path/to/the.feature' }
63
+
64
+ let(:test_cases) do
65
+ receiver = double.as_null_object
66
+ result = []
67
+ allow(receiver).to receive(:test_case) { |test_case| result << test_case }
68
+ compile [doc], receiver
69
+ result
70
+ end
71
+
72
+ context "for a scenario" do
73
+ let(:doc) do
74
+ Gherkin::Document.new(file, <<-END)
75
+ Feature:
76
+
77
+ Scenario: one
78
+ Given one a
79
+
80
+ # comment
81
+ @tags
82
+ Scenario: two
83
+ Given two a
84
+ And two b
85
+
86
+ Scenario: three
87
+ Given three b
88
+
89
+ Scenario: with docstring
90
+ Given a docstring
91
+ """
92
+ this is a docstring
93
+ """
94
+
95
+ Scenario: with a table
96
+ Given a table
97
+ | a | b |
98
+ | 1 | 2 |
99
+ | 3 | 4 |
100
+
101
+ END
102
+ end
103
+
104
+ def test_case_named(name)
105
+ test_cases.find { |c| c.name == name }
106
+ end
107
+
108
+ it 'matches the precise location of the scenario' do
109
+ location = test_case_named('two').location
110
+ filter = Test::LocationsFilter.new([location])
111
+ compile [doc], receiver, [filter]
112
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
113
+ end
114
+
115
+ it 'matches multiple locations' do
116
+ good_location = Test::Location.new(file, 8)
117
+ bad_location = Test::Location.new(file, 5)
118
+ filter = Test::LocationsFilter.new([good_location, bad_location])
119
+ compile [doc], receiver, [filter]
120
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
121
+ end
122
+
123
+ it "doesn't match a location after the scenario line" do
124
+ location = Test::Location.new(file, 9)
125
+ filter = Test::LocationsFilter.new([location])
126
+ compile [doc], receiver, [filter]
127
+ expect(receiver.test_case_locations).to eq []
128
+ end
129
+
130
+ it "doesn't match a location before the scenario line" do
131
+ location = Test::Location.new(file, 7)
132
+ filter = Test::LocationsFilter.new([location])
133
+ compile [doc], receiver, [filter]
134
+ expect(receiver.test_case_locations).to eq []
135
+ end
136
+
137
+ context "with duplicate locations in the filter" do
138
+ it "matches each test case only once" do
139
+ location_tc_two = test_case_named('two').location
140
+ location_tc_one = test_case_named('one').location
141
+ location_last_step_tc_two = Test::Location.new(file, 10)
142
+ filter = Test::LocationsFilter.new([location_tc_two, location_tc_one, location_last_step_tc_two])
143
+ compile [doc], receiver, [filter]
144
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location, location_tc_one = test_case_named('one').location]
145
+ end
146
+ end
147
+ end
148
+
149
+ context "for a scenario outline" do
150
+ let(:doc) do
151
+ Gherkin::Document.new(file, <<-END)
152
+ Feature:
153
+
154
+ Scenario: one
155
+ Given one a
156
+
157
+ # comment on line 6
158
+ @tags-on-line-7
159
+ Scenario Outline: two <arg>
160
+ Given two a
161
+ And two <arg>
162
+ """
163
+ docstring
164
+ """
165
+
166
+ # comment on line 15
167
+ @tags-on-line-16
168
+ Examples: x1
169
+ | arg |
170
+ | b |
171
+
172
+ Examples: x2
173
+ | arg |
174
+ | c |
175
+ | d |
176
+
177
+ Scenario: three
178
+ Given three b
179
+ END
180
+ end
181
+
182
+ let(:test_case) do
183
+ test_cases.find { |c| c.name == "two b" }
184
+ end
185
+
186
+ it "matches row location to the test case of the row" do
187
+ locations = [
188
+ Test::Location.new(file, 19),
189
+ ]
190
+ filter = Test::LocationsFilter.new(locations)
191
+ compile [doc], receiver, [filter]
192
+ expect(receiver.test_case_locations).to eq [test_case.location]
193
+ end
194
+
195
+ it "matches outline location with the all test cases of all the tables" do
196
+ locations = [
197
+ Test::Location.new(file, 8),
198
+ ]
199
+ filter = Test::LocationsFilter.new(locations)
200
+ compile [doc], receiver, [filter]
201
+ expect(receiver.test_case_locations.map(&:line)).to eq [19, 23, 24]
202
+ end
203
+
204
+ it "doesn't match the location of the examples line" do
205
+ location = Test::Location.new(file, 17)
206
+ filter = Test::LocationsFilter.new([location])
207
+ compile [doc], receiver, [filter]
208
+ expect(receiver.test_case_locations).to eq []
209
+ end
210
+ end
211
+ end
212
+
213
+ context "under load", slow: true do
214
+ num_features = 50
215
+ num_scenarios_per_feature = 50
216
+
217
+ let(:docs) do
218
+ (1..num_features).map do |i|
219
+ gherkin("features/test_#{i}.feature") do
220
+ feature do
221
+ (1..num_scenarios_per_feature).each do |j|
222
+ scenario "scenario #{j}" do
223
+ step 'text'
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ num_locations = num_features
232
+ let(:locations) do
233
+ (1..num_locations).map do |i|
234
+ (1..num_scenarios_per_feature).map do |j|
235
+ line = 3 + (j - 1) * 3
236
+ Test::Location.new("features/test_#{i}.feature", line)
237
+ end
238
+ end.flatten
239
+ end
240
+
241
+ max_duration_ms = 10000
242
+ max_duration_ms = max_duration_ms * 2.5 if defined?(JRUBY_VERSION)
243
+ it "filters #{num_features * num_scenarios_per_feature} test cases within #{max_duration_ms}ms" do
244
+ filter = Test::LocationsFilter.new(locations)
245
+ Timeout.timeout(max_duration_ms / 1000.0) do
246
+ compile docs, receiver, [filter]
247
+ end
248
+ expect(receiver.test_cases.length).to eq num_features * num_scenarios_per_feature
249
+ end
250
+
251
+ end
252
+ end
253
+
254
+ class SpyReceiver
255
+ def test_case(test_case)
256
+ test_cases << test_case
257
+ end
258
+
259
+ def done
260
+ end
261
+
262
+ def test_case_locations
263
+ test_cases.map(&:location)
264
+ end
265
+
266
+ def test_cases
267
+ @test_cases ||= []
268
+ end
269
+
270
+ end
271
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+ require 'cucumber/core/test/location'
3
+
4
+ module Cucumber::Core::Test
5
+ RSpec::Matchers.define :be_included_in do |expected|
6
+ match do |actual|
7
+ expected.include? actual
8
+ end
9
+ end
10
+
11
+ describe Location do
12
+ let(:line) { 12 }
13
+ let(:file) { "foo.feature" }
14
+
15
+ describe "equality" do
16
+ it "is equal to another Location on the same line of the same file" do
17
+ one_location = Location.new(file, line)
18
+ another_location = Location.new(file, line)
19
+ expect( one_location ).to eq another_location
20
+ end
21
+
22
+ it "is not equal to a wild card of the same file" do
23
+ expect( Location.new(file, line) ).not_to eq Location.new(file)
24
+ end
25
+
26
+ context "collections of locations" do
27
+ it "behave as expected with uniq" do
28
+ unique_collection = [Location.new(file, line), Location.new(file, line)].uniq
29
+ expect( unique_collection ).to eq [Location.new(file, line)]
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "to_s" do
35
+ it "is file:line for a precise location" do
36
+ expect( Location.new("foo.feature", 12).to_s ).to eq "foo.feature:12"
37
+ end
38
+
39
+ it "is file for a wildcard location" do
40
+ expect( Location.new("foo.feature").to_s ).to eq "foo.feature"
41
+ end
42
+
43
+ it "is file:first_line..last_line for a ranged location" do
44
+ expect( Location.new("foo.feature", 13..19).to_s ).to eq "foo.feature:13..19"
45
+ end
46
+
47
+ it "is file:line:line:line for an arbitrary set of lines" do
48
+ expect( Location.new("foo.feature", [1,3,5]).to_s ).to eq "foo.feature:1:3:5"
49
+ end
50
+ end
51
+
52
+ describe "matches" do
53
+ let(:matching) { Location.new(file, line) }
54
+ let(:same_file_other_line) { Location.new(file, double) }
55
+ let(:not_matching) { Location.new(other_file, line) }
56
+ let(:other_file) { double }
57
+
58
+ context 'a precise location' do
59
+ let(:precise) { Location.new(file, line) }
60
+
61
+ it "matches a precise location of the same file and line" do
62
+ expect( matching ).to be_match(precise)
63
+ end
64
+
65
+ it "does not match a precise location on a different line in the same file" do
66
+ expect( matching ).not_to be_match(same_file_other_line)
67
+ end
68
+
69
+ end
70
+
71
+ context 'a wildcard' do
72
+ let(:wildcard) { Location.new(file) }
73
+
74
+ it "matches any location with the same filename" do
75
+ expect( wildcard ).to be_match(matching)
76
+ end
77
+
78
+ it "is matched by any location of the same file" do
79
+ expect( matching ).to be_match(wildcard)
80
+ end
81
+
82
+ it "does not match a location in a different file" do
83
+ expect( wildcard ).not_to be_match(not_matching)
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "created from source location" do
89
+ context "when the location is in the tree below pwd" do
90
+ it "create a relative path from pwd" do
91
+ expect( Location.from_source_location(Dir.pwd + "/path/file.rb", 1).file ).to eq "path/file.rb"
92
+ end
93
+ end
94
+
95
+ context "when the location is in an installed gem" do
96
+ it "create a relative path from the gem directory" do
97
+ expect( Location.from_source_location("/path/gems/gem-name/path/file.rb", 1).file ).to eq "gem-name/path/file.rb"
98
+ end
99
+ end
100
+
101
+ context "when the location is neither below pwd nor in an installed gem" do
102
+ it "use the absolute path to the file" do
103
+ expect( Location.from_source_location("/path/file.rb", 1).file ).to eq "/path/file.rb"
104
+ end
105
+ end
106
+ end
107
+
108
+ describe "created from file-colon-line" do
109
+ it "handles also Windows paths" do
110
+ expect( Location.from_file_colon_line("c:\path\file.rb:123").file ).to eq "c:\path\file.rb"
111
+ end
112
+ end
113
+
114
+ describe "created of caller" do
115
+ it "use the location of the caller" do
116
+ expect( Location.of_caller.to_s ).to be_included_in caller[0]
117
+ end
118
+
119
+ context "when specifying additional caller depth"do
120
+ it "use the location of the n:th caller" do
121
+ expect( Location.of_caller(1).to_s ).to be_included_in caller[1]
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end