Skip to content

Commit 91f7c14

Browse files
authored
Merge pull request #116 from cucumber/use-tag-expressions
Use tag expressions from the cucumber-tag_expressions gem.
2 parents 24b5801 + 700ac0d commit 91f7c14

File tree

5 files changed

+70
-187
lines changed

5 files changed

+70
-187
lines changed

cucumber-core.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
1515
s.required_ruby_version = ">= 1.9.3"
1616

1717
s.add_dependency 'gherkin', '~> 4.0'
18+
s.add_dependency 'cucumber-tag_expressions', '~> 1.0'
1819
s.add_dependency 'backports', '~> 3.6'
1920

2021
s.add_development_dependency 'bundler', '>= 1.3.5'

lib/cucumber/core/test/case.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# frozen_string_literal: true
22
require 'cucumber/core/test/result'
3+
require 'cucumber/tag_expressions'
34
require 'cucumber/core/gherkin/tag_expression'
45
require 'cucumber/core/ast/location'
56

@@ -59,7 +60,7 @@ def tags
5960
end
6061

6162
def match_tags?(*expressions)
62-
Cucumber::Core::Gherkin::TagExpression.new(expressions.flatten).evaluate(tags)
63+
expressions.flatten.all? { |expression| match_single_tag_expression?(expression) }
6364
end
6465

6566
def match_name?(name_regexp)
@@ -120,6 +121,18 @@ def compose_around_hooks(visitor, *args, &block)
120121
end.call
121122
end
122123

124+
def match_single_tag_expression?(expression)
125+
if old_style_tag_expression?(expression)
126+
Cucumber::Core::Gherkin::TagExpression.new([expression]).evaluate(tags)
127+
else
128+
Cucumber::TagExpressions::Parser.new.parse(expression).evaluate(tags.map(&:name))
129+
end
130+
end
131+
132+
def old_style_tag_expression?(expression)
133+
expression.include?(',') || expression.include?('~')
134+
end
135+
123136
class NameBuilder
124137
attr_reader :result
125138
attr_reader :keyword

lib/cucumber/core/test/filters/tag_filter.rb

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ def test_case(test_case)
1515
end
1616

1717
def done
18-
tag_limits.enforce(test_cases)
1918
receiver.done
2019
self
2120
end
@@ -26,10 +25,6 @@ def test_cases
2625
@test_cases ||= TestCases.new
2726
end
2827

29-
def tag_limits
30-
@tag_limits ||= TagLimits.new(filter_expressions)
31-
end
32-
3328
class TestCases
3429
attr_reader :test_cases_by_tag_name
3530
private :test_cases_by_tag_name
@@ -48,65 +43,6 @@ def with_tag_name(tag_name)
4843
test_cases_by_tag_name[tag_name]
4944
end
5045
end
51-
52-
class TagLimits
53-
TAG_MATCHER = /^
54-
(?:~)? #The tag negation symbol "~". This is optional and not captured.
55-
(?<tag_name>\@\w+) #Captures the tag name including the "@" symbol.
56-
\: #The seperator, ":", between the tag name and the limit.
57-
(?<limit>\d+) #Caputres the limit number.
58-
$/x
59-
60-
attr_reader :limit_list
61-
private :limit_list
62-
def initialize(filter_expressions)
63-
@limit_list = Array(filter_expressions).flat_map do |raw_expression|
64-
raw_expression.split(/\s*,\s*/)
65-
end.map do |filter_expression|
66-
TAG_MATCHER.match(filter_expression)
67-
end.compact.each_with_object({}) do |matchdata, limit_list|
68-
limit_list[matchdata[:tag_name]] = Integer(matchdata[:limit])
69-
end
70-
end
71-
72-
def enforce(test_cases)
73-
limit_breaches = limit_list.reduce([]) do |breaches, (tag_name, limit)|
74-
tag_count = test_cases.with_tag_name(tag_name).count
75-
if tag_count > limit
76-
tag_locations = test_cases.with_tag_name(tag_name).map(&:location)
77-
breaches << TagLimitBreach.new(
78-
tag_count,
79-
limit,
80-
tag_name,
81-
tag_locations
82-
)
83-
end
84-
breaches
85-
end
86-
raise TagExcess.new(limit_breaches) if limit_breaches.any?
87-
self
88-
end
89-
end
90-
91-
TagLimitBreach = Struct.new(
92-
:tag_count,
93-
:tag_limit,
94-
:tag_name,
95-
:tag_locations
96-
) do
97-
98-
def message
99-
"#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
100-
tag_locations.map(&:to_s).join("\n ")
101-
end
102-
alias :to_s :message
103-
end
104-
105-
class TagExcess < StandardError
106-
def initialize(limit_breaches)
107-
super(limit_breaches.map(&:to_s).join("\n"))
108-
end
109-
end
11046
end
11147
end
11248
end

spec/cucumber/core/test/case_spec.rb

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,42 @@ module Test
185185
end
186186

187187
describe "matching tags" do
188+
it "matches tags using tag expressions" do
189+
gherkin = gherkin do
190+
feature tags: ['@a', '@b'] do
191+
scenario tags: ['@c'] do
192+
step
193+
end
194+
end
195+
end
196+
receiver = double.as_null_object
197+
expect( receiver ).to receive(:test_case) do |test_case|
198+
expect( test_case.match_tags?(['@a and @b']) ).to be_truthy
199+
expect( test_case.match_tags?(['@a or @d']) ).to be_truthy
200+
expect( test_case.match_tags?(['not @d']) ).to be_truthy
201+
expect( test_case.match_tags?(['@a and @d']) ).to be_falsy
202+
end
203+
compile [gherkin], receiver
204+
end
205+
206+
it "matches handles multiple expressions" do
207+
gherkin = gherkin do
208+
feature tags: ['@a', '@b'] do
209+
scenario tags: ['@c'] do
210+
step
211+
end
212+
end
213+
end
214+
receiver = double.as_null_object
215+
expect( receiver ).to receive(:test_case) do |test_case|
216+
expect( test_case.match_tags?(['@a and @b', 'not @d']) ).to be_truthy
217+
expect( test_case.match_tags?(['@a and @b', 'not @c']) ).to be_falsy
218+
end
219+
compile [gherkin], receiver
220+
end
221+
end
222+
223+
describe "matching tags (old style)" do
188224
it "matches boolean expressions of tags" do
189225
gherkin = gherkin do
190226
feature tags: ['@a', '@b'] do
@@ -195,7 +231,25 @@ module Test
195231
end
196232
receiver = double.as_null_object
197233
expect( receiver ).to receive(:test_case) do |test_case|
198-
expect( test_case.match_tags?('@a') ).to be_truthy
234+
expect( test_case.match_tags?(['@a', '@b']) ).to be_truthy
235+
expect( test_case.match_tags?(['@a, @d']) ).to be_truthy
236+
expect( test_case.match_tags?(['~@d']) ).to be_truthy
237+
expect( test_case.match_tags?(['@a', '@d']) ).to be_falsy
238+
end
239+
compile [gherkin], receiver
240+
end
241+
242+
it "handles mixing old and new style expressions" do
243+
gherkin = gherkin do
244+
feature tags: ['@a', '@b'] do
245+
scenario tags: ['@c'] do
246+
step
247+
end
248+
end
249+
end
250+
receiver = double.as_null_object
251+
expect( receiver ).to receive(:test_case) do |test_case|
252+
expect( test_case.match_tags?(['@a and @b', '~@d']) ).to be_truthy
199253
end
200254
compile [gherkin], receiver
201255
end

spec/cucumber/core_spec.rb

Lines changed: 0 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -77,127 +77,6 @@ module Cucumber
7777

7878
compile [gherkin], visitor, [Cucumber::Core::Test::TagFilter.new(['@a', '@b'])]
7979
end
80-
81-
describe 'with tag filters that have limits' do
82-
let(:visitor) { double.as_null_object }
83-
let(:gherkin_doc) do
84-
gherkin do
85-
feature tags: '@feature' do
86-
scenario tags: '@one @three' do
87-
step
88-
end
89-
90-
scenario tags: '@one' do
91-
step
92-
end
93-
94-
scenario_outline do
95-
step '<arg>'
96-
97-
examples tags: '@three'do
98-
row 'arg'
99-
row 'x'
100-
end
101-
end
102-
103-
scenario tags: '@ignore' do
104-
step
105-
end
106-
end
107-
end
108-
end
109-
110-
require 'unindent'
111-
def expect_tag_excess(error_message)
112-
expect {
113-
compile [gherkin_doc], visitor, tag_filters
114-
}.to raise_error(
115-
Cucumber::Core::Test::TagFilter::TagExcess, error_message.unindent.chomp
116-
)
117-
end
118-
119-
context 'on scenarios' do
120-
let(:tag_filters) {
121-
[ Cucumber::Core::Test::TagFilter.new(['@one:1']) ]
122-
}
123-
124-
it 'raises a tag excess error with the location of the test cases' do
125-
expect_tag_excess <<-STR
126-
@one occurred 2 times, but the limit was set to 1
127-
features/test.feature:5
128-
features/test.feature:9
129-
STR
130-
end
131-
end
132-
133-
context 'on scenario outlines' do
134-
let(:tag_filters) {
135-
[ Cucumber::Core::Test::TagFilter.new(['@three:1']) ]
136-
}
137-
138-
it 'raises a tag excess error with the location of the test cases' do
139-
expect_tag_excess <<-STR
140-
@three occurred 2 times, but the limit was set to 1
141-
features/test.feature:5
142-
features/test.feature:18
143-
STR
144-
end
145-
end
146-
147-
context 'on a feature with scenarios' do
148-
let(:tag_filters) {
149-
[ Cucumber::Core::Test::TagFilter.new(['@feature:2']) ]
150-
}
151-
152-
it 'raises a tag excess error with the location of the test cases' do
153-
expect_tag_excess <<-STR
154-
@feature occurred 4 times, but the limit was set to 2
155-
features/test.feature:5
156-
features/test.feature:9
157-
features/test.feature:18
158-
features/test.feature:21
159-
STR
160-
end
161-
end
162-
163-
context 'with negated tags' do
164-
let(:tag_filters) {
165-
[ Cucumber::Core::Test::TagFilter.new(['~@one:1']) ]
166-
}
167-
168-
it 'raises a tag excess error with the location of the test cases' do
169-
expect_tag_excess <<-STR
170-
@one occurred 2 times, but the limit was set to 1
171-
features/test.feature:5
172-
features/test.feature:9
173-
STR
174-
end
175-
end
176-
177-
context 'whith multiple tag limits' do
178-
let(:tag_filters) {
179-
[ Cucumber::Core::Test::TagFilter.new(['@one:1, @three:1', '~@feature:3']) ]
180-
}
181-
182-
it 'raises a tag excess error with the location of the test cases' do
183-
expect_tag_excess <<-STR
184-
@one occurred 2 times, but the limit was set to 1
185-
features/test.feature:5
186-
features/test.feature:9
187-
@three occurred 2 times, but the limit was set to 1
188-
features/test.feature:5
189-
features/test.feature:18
190-
@feature occurred 4 times, but the limit was set to 3
191-
features/test.feature:5
192-
features/test.feature:9
193-
features/test.feature:18
194-
features/test.feature:21
195-
STR
196-
end
197-
end
198-
199-
end
200-
20180
end
20281

20382
describe "executing a test suite" do

0 commit comments

Comments
 (0)