cucumber-core 1.3.0 → 1.3.1

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