diff --git a/cucumber-core.gemspec b/cucumber-core.gemspec index 15c4fbcc..23959db4 100644 --- a/cucumber-core.gemspec +++ b/cucumber-core.gemspec @@ -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' diff --git a/lib/cucumber/core/test/case.rb b/lib/cucumber/core/test/case.rb index fa026b12..f0f2563c 100644 --- a/lib/cucumber/core/test/case.rb +++ b/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' @@ -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) @@ -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 diff --git a/lib/cucumber/core/test/filters/tag_filter.rb b/lib/cucumber/core/test/filters/tag_filter.rb index 5298a9f9..2e4c9fca 100644 --- a/lib/cucumber/core/test/filters/tag_filter.rb +++ b/lib/cucumber/core/test/filters/tag_filter.rb @@ -15,7 +15,6 @@ def test_case(test_case) end def done - tag_limits.enforce(test_cases) receiver.done self end @@ -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 @@ -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. - (?\@\w+) #Captures the tag name including the "@" symbol. - \: #The seperator, ":", between the tag name and the 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 diff --git a/spec/cucumber/core/test/case_spec.rb b/spec/cucumber/core/test/case_spec.rb index a43c5d04..8db7bce7 100644 --- a/spec/cucumber/core/test/case_spec.rb +++ b/spec/cucumber/core/test/case_spec.rb @@ -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 @@ -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 diff --git a/spec/cucumber/core_spec.rb b/spec/cucumber/core_spec.rb index 5b66c6ff..14b34c97 100644 --- a/spec/cucumber/core_spec.rb +++ b/spec/cucumber/core_spec.rb @@ -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 '' - - 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