Skip to content

Commit

Permalink
Merge pull request #116 from cucumber/use-tag-expressions
Browse files Browse the repository at this point in the history
Use tag expressions from the cucumber-tag_expressions gem.
  • Loading branch information
mattwynne committed Feb 15, 2017
2 parents 24b5801 + 700ac0d commit 91f7c14
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 187 deletions.
1 change: 1 addition & 0 deletions cucumber-core.gemspec
Expand Up @@ -15,6 +15,7 @@ Gem::Specification.new do |s|
s.required_ruby_version = ">= 1.9.3"

s.add_dependency 'gherkin', '~> 4.0'
s.add_dependency 'cucumber-tag_expressions', '~> 1.0'
s.add_dependency 'backports', '~> 3.6'

s.add_development_dependency 'bundler', '>= 1.3.5'
Expand Down
15 changes: 14 additions & 1 deletion lib/cucumber/core/test/case.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require 'cucumber/core/test/result'
require 'cucumber/tag_expressions'
require 'cucumber/core/gherkin/tag_expression'
require 'cucumber/core/ast/location'

Expand Down Expand Up @@ -59,7 +60,7 @@ def tags
end

def match_tags?(*expressions)
Cucumber::Core::Gherkin::TagExpression.new(expressions.flatten).evaluate(tags)
expressions.flatten.all? { |expression| match_single_tag_expression?(expression) }
end

def match_name?(name_regexp)
Expand Down Expand Up @@ -120,6 +121,18 @@ def compose_around_hooks(visitor, *args, &block)
end.call
end

def match_single_tag_expression?(expression)
if old_style_tag_expression?(expression)
Cucumber::Core::Gherkin::TagExpression.new([expression]).evaluate(tags)
else
Cucumber::TagExpressions::Parser.new.parse(expression).evaluate(tags.map(&:name))
end
end

def old_style_tag_expression?(expression)
expression.include?(',') || expression.include?('~')
end

class NameBuilder
attr_reader :result
attr_reader :keyword
Expand Down
64 changes: 0 additions & 64 deletions lib/cucumber/core/test/filters/tag_filter.rb
Expand Up @@ -15,7 +15,6 @@ def test_case(test_case)
end

def done
tag_limits.enforce(test_cases)
receiver.done
self
end
Expand All @@ -26,10 +25,6 @@ def test_cases
@test_cases ||= TestCases.new
end

def tag_limits
@tag_limits ||= TagLimits.new(filter_expressions)
end

class TestCases
attr_reader :test_cases_by_tag_name
private :test_cases_by_tag_name
Expand All @@ -48,65 +43,6 @@ def with_tag_name(tag_name)
test_cases_by_tag_name[tag_name]
end
end

class TagLimits
TAG_MATCHER = /^
(?:~)? #The tag negation symbol "~". This is optional and not captured.
(?<tag_name>\@\w+) #Captures the tag name including the "@" symbol.
\: #The seperator, ":", between the tag name and the limit.
(?<limit>\d+) #Caputres the limit number.
$/x

attr_reader :limit_list
private :limit_list
def initialize(filter_expressions)
@limit_list = Array(filter_expressions).flat_map do |raw_expression|
raw_expression.split(/\s*,\s*/)
end.map do |filter_expression|
TAG_MATCHER.match(filter_expression)
end.compact.each_with_object({}) do |matchdata, limit_list|
limit_list[matchdata[:tag_name]] = Integer(matchdata[:limit])
end
end

def enforce(test_cases)
limit_breaches = limit_list.reduce([]) do |breaches, (tag_name, limit)|
tag_count = test_cases.with_tag_name(tag_name).count
if tag_count > limit
tag_locations = test_cases.with_tag_name(tag_name).map(&:location)
breaches << TagLimitBreach.new(
tag_count,
limit,
tag_name,
tag_locations
)
end
breaches
end
raise TagExcess.new(limit_breaches) if limit_breaches.any?
self
end
end

TagLimitBreach = Struct.new(
:tag_count,
:tag_limit,
:tag_name,
:tag_locations
) do

def message
"#{tag_name} occurred #{tag_count} times, but the limit was set to #{tag_limit}\n " +
tag_locations.map(&:to_s).join("\n ")
end
alias :to_s :message
end

class TagExcess < StandardError
def initialize(limit_breaches)
super(limit_breaches.map(&:to_s).join("\n"))
end
end
end
end
end
Expand Down
56 changes: 55 additions & 1 deletion spec/cucumber/core/test/case_spec.rb
Expand Up @@ -185,6 +185,42 @@ module Test
end

describe "matching tags" do
it "matches tags using tag expressions" do
gherkin = gherkin do
feature tags: ['@a', '@b'] do
scenario tags: ['@c'] do
step
end
end
end
receiver = double.as_null_object
expect( receiver ).to receive(:test_case) do |test_case|
expect( test_case.match_tags?(['@a and @b']) ).to be_truthy
expect( test_case.match_tags?(['@a or @d']) ).to be_truthy
expect( test_case.match_tags?(['not @d']) ).to be_truthy
expect( test_case.match_tags?(['@a and @d']) ).to be_falsy
end
compile [gherkin], receiver
end

it "matches handles multiple expressions" do
gherkin = gherkin do
feature tags: ['@a', '@b'] do
scenario tags: ['@c'] do
step
end
end
end
receiver = double.as_null_object
expect( receiver ).to receive(:test_case) do |test_case|
expect( test_case.match_tags?(['@a and @b', 'not @d']) ).to be_truthy
expect( test_case.match_tags?(['@a and @b', 'not @c']) ).to be_falsy
end
compile [gherkin], receiver
end
end

describe "matching tags (old style)" do
it "matches boolean expressions of tags" do
gherkin = gherkin do
feature tags: ['@a', '@b'] do
Expand All @@ -195,7 +231,25 @@ module Test
end
receiver = double.as_null_object
expect( receiver ).to receive(:test_case) do |test_case|
expect( test_case.match_tags?('@a') ).to be_truthy
expect( test_case.match_tags?(['@a', '@b']) ).to be_truthy
expect( test_case.match_tags?(['@a, @d']) ).to be_truthy
expect( test_case.match_tags?(['~@d']) ).to be_truthy
expect( test_case.match_tags?(['@a', '@d']) ).to be_falsy
end
compile [gherkin], receiver
end

it "handles mixing old and new style expressions" do
gherkin = gherkin do
feature tags: ['@a', '@b'] do
scenario tags: ['@c'] do
step
end
end
end
receiver = double.as_null_object
expect( receiver ).to receive(:test_case) do |test_case|
expect( test_case.match_tags?(['@a and @b', '~@d']) ).to be_truthy
end
compile [gherkin], receiver
end
Expand Down
121 changes: 0 additions & 121 deletions spec/cucumber/core_spec.rb
Expand Up @@ -77,127 +77,6 @@ module Cucumber

compile [gherkin], visitor, [Cucumber::Core::Test::TagFilter.new(['@a', '@b'])]
end

describe 'with tag filters that have limits' do
let(:visitor) { double.as_null_object }
let(:gherkin_doc) do
gherkin do
feature tags: '@feature' do
scenario tags: '@one @three' do
step
end

scenario tags: '@one' do
step
end

scenario_outline do
step '<arg>'

examples tags: '@three'do
row 'arg'
row 'x'
end
end

scenario tags: '@ignore' do
step
end
end
end
end

require 'unindent'
def expect_tag_excess(error_message)
expect {
compile [gherkin_doc], visitor, tag_filters
}.to raise_error(
Cucumber::Core::Test::TagFilter::TagExcess, error_message.unindent.chomp
)
end

context 'on scenarios' do
let(:tag_filters) {
[ Cucumber::Core::Test::TagFilter.new(['@one:1']) ]
}

it 'raises a tag excess error with the location of the test cases' do
expect_tag_excess <<-STR
@one occurred 2 times, but the limit was set to 1
features/test.feature:5
features/test.feature:9
STR
end
end

context 'on scenario outlines' do
let(:tag_filters) {
[ Cucumber::Core::Test::TagFilter.new(['@three:1']) ]
}

it 'raises a tag excess error with the location of the test cases' do
expect_tag_excess <<-STR
@three occurred 2 times, but the limit was set to 1
features/test.feature:5
features/test.feature:18
STR
end
end

context 'on a feature with scenarios' do
let(:tag_filters) {
[ Cucumber::Core::Test::TagFilter.new(['@feature:2']) ]
}

it 'raises a tag excess error with the location of the test cases' do
expect_tag_excess <<-STR
@feature occurred 4 times, but the limit was set to 2
features/test.feature:5
features/test.feature:9
features/test.feature:18
features/test.feature:21
STR
end
end

context 'with negated tags' do
let(:tag_filters) {
[ Cucumber::Core::Test::TagFilter.new(['~@one:1']) ]
}

it 'raises a tag excess error with the location of the test cases' do
expect_tag_excess <<-STR
@one occurred 2 times, but the limit was set to 1
features/test.feature:5
features/test.feature:9
STR
end
end

context 'whith multiple tag limits' do
let(:tag_filters) {
[ Cucumber::Core::Test::TagFilter.new(['@one:1, @three:1', '~@feature:3']) ]
}

it 'raises a tag excess error with the location of the test cases' do
expect_tag_excess <<-STR
@one occurred 2 times, but the limit was set to 1
features/test.feature:5
features/test.feature:9
@three occurred 2 times, but the limit was set to 1
features/test.feature:5
features/test.feature:18
@feature occurred 4 times, but the limit was set to 3
features/test.feature:5
features/test.feature:9
features/test.feature:18
features/test.feature:21
STR
end
end

end

end

describe "executing a test suite" do
Expand Down

0 comments on commit 91f7c14

Please sign in to comment.