cucumber-core 1.3.0 → 1.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f56999cf96221b9de93c823a5559efeb56bfb34
4
- data.tar.gz: 1e955adc2d60f414a53e6775d0e6adc44b4eafa5
3
+ metadata.gz: 25825fee710c84b86d2a37eed2006660a2a60df0
4
+ data.tar.gz: 4515e946a6558a3aa42509da7380f61782b7e43f
5
5
  SHA512:
6
- metadata.gz: 529edb34199a3bea271b93d0f09de3e4b2c24dc7f4c198ff553fe029b7d0b5c1a7a4a18d744586725351d5e1f9795c6caec1e8e35b64e2c77eab858ad3d8052e
7
- data.tar.gz: 42aa0c463801d3aaba42d95c2bbdd0519a8d5344da0ef6c594d7a6bd704329947c5265aea47e3eee601e20c043fac8ca31b25afc5e75a9d1630b85ead562bd80
6
+ metadata.gz: a1fa91c0ea24501ab7d57a14c456e39eee87ced703456076e9b8dc16c191697d27fbeab1e75bd85bd769983bdbd5f4d14e80aeafb8d04e12175943203f06a24b
7
+ data.tar.gz: e4aff4a98a4702419bc43a5818ec3253deb5a075c71fcadb9f10826cbd3376199132cf65ad3b16761b11785e45bfffea5c94180756c033231dcb00fee0e20f1e
data/.rspec CHANGED
@@ -1 +1 @@
1
- --color
1
+ --color --tag ~slow
@@ -1,3 +1,5 @@
1
+ sudo: false
2
+
1
3
  rvm:
2
4
  - 2.2
3
5
  - 2.1
@@ -13,5 +15,6 @@ branches:
13
15
  notifications:
14
16
  email:
15
17
  - cukes-devs@googlegroups.com
16
- irc:
17
- - "irc.freenode.org#cucumber"
18
+ webhooks:
19
+ urls: # gitter
20
+ - https://webhooks.gitter.im/e/dc010332f9d40fcc21c4
data/HISTORY.md CHANGED
@@ -1,4 +1,18 @@
1
- ## [In Git](https://github.com/cucumber/cucumber-ruby-core/compare/v1.2.0...master)
1
+ ## [In Git](https://github.com/cucumber/cucumber-ruby-core/compare/v1.3.0...master)
2
+
3
+ ### New Features
4
+
5
+ ### Bugfixes
6
+
7
+ ## [v1.3.1](https://github.com/cucumber/cucumber-ruby-core/compare/v1.3.0...v1.3.1)
8
+
9
+ ### New Features
10
+
11
+ ### Bugfixes
12
+
13
+ * Speed up location filtering ([99](https://github.com/cucumber/cucumber-ruby-core/issues/99) @mattwynne @akostadinov @brasmusson)
14
+
15
+ ## [v1.3.0](https://github.com/cucumber/cucumber-ruby-core/compare/v1.2.0...v1.3.0)
2
16
 
3
17
  ### New Features
4
18
 
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # cucumber-core
2
2
 
3
+ [![Chat with us](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cucumber/cucumber-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3
4
  [![Build Status](https://secure.travis-ci.org/cucumber/cucumber-ruby-core.png)](http://travis-ci.org/cucumber/cucumber-ruby-core)
4
5
  [![Code Climate](https://codeclimate.com/github/cucumber/cucumber-ruby-core.png)](https://codeclimate.com/github/cucumber/cucumber-ruby-core)
5
6
  [![Coverage Status](https://coveralls.io/repos/cucumber/cucumber-ruby-core/badge.png?branch=master)](https://coveralls.io/r/cucumber/cucumber-ruby-core?branch=master)
6
7
  [![Dependency Status](https://gemnasium.com/cucumber/cucumber-ruby-core.png)](https://gemnasium.com/cucumber/cucumber-ruby-core)
7
8
 
8
- Cucumber Core is the [inner hexagon](http://alistair.cockburn.us/Hexagonal+architecture) for the [Ruby flavour of Cucumber](https://github.com/cucumber/cucumber).
9
+ Cucumber Core is the [inner hexagon](http://alistair.cockburn.us/Hexagonal+architecture) for the [Ruby flavour of Cucumber](https://github.com/cucumber/cucumber-ruby).
9
10
 
10
11
  It contains the core domain logic to execute Cucumber features. It has no user interface, just a Ruby API. If you're interested in how Cucumber works, or in building other tools that work with Gherkin documents, you've come to the right place.
11
12
 
data/Rakefile CHANGED
@@ -8,7 +8,6 @@ $:.unshift File.expand_path("../lib", __FILE__)
8
8
  require "rspec/core/rake_task"
9
9
  RSpec::Core::RakeTask.new(:spec) do |t|
10
10
  t.ruby_opts = %w[-r./spec/coverage -w]
11
- t.rspec_opts = %w[--color]
12
11
  end
13
12
 
14
13
  require_relative 'spec/capture_warnings'
@@ -19,6 +18,11 @@ namespace :spec do
19
18
  Rake::Task['spec'].invoke
20
19
  end
21
20
  end
21
+
22
+ desc "run (slow) performance tests"
23
+ RSpec::Core::RakeTask.new(:slow) do |t|
24
+ t.rspec_opts = %w[--tag slow]
25
+ end
22
26
  end
23
27
 
24
- task default: ['spec:warnings']
28
+ task default: ['spec:warnings', 'spec:slow']
@@ -18,8 +18,8 @@ module Cucumber
18
18
  self
19
19
  end
20
20
 
21
- def match_locations?(*args)
22
- false
21
+ def all_locations
22
+ []
23
23
  end
24
24
 
25
25
  def to_sexp
@@ -66,6 +66,7 @@ module Cucumber
66
66
  end
67
67
  end
68
68
 
69
+ require 'delegate'
69
70
  class LanguageDelegator < SimpleDelegator
70
71
  attr_reader :iso_code
71
72
 
@@ -3,15 +3,9 @@ require 'cucumber/core/platform'
3
3
  module Cucumber
4
4
  module Core
5
5
  module Ast
6
- class Location < Struct.new(:filepath, :lines)
7
- WILDCARD = :*
6
+ IncompatibleLocations = Class.new(StandardError)
8
7
 
9
- extend Forwardable
10
-
11
- def_delegator :lines, :include?
12
- def_delegator :lines, :line
13
- def_delegator :filepath, :same_as?
14
- def_delegator :filepath, :filename, :file
8
+ module Location
15
9
 
16
10
  def self.of_caller(additional_depth = 0)
17
11
  from_file_colon_line(*caller[1 + additional_depth])
@@ -34,62 +28,100 @@ module Cucumber
34
28
  new(file, line)
35
29
  end
36
30
 
37
- def initialize(filepath, raw_lines=WILDCARD)
38
- filepath || raise(ArgumentError, "file is mandatory")
39
- super(FilePath.new(filepath), Lines.new(raw_lines))
31
+ def self.new(file, raw_lines=nil)
32
+ file || raise(ArgumentError, "file is mandatory")
33
+ if raw_lines
34
+ Precise.new(file, Lines.new(raw_lines))
35
+ else
36
+ Wildcard.new(file)
37
+ end
40
38
  end
41
39
 
42
- def match?(other)
43
- other.same_as?(filepath) && other.include?(lines)
40
+ def self.merge(*locations)
41
+ locations.reduce do |a, b|
42
+ a + b
43
+ end
44
44
  end
45
45
 
46
- def to_s
47
- [filepath.to_s, lines.to_s].reject { |v| v == WILDCARD.to_s }.join(":")
48
- end
46
+ class Wildcard < Struct.new(:file)
47
+ def to_s
48
+ file
49
+ end
49
50
 
50
- def hash
51
- self.class.hash ^ to_s.hash
52
- end
51
+ def match?(other)
52
+ other.file == file
53
+ end
53
54
 
54
- def to_str
55
- to_s
55
+ def include?(lines)
56
+ true
57
+ end
56
58
  end
57
59
 
58
- def on_line(new_line)
59
- Location.new(filepath.filename, new_line)
60
- end
60
+ class Precise < Struct.new(:file, :lines)
61
+ def include?(other_lines)
62
+ lines.include?(other_lines)
63
+ end
61
64
 
62
- def inspect
63
- "<#{self.class}: #{to_s}>"
64
- end
65
+ def line
66
+ lines.first
67
+ end
65
68
 
66
- class FilePath < Struct.new(:filename)
67
- def same_as?(other)
68
- filename == other.filename
69
+ def match?(other)
70
+ return false unless other.file == file
71
+ other.include?(lines)
69
72
  end
70
73
 
71
74
  def to_s
72
- filename
75
+ [file, lines.to_s].join(":")
76
+ end
77
+
78
+ def hash
79
+ self.class.hash ^ to_s.hash
80
+ end
81
+
82
+ def to_str
83
+ to_s
84
+ end
85
+
86
+ def on_line(new_line)
87
+ Location.new(file, new_line)
88
+ end
89
+
90
+ def +(other)
91
+ raise IncompatibleLocations if file != other.file
92
+ Precise.new(file, lines + other.lines)
93
+ end
94
+
95
+ def inspect
96
+ "<#{self.class}: #{to_s}>"
73
97
  end
74
98
  end
75
99
 
76
100
  require 'set'
77
101
  class Lines < Struct.new(:data)
78
102
  protected :data
79
- attr_reader :line
80
103
 
81
104
  def initialize(raw_data)
82
105
  super Array(raw_data).to_set
83
- @line = data.first
106
+ end
107
+
108
+ def first
109
+ data.first
84
110
  end
85
111
 
86
112
  def include?(other)
87
- return true if (data|other.data).include?(WILDCARD)
88
113
  other.data.subset?(data) || data.subset?(other.data)
89
114
  end
90
115
 
116
+ def +(more_lines)
117
+ new_data = data + more_lines.data
118
+ self.class.new(new_data)
119
+ end
120
+
91
121
  def to_s
92
- boundary.join('..')
122
+ return first.to_s if data.length == 1
123
+ return "#{data.min}..#{data.max}" if range?
124
+ data.to_a.join(":")
93
125
  end
94
126
 
95
127
  def inspect
@@ -98,20 +130,8 @@ module Cucumber
98
130
 
99
131
  protected
100
132
 
101
- def boundary
102
- first_and_last(value).uniq
103
- end
104
-
105
- def at_index(idx)
106
- data.to_a[idx]
107
- end
108
-
109
- def value
110
- method :at_index
111
- end
112
-
113
- def first_and_last(something)
114
- [0, -1].map(&something)
133
+ def range?
134
+ data.size == (data.max - data.min + 1)
115
135
  end
116
136
  end
117
137
  end
@@ -134,9 +154,8 @@ module Cucumber
134
154
  @location
135
155
  end
136
156
 
137
- def match_locations?(queried_locations)
138
- return true if attributes.any? { |node| node.match_locations? queried_locations }
139
- queried_locations.any? { |queried_location| queried_location.match? location }
157
+ def all_locations
158
+ @all_locations ||= Location.merge([location] + attributes.map { |node| node.all_locations }.flatten)
140
159
  end
141
160
 
142
161
  def attributes
@@ -4,11 +4,6 @@ module Cucumber
4
4
  module Names
5
5
  attr_reader :description, :name
6
6
 
7
- def title
8
- warn("deprecated. Use #name")
9
- name
10
- end
11
-
12
7
  def legacy_conflated_name_and_description
13
8
  s = name
14
9
  s += "\n#{@description}" if @description != ""
@@ -56,11 +56,8 @@ module Cucumber
56
56
  @outline_step, @language, @location, @comments, @keyword, @name, @multiline_arg = outline_step, language, location, comments, keyword, name, multiline_arg
57
57
  end
58
58
 
59
- alias :self_match_locations? :match_locations?
60
-
61
- def match_locations?(queried_locations)
62
- return true if self_match_locations?(queried_locations)
63
- @outline_step.match_locations?(queried_locations)
59
+ def all_locations
60
+ @outline_step.all_locations
64
61
  end
65
62
 
66
63
  alias :step_backtrace_line :backtrace_line
@@ -1,5 +1,6 @@
1
1
  require 'cucumber/core/test/result'
2
2
  require 'cucumber/core/gherkin/tag_expression'
3
+ require 'cucumber/core/ast/location'
3
4
 
4
5
  module Cucumber
5
6
  module Core
@@ -73,8 +74,21 @@ module Cucumber
73
74
  end
74
75
 
75
76
  def match_locations?(queried_locations)
76
- return true if source.any? { |s| s.match_locations?(queried_locations) }
77
- test_steps.any? { |node| node.match_locations? queried_locations }
77
+ queried_locations.any? { |queried_location|
78
+ all_source.any? { |node|
79
+ node.all_locations.any? { |location|
80
+ queried_location.match? location
81
+ }
82
+ }
83
+ }
84
+ end
85
+
86
+ def all_locations
87
+ @all_locations ||= Ast::Location.merge(all_source.map(&:all_locations).flatten)
88
+ end
89
+
90
+ def all_source
91
+ @all_source ||= (source + test_steps.map(&:source)).flatten.uniq
78
92
  end
79
93
 
80
94
  def inspect
@@ -5,10 +5,10 @@ module Cucumber
5
5
  module Test
6
6
 
7
7
  # Sorts and filters scenarios based on a list of locations
8
- class LocationsFilter < Filter.new(:locations)
8
+ class LocationsFilter < Filter.new(:filter_locations)
9
9
 
10
10
  def test_case(test_case)
11
- test_cases << test_case
11
+ test_cases[test_case.location.file] << test_case
12
12
  self
13
13
  end
14
14
 
@@ -23,19 +23,16 @@ module Cucumber
23
23
  private
24
24
 
25
25
  def sorted_test_cases
26
- locations.map { |location| test_cases_matching(location) }.flatten
27
- end
28
-
29
- def test_cases_matching(location)
30
- test_cases.select do |test_case|
31
- test_case.match_locations?([location])
32
- end
26
+ filter_locations.map { |filter_location|
27
+ test_cases[filter_location.file].select { |test_case|
28
+ test_case.all_locations.any? { |location| filter_location.match?(location) }
29
+ }
30
+ }.flatten.uniq
33
31
  end
34
32
 
35
33
  def test_cases
36
- @test_cases ||= []
34
+ @test_cases ||= Hash.new { |hash, key| hash[key] = [] }
37
35
  end
38
-
39
36
  end
40
37
  end
41
38
  end
@@ -47,11 +47,6 @@ module Cucumber
47
47
  @action.location
48
48
  end
49
49
 
50
- def match_locations?(queried_locations)
51
- return true if queried_locations.include? location
52
- source.any? { |s| s.match_locations?(queried_locations) }
53
- end
54
-
55
50
  def inspect
56
51
  "#<#{self.class}: #{location}>"
57
52
  end
@@ -2,7 +2,7 @@ module Cucumber
2
2
  module Core
3
3
  class Version
4
4
  def self.to_s
5
- "1.3.0"
5
+ "1.3.1"
6
6
  end
7
7
  end
8
8
  end
@@ -30,14 +30,6 @@ module Cucumber::Core::Ast
30
30
  end
31
31
  end
32
32
 
33
- describe "line" do
34
- it "is an integer" do
35
- expect(Location.new(file, line).line).to be_kind_of(Integer)
36
- expect(Location.new(file, 1..2).line).to be_kind_of(Integer)
37
- expect(Location.of_caller.line).to be_kind_of(Integer)
38
- end
39
- end
40
-
41
33
  describe "to_s" do
42
34
  it "is file:line for a precise location" do
43
35
  expect( Location.new("foo.feature", 12).to_s ).to eq "foo.feature:12"
@@ -50,6 +42,10 @@ module Cucumber::Core::Ast
50
42
  it "is file:first_line..last_line for a ranged location" do
51
43
  expect( Location.new("foo.feature", 13..19).to_s ).to eq "foo.feature:13..19"
52
44
  end
45
+
46
+ it "is file:line:line:line for an arbitrary set of lines" do
47
+ expect( Location.new("foo.feature", [1,3,5]).to_s ).to eq "foo.feature:1:3:5"
48
+ end
53
49
  end
54
50
 
55
51
  describe "matches" do
@@ -120,6 +116,22 @@ module Cucumber::Core::Ast
120
116
  expect( range ).not_to be_match(other)
121
117
  end
122
118
  end
119
+
120
+ context "an arbitrary list of lines" do
121
+ let(:location) { Location.new("foo.feature", [1,5,6,7]) }
122
+
123
+ it "matches any of the given lines" do
124
+ [1,5,6,7].each do |line|
125
+ other = Location.new("foo.feature", line)
126
+ expect(location).to be_match(other)
127
+ end
128
+ end
129
+
130
+ it "does not match another line" do
131
+ other = Location.new("foo.feature", 2)
132
+ expect(location).not_to be_match(other)
133
+ end
134
+ end
123
135
  end
124
136
 
125
137
  describe "created from source location" do
@@ -160,6 +172,27 @@ module Cucumber::Core::Ast
160
172
  end
161
173
  end
162
174
 
175
+ describe ".merge" do
176
+ it "merges locations from the same file" do
177
+ file = "test.feature"
178
+ location = Location.merge(
179
+ Location.new(file, 1),
180
+ Location.new(file, 6),
181
+ Location.new(file, 7)
182
+ )
183
+ expect(location.to_s).to eq "test.feature:1:6:7"
184
+ end
185
+
186
+ it "raises an error when the locations are from different files" do
187
+ expect {
188
+ Location.merge(
189
+ Location.new("one.feature", 1),
190
+ Location.new("two.feature", 1)
191
+ )
192
+ }.to raise_error(IncompatibleLocations)
193
+ end
194
+ end
195
+
163
196
  end
164
197
  end
165
198
 
@@ -148,16 +148,6 @@ module Cucumber
148
148
  end
149
149
  end
150
150
 
151
- describe "matching location" do
152
- let(:location) { double }
153
-
154
- it "also match the outline steps location" do
155
- allow( location).to receive(:any?).and_return(nil)
156
- expect( outline_step ).to receive(:match_locations?).with(location)
157
- step.match_locations?(location)
158
- end
159
- end
160
-
161
151
  describe "backtrace line" do
162
152
  let(:outline_step) { OutlineStep.new(double, "path/file.feature:5", double, "Given ", "this step <state>", double) }
163
153
  let(:step) { ExpandedOutlineStep.new(outline_step, double, "path/file.feature:10", double, "Given ", "this step passes", double) }
@@ -232,196 +232,6 @@ module Cucumber
232
232
  end
233
233
  end
234
234
 
235
- describe "matching location" do
236
- let(:file) { 'features/path/to/the.feature' }
237
- let(:test_cases) do
238
- receiver = double.as_null_object
239
- result = []
240
- allow( receiver ).to receive(:test_case) { |test_case| result << test_case }
241
- compile [source], receiver
242
- result
243
- end
244
-
245
- context "for a scenario" do
246
- let(:source) do
247
- Gherkin::Document.new(file, <<-END.unindent)
248
- Feature:
249
-
250
- Scenario: one
251
- Given one a
252
-
253
- # comment
254
- @tags
255
- Scenario: two
256
- Given two a
257
- And two b
258
-
259
- Scenario: three
260
- Given three b
261
-
262
- Scenario: with docstring
263
- Given a docstring
264
- """
265
- this is a docstring
266
- """
267
-
268
- Scenario: with a table
269
- Given a table
270
- | a | b |
271
- | 1 | 2 |
272
-
273
- Scenario: empty
274
- END
275
- end
276
-
277
- let(:test_case) do
278
- test_cases.find { |c| c.name == 'two' }
279
- end
280
-
281
- it 'matches the precise location of the scenario' do
282
- location = Ast::Location.new(file, 8)
283
- expect( test_case.match_locations?([location]) ).to be_truthy
284
- end
285
-
286
- it 'matches the precise location of an empty scenario' do
287
- empty_scenario_test_case = test_cases.find { |c| c.name == 'empty' }
288
- location = Ast::Location.new(file, 26)
289
- expect( empty_scenario_test_case.match_locations?([location]) ).to be_truthy
290
- end
291
-
292
- it 'matches multiple locations' do
293
- good_location = Ast::Location.new(file, 8)
294
- bad_location = Ast::Location.new(file, 5)
295
- expect( test_case.match_locations?([good_location, bad_location]) ).to be_truthy
296
- end
297
-
298
- it 'matches a location on the last step of the scenario' do
299
- location = Ast::Location.new(file, 10)
300
- expect( test_case.match_locations?([location]) ).to be_truthy
301
- end
302
-
303
- it "matches a location on the scenario's comment" do
304
- location = Ast::Location.new(file, 6)
305
- expect( test_case.match_locations?([location]) ).to be_truthy
306
- end
307
-
308
- it "matches a location on the scenario's tags" do
309
- location = Ast::Location.new(file, 7)
310
- expect( test_case.match_locations?([location]) ).to be_truthy
311
- end
312
-
313
- it "doesn't match a location after the last step of the scenario" do
314
- location = Ast::Location.new(file, 11)
315
- expect( test_case.match_locations?([location]) ).to be_falsey
316
- end
317
-
318
- it "doesn't match a location before the scenario" do
319
- location = Ast::Location.new(file, 5)
320
- expect( test_case.match_locations?([location]) ).to be_falsey
321
- end
322
-
323
- context "with a docstring" do
324
- let(:test_case) do
325
- test_cases.find { |c| c.name == 'with docstring' }
326
- end
327
-
328
- it "matches a location at the start the docstring" do
329
- location = Ast::Location.new(file, 17)
330
- expect( test_case.match_locations?([location]) ).to be_truthy
331
- end
332
-
333
- it "matches a location in the middle of the docstring" do
334
- location = Ast::Location.new(file, 18)
335
- expect( test_case.match_locations?([location]) ).to be_truthy
336
- end
337
-
338
- it "matches a location at the end of the docstring" do
339
- location = Ast::Location.new(file, 19)
340
- expect( test_case.match_locations?([location]) ).to be_truthy
341
- end
342
-
343
- it "does not match a location after the docstring" do
344
- location = Ast::Location.new(file, 20)
345
- expect( test_case.match_locations?([location]) ).to be_falsy
346
- end
347
- end
348
-
349
- context "with a table" do
350
- let(:test_case) do
351
- test_cases.find { |c| c.name == 'with a table' }
352
- end
353
-
354
- it "matches a location on the first table row" do
355
- location = Ast::Location.new(file, 23)
356
- expect( test_case.match_locations?([location]) ).to be_truthy
357
- end
358
- end
359
- end
360
-
361
- context "for a scenario outline" do
362
- let(:source) do
363
- Gherkin::Document.new(file, <<-END.unindent)
364
- Feature:
365
-
366
- Scenario: one
367
- Given one a
368
-
369
- # comment on line 6
370
- @tags-on-line-7
371
- Scenario Outline: two
372
- Given two a
373
- And two <arg>
374
-
375
- # comment on line 12
376
- @tags-on-line-13
377
- Examples: x1
378
- | arg |
379
- | b |
380
-
381
- Examples: x2
382
- | arg |
383
- | c |
384
-
385
- Scenario: three
386
- Given three b
387
- END
388
- end
389
-
390
- let(:test_case) do
391
- test_cases.find { |c| c.name == "two, x1 (#1)" }
392
- end
393
-
394
- it 'matches the precise location of the scenario outline examples table row' do
395
- location = Ast::Location.new(file, 16)
396
- expect( test_case.match_locations?([location]) ).to be_truthy
397
- end
398
-
399
- it 'matches a location on a step of the scenario outline' do
400
- location = Ast::Location.new(file, 10)
401
- expect( test_case.match_locations?([location]) ).to be_truthy
402
- end
403
-
404
- it "matches a location on the scenario outline's comment" do
405
- location = Ast::Location.new(file, 6)
406
- expect( test_case.match_locations?([location]) ).to be_truthy
407
- end
408
-
409
- it "matches a location on the scenario outline's tags" do
410
- location = Ast::Location.new(file, 7)
411
- expect( test_case.match_locations?([location]) ).to be_truthy
412
- end
413
-
414
- it "doesn't match a location after the last row of the examples table" do
415
- location = Ast::Location.new(file, 17)
416
- expect( test_case.match_locations?([location]) ).to be_falsey
417
- end
418
-
419
- it "doesn't match a location before the scenario outline" do
420
- location = Ast::Location.new(file, 5)
421
- expect( test_case.match_locations?([location]) ).to be_falsey
422
- end
423
- end
424
- end
425
235
  end
426
236
  end
427
237
  end
@@ -1,10 +1,12 @@
1
+ # encoding: utf-8
1
2
  require 'cucumber/core/gherkin/writer'
2
3
  require 'cucumber/core'
3
4
  require 'cucumber/core/test/filters/locations_filter'
4
5
  require 'timeout'
6
+ require 'cucumber/core/ast/location'
5
7
 
6
- module Cucumber::Core::Test
7
- describe LocationsFilter do
8
+ module Cucumber::Core
9
+ describe Test::LocationsFilter do
8
10
  include Cucumber::Core::Gherkin::Writer
9
11
  include Cucumber::Core
10
12
 
@@ -26,35 +28,317 @@ module Cucumber::Core::Test
26
28
 
27
29
  it "sorts by the given locations" do
28
30
  locations = [
29
- Cucumber::Core::Ast::Location.new('features/test.feature', 6),
30
- Cucumber::Core::Ast::Location.new('features/test.feature', 3)
31
+ Ast::Location.new('features/test.feature', 6),
32
+ Ast::Location.new('features/test.feature', 3)
31
33
  ]
32
- filter = LocationsFilter.new(locations)
34
+ filter = Test::LocationsFilter.new(locations)
33
35
  compile [doc], receiver, [filter]
34
- expect(receiver.test_case_locations).to eq ["features/test.feature:6", "features/test.feature:3"]
36
+ expect(receiver.test_case_locations).to eq locations
35
37
  end
36
38
 
37
39
  it "works with wildcard locations" do
38
40
  locations = [
39
- Cucumber::Core::Ast::Location.new('features/test.feature')
41
+ Ast::Location.new('features/test.feature')
40
42
  ]
41
- filter = LocationsFilter.new(locations)
43
+ filter = Test::LocationsFilter.new(locations)
42
44
  compile [doc], receiver, [filter]
43
- expect(receiver.test_case_locations).to eq ["features/test.feature:3", "features/test.feature:6"]
45
+ expect(receiver.test_case_locations).to eq [
46
+ Ast::Location.new('features/test.feature', 3),
47
+ Ast::Location.new('features/test.feature', 6)
48
+ ]
44
49
  end
45
50
 
46
51
  it "filters out scenarios that don't match" do
47
52
  locations = [
48
- Cucumber::Core::Ast::Location.new('features/test.feature', 3)
53
+ Ast::Location.new('features/test.feature', 3)
49
54
  ]
50
- filter = LocationsFilter.new(locations)
55
+ filter = Test::LocationsFilter.new(locations)
51
56
  compile [doc], receiver, [filter]
52
- expect(receiver.test_case_locations).to eq ["features/test.feature:3"]
57
+ expect(receiver.test_case_locations).to eq locations
58
+ end
59
+
60
+ describe "matching location" do
61
+ let(:file) { 'features/path/to/the.feature' }
62
+
63
+ let(:test_cases) do
64
+ receiver = double.as_null_object
65
+ result = []
66
+ allow(receiver).to receive(:test_case) { |test_case| result << test_case }
67
+ compile [doc], receiver
68
+ result
69
+ end
70
+
71
+ context "for a scenario" do
72
+ let(:doc) do
73
+ Gherkin::Document.new(file, <<-END)
74
+ Feature:
75
+
76
+ Scenario: one
77
+ Given one a
78
+
79
+ # comment
80
+ @tags
81
+ Scenario: two
82
+ Given two a
83
+ And two b
84
+
85
+ Scenario: three
86
+ Given three b
87
+
88
+ Scenario: with docstring
89
+ Given a docstring
90
+ """
91
+ this is a docstring
92
+ """
93
+
94
+ Scenario: with a table
95
+ Given a table
96
+ | a | b |
97
+ | 1 | 2 |
98
+
99
+ Scenario: empty
100
+ END
101
+ end
102
+
103
+ def test_case_named(name)
104
+ test_cases.find { |c| c.name == name }
105
+ end
106
+
107
+ it 'matches the precise location of the scenario' do
108
+ location = test_case_named('two').location
109
+ filter = Test::LocationsFilter.new([location])
110
+ compile [doc], receiver, [filter]
111
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
112
+ end
113
+
114
+ it 'matches the precise location of an empty scenario' do
115
+ location = test_case_named('empty').location
116
+ filter = Test::LocationsFilter.new([location])
117
+ compile [doc], receiver, [filter]
118
+ expect(receiver.test_case_locations).to eq [test_case_named('empty').location]
119
+ end
120
+
121
+ it 'matches multiple locations' do
122
+ good_location = Ast::Location.new(file, 8)
123
+ bad_location = Ast::Location.new(file, 5)
124
+ filter = Test::LocationsFilter.new([good_location, bad_location])
125
+ compile [doc], receiver, [filter]
126
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
127
+ end
128
+
129
+ it 'matches a location on the last step of the scenario' do
130
+ location = Ast::Location.new(file, 10)
131
+ filter = Test::LocationsFilter.new([location])
132
+ compile [doc], receiver, [filter]
133
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
134
+ end
135
+
136
+ it "matches a location on the scenario's comment" do
137
+ location = Ast::Location.new(file, 6)
138
+ filter = Test::LocationsFilter.new([location])
139
+ compile [doc], receiver, [filter]
140
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
141
+ end
142
+
143
+ it "matches a location on the scenario's tags" do
144
+ location = Ast::Location.new(file, 7)
145
+ filter = Test::LocationsFilter.new([location])
146
+ compile [doc], receiver, [filter]
147
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location]
148
+ end
149
+
150
+ it "doesn't match a location after the last step of the scenario" do
151
+ location = Ast::Location.new(file, 11)
152
+ filter = Test::LocationsFilter.new([location])
153
+ compile [doc], receiver, [filter]
154
+ expect(receiver.test_case_locations).to eq []
155
+ end
156
+
157
+ it "doesn't match a location before the scenario" do
158
+ location = Ast::Location.new(file, 5)
159
+ filter = Test::LocationsFilter.new([location])
160
+ compile [doc], receiver, [filter]
161
+ expect(receiver.test_case_locations).to eq []
162
+ end
163
+
164
+ context "with a docstring" do
165
+ let(:test_case) do
166
+ test_cases.find { |c| c.name == 'with docstring' }
167
+ end
168
+
169
+ it "matches a location at the start the docstring" do
170
+ location = Ast::Location.new(file, 17)
171
+ filter = Test::LocationsFilter.new([location])
172
+ compile [doc], receiver, [filter]
173
+ expect(receiver.test_case_locations).to eq [test_case.location]
174
+ end
175
+
176
+ it "matches a location in the middle of the docstring" do
177
+ location = Ast::Location.new(file, 18)
178
+ filter = Test::LocationsFilter.new([location])
179
+ compile [doc], receiver, [filter]
180
+ expect(receiver.test_case_locations).to eq [test_case.location]
181
+ end
182
+
183
+ it "matches a location at the end of the docstring" do
184
+ location = Ast::Location.new(file, 19)
185
+ filter = Test::LocationsFilter.new([location])
186
+ compile [doc], receiver, [filter]
187
+ expect(receiver.test_case_locations).to eq [test_case.location]
188
+ end
189
+
190
+ it "does not match a location after the docstring" do
191
+ location = Ast::Location.new(file, 20)
192
+ filter = Test::LocationsFilter.new([location])
193
+ compile [doc], receiver, [filter]
194
+ expect(receiver.test_case_locations).to eq []
195
+ end
196
+ end
197
+
198
+ context "with a table" do
199
+ let(:test_case) do
200
+ test_cases.find { |c| c.name == 'with a table' }
201
+ end
202
+
203
+ it "matches a location on the first table row" do
204
+ location = Ast::Location.new(file, 23)
205
+ expect( test_case.match_locations?([location]) ).to be_truthy
206
+ end
207
+ end
208
+
209
+ context "with duplicate locations in the filter" do
210
+ it "matches each test case only once" do
211
+ location_tc_two = test_case_named('two').location
212
+ location_tc_one = test_case_named('one').location
213
+ location_last_step_tc_two = Ast::Location.new(file, 10)
214
+ filter = Test::LocationsFilter.new([location_tc_two, location_tc_one, location_last_step_tc_two])
215
+ compile [doc], receiver, [filter]
216
+ expect(receiver.test_case_locations).to eq [test_case_named('two').location, location_tc_one = test_case_named('one').location]
217
+ end
218
+ end
219
+ end
220
+
221
+ context "for a scenario outline" do
222
+ let(:doc) do
223
+ Gherkin::Document.new(file, <<-END)
224
+ Feature:
225
+
226
+ Scenario: one
227
+ Given one a
228
+
229
+ # comment on line 6
230
+ @tags-on-line-7
231
+ Scenario Outline: two
232
+ Given two a
233
+ And two <arg>
234
+ """
235
+ docstring
236
+ """
237
+
238
+ # comment on line 15
239
+ @tags-on-line-16
240
+ Examples: x1
241
+ | arg |
242
+ | b |
243
+
244
+ Examples: x2
245
+ | arg |
246
+ | c |
247
+ | d |
248
+
249
+ Scenario: three
250
+ Given three b
251
+ END
252
+ end
253
+
254
+ let(:test_case) do
255
+ test_cases.find { |c| c.name == "two, x1 (#1)" }
256
+ end
257
+
258
+ it "matches row location to the test case of the row" do
259
+ locations = [
260
+ Ast::Location.new(file, 19),
261
+ ]
262
+ filter = Test::LocationsFilter.new(locations)
263
+ compile [doc], receiver, [filter]
264
+ expect(receiver.test_case_locations).to eq [test_case.location]
265
+ end
266
+
267
+ it "matches examples location with all test cases of the table" do
268
+ locations = [
269
+ Ast::Location.new(file, 21),
270
+ ]
271
+ filter = Test::LocationsFilter.new(locations)
272
+ compile [doc], receiver, [filter]
273
+ expect(receiver.test_case_locations.map(&:line)).to eq [23, 24]
274
+ end
275
+
276
+ it "matches outline location with the all test cases of all the tables" do
277
+ locations = [
278
+ Ast::Location.new(file, 8),
279
+ ]
280
+ filter = Test::LocationsFilter.new(locations)
281
+ compile [doc], receiver, [filter]
282
+ expect(receiver.test_case_locations.map(&:line)).to eq [19, 23, 24]
283
+ end
284
+
285
+ it "matches outline step location the all test cases of all the tables" do
286
+ locations = [
287
+ Ast::Location.new(file, 10)
288
+ ]
289
+ filter = Test::LocationsFilter.new(locations)
290
+ compile [doc], receiver, [filter]
291
+ expect(receiver.test_case_locations.map(&:line)).to eq [19, 23, 24]
292
+ end
293
+
294
+ it 'matches a location on a step of the scenario outline' do
295
+ location = Ast::Location.new(file, 10)
296
+ filter = Test::LocationsFilter.new([location])
297
+ compile [doc], receiver, [filter]
298
+ expect(receiver.test_case_locations).to include test_case.location
299
+ end
300
+
301
+ it 'matches a location on the docstring of a step of the scenario outline' do
302
+ location = Ast::Location.new(file, 12)
303
+ filter = Test::LocationsFilter.new([location])
304
+ compile [doc], receiver, [filter]
305
+ expect(receiver.test_case_locations).to include test_case.location
306
+ end
307
+
308
+ it "matches a location on the scenario outline's comment" do
309
+ location = Ast::Location.new(file, 6)
310
+ filter = Test::LocationsFilter.new([location])
311
+ compile [doc], receiver, [filter]
312
+ expect(receiver.test_case_locations).to include test_case.location
313
+ end
314
+
315
+ it "matches a location on the scenario outline's tags" do
316
+ location = Ast::Location.new(file, 7)
317
+ filter = Test::LocationsFilter.new([location])
318
+ compile [doc], receiver, [filter]
319
+ expect(receiver.test_case_locations).to include test_case.location
320
+ end
321
+
322
+ it "doesn't match a location after the last row of the examples table" do
323
+ location = Ast::Location.new(file, 20)
324
+ filter = Test::LocationsFilter.new([location])
325
+ compile [doc], receiver, [filter]
326
+ expect(receiver.test_case_locations).to eq []
327
+ end
328
+
329
+ it "doesn't match a location before the scenario outline" do
330
+ location = Ast::Location.new(file, 5)
331
+ filter = Test::LocationsFilter.new([location])
332
+ compile [doc], receiver, [filter]
333
+ expect(receiver.test_case_locations).to eq []
334
+ end
335
+ end
53
336
  end
54
337
 
55
- num_features = 1
56
- num_scenarios_per_feature = 300
57
- context "under load" do
338
+ context "under load", slow: true do
339
+ num_features = 50
340
+ num_scenarios_per_feature = 50
341
+
58
342
  let(:docs) do
59
343
  (1..num_features).map do |i|
60
344
  gherkin("features/test_#{i}.feature") do
@@ -74,14 +358,14 @@ module Cucumber::Core::Test
74
358
  (1..num_locations).map do |i|
75
359
  (1..num_scenarios_per_feature).map do |j|
76
360
  line = 3 + (j - 1) * 3
77
- Cucumber::Core::Ast::Location.new("features/test_#{i}.feature", line)
361
+ Ast::Location.new("features/test_#{i}.feature", line)
78
362
  end
79
363
  end.flatten
80
364
  end
81
365
 
82
366
  max_duration_ms = 10000
83
367
  it "filters #{num_features * num_scenarios_per_feature} test cases within #{max_duration_ms}ms" do
84
- filter = LocationsFilter.new(locations)
368
+ filter = Test::LocationsFilter.new(locations)
85
369
  Timeout.timeout(max_duration_ms / 1000.0) do
86
370
  compile docs, receiver, [filter]
87
371
  end
@@ -89,23 +373,23 @@ module Cucumber::Core::Test
89
373
  end
90
374
 
91
375
  end
376
+ end
92
377
 
93
- class SpyReceiver
94
- def test_case(test_case)
95
- test_cases << test_case
96
- end
97
-
98
- def done
99
- end
378
+ class SpyReceiver
379
+ def test_case(test_case)
380
+ test_cases << test_case
381
+ end
100
382
 
101
- def test_case_locations
102
- test_cases.map(&:location).map(&:to_s)
103
- end
383
+ def done
384
+ end
104
385
 
105
- def test_cases
106
- @test_cases ||= []
107
- end
386
+ def test_case_locations
387
+ test_cases.map(&:location)
388
+ end
108
389
 
390
+ def test_cases
391
+ @test_cases ||= []
109
392
  end
393
+
110
394
  end
111
395
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cucumber-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aslak Hellesøy
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2015-09-11 00:00:00.000000000 Z
15
+ date: 2015-12-17 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: gherkin3
@@ -219,7 +219,7 @@ rubyforge_project:
219
219
  rubygems_version: 2.2.2
220
220
  signing_key:
221
221
  specification_version: 4
222
- summary: cucumber-core-1.3.0
222
+ summary: cucumber-core-1.3.1
223
223
  test_files:
224
224
  - spec/capture_warnings.rb
225
225
  - spec/coverage.rb