cucumber-core 4.0.0 → 5.0.0

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