Permalink
Browse files

Merge pull request #96 from robinroestenburg/spinach-tags

Spinach tags
  • Loading branch information...
2 parents abfd50a + 943d918 commit eda83c0e4944a8a2ef62d14bbd1bacc63a814fa4 @txus txus committed Apr 2, 2012
View
@@ -75,8 +75,14 @@ def parse_options
reporter_options[:backtrace] = show_backtrace
end
+ opts.on('-t', '--tags TAG',
+ 'Run all scenarios for given tags.') do |tag|
+ config[:tags] ||= []
+ config[:tags] << tag.delete('@').split(',')
+ end
+
opts.on('-g', '--generate',
- 'Auto-generate the feeature steps files') do
+ 'Auto-generate the feature steps files') do
Spinach::Generators.bind
end
View
@@ -20,8 +20,14 @@ def self.config
# to run.
#
class Config
- attr_writer :features_path, :step_definitions_path, :default_reporter, :support_path,
- :failure_exceptions, :config_path, :save_and_open_page_on_failure
+ attr_writer :features_path,
+ :step_definitions_path,
+ :default_reporter,
+ :support_path,
+ :failure_exceptions,
+ :config_path,
+ :tags,
+ :save_and_open_page_on_failure
# The "features path" holds the place where your features will be
# searched for. Defaults to 'features'
@@ -106,13 +112,23 @@ def config_path
@config_path ||= 'config/spinach.yml'
end
- # When using capybara, it automatically shows the current page when there's
+ # When using capybara, it automatically shows the current page when there's
# a failure
#
def save_and_open_page_on_failure
@save_and_open_page_on_failure ||= false
end
+ # Tags to tell Spinach that you only want to run scenarios that have (or
+ # don't have) certain tags.
+ #
+ # @return [Array]
+ # The tags.
+ #
+ def tags
+ @tags ||= []
+ end
+
# Parse options from the config file
#
# @return [Boolean]
@@ -1,3 +1,5 @@
+require_relative '../tags_matcher'
+
module Spinach
class Runner
# A feature runner handles a particular feature run.
@@ -55,20 +57,25 @@ def run
def run_scenarios!
scenarios.each_with_index do |scenario, current_scenario_index|
- if match(current_scenario_index)
+ if run_scenario?(scenario, current_scenario_index)
success = ScenarioRunner.new(scenario).run
@failed = true unless success
end
end
end
- def match(current_scenario_index)
+ def run_scenario?(scenario, current_scenario_index)
+ match_line(current_scenario_index) && TagsMatcher.match(scenario.tags)
+ end
+
+ def match_line(current_scenario_index)
return true unless @line
- return false if @line<scenarios[current_scenario_index].line
+ return false if @line < scenarios[current_scenario_index].line
next_scenario = scenarios[current_scenario_index+1]
- !next_scenario || @line<next_scenario.line
+ !next_scenario || @line < next_scenario.line
end
+
def undefined_steps!
Spinach.hooks.run_on_undefined_feature @feature
@failed = true
@@ -0,0 +1,47 @@
+module Spinach
+ module TagsMatcher
+
+ NEGATION_SIGN = '~'
+
+ class << self
+
+ # Matches an array of tags (e.g. of a scenario) against the tags present
+ # in Spinach' runtime options.
+ #
+ # Spinach' tag option is an array which consists of (possibly) multiple
+ # arrays containing tags provided by the user running the features and
+ # scenarios. Each of these arrays is considered a tag group.
+ #
+ # When matching tags against the tags groups, the tags inside a tag group
+ # are OR-ed and the tag groups themselves are AND-ed.
+ def match(tags)
+ return true if tag_groups.empty?
+
+ tag_groups.all? { |tag_group| match_tag_group(tag_group, tags) }
+ end
+
+ private
+
+ def tag_groups
+ Spinach.config.tags
+ end
+
+ def match_tag_group(tag_group, tags)
+ tag_group.any? do |tag|
+ tag_matched = tags.include?(tag.delete(NEGATION_SIGN))
+
+ if tag_negated?(tag)
+ !tag_matched
+ else
+ tag_matched
+ end
+ end
+ end
+
+ def tag_negated?(tag)
+ tag.start_with? NEGATION_SIGN
+ end
+
+ end
+ end
+end
View
@@ -30,6 +30,34 @@
end
end
+ describe 'tags' do
+ %w{-t --tags}.each do |opt|
+ it 'sets the given tag' do
+ config = Spinach::Config.new
+ Spinach.stubs(:config).returns(config)
+ cli = Spinach::Cli.new([opt,'wip'])
+ cli.options
+ config.tags.must_equal [['wip']]
+ end
+
+ it 'sets OR-ed tags' do
+ config = Spinach::Config.new
+ Spinach.stubs(:config).returns(config)
+ cli = Spinach::Cli.new([opt,'wip,javascript'])
+ cli.options
+ config.tags.must_equal [['wip', 'javascript']]
+ end
+ end
+
+ it 'sets AND-ed tags' do
+ config = Spinach::Config.new
+ Spinach.stubs(:config).returns(config)
+ cli = Spinach::Cli.new(['-t','javascript', '-t', 'wip'])
+ cli.options
+ config.tags.must_equal [['javascript'],['wip']]
+ end
+ end
+
describe 'generate' do
%w{-g --generate}.each do |opt|
it 'inits the generator if #{opt}' do
@@ -9,7 +9,7 @@
it 'returns a default' do
subject[:features_path].must_be_kind_of String
end
-
+
it 'can be overwritten' do
subject[:features_path] = 'test'
subject[:features_path].must_equal 'test'
@@ -81,4 +81,14 @@
end
end
+ describe '#tags' do
+ it 'returns a default' do
+ subject[:tags].must_be_kind_of Array
+ end
+
+ it 'can be overwritten' do
+ subject[:tags] = ['wip']
+ subject[:tags].must_equal ['wip']
+ end
+ end
end
@@ -37,8 +37,8 @@
@feature = stub('feature', name: 'Feature')
Spinach.stubs(:find_step_definitions).returns(true)
@scenarios = [
- scenario = stub,
- another_scenario = stub
+ scenario = stub(tags: []),
+ another_scenario = stub(tags: [])
]
@feature.stubs(:scenarios).returns @scenarios
@runner = Spinach::Runner::FeatureRunner.new(@feature)
@@ -80,31 +80,61 @@
@feature = stub('feature', name: 'Feature')
Spinach.stubs(:find_step_definitions).returns(true)
@scenarios = [
- scenario = stub(line: 4),
- another_scenario = stub(line: 12)
+ scenario = stub(line: 4, tags: []),
+ another_scenario = stub(line: 12, tags: [])
]
@feature.stubs(:scenarios).returns @scenarios
end
+
it "runs exactly matching scenario" do
Spinach::Runner::ScenarioRunner.expects(:new).with(@scenarios[1]).returns stub(run: true)
@runner = Spinach::Runner::FeatureRunner.new(@feature, "12")
@runner.run
end
+
it "runs no scenario and returns false" do
Spinach::Runner::ScenarioRunner.expects(:new).never
@runner = Spinach::Runner::FeatureRunner.new(@feature, "3")
@runner.run
end
+
it "runs matching scenario" do
Spinach::Runner::ScenarioRunner.expects(:new).with(@scenarios[0]).returns stub(run: true)
@runner = Spinach::Runner::FeatureRunner.new(@feature, "8")
@runner.run
end
+
it "runs last scenario" do
Spinach::Runner::ScenarioRunner.expects(:new).with(@scenarios[1]).returns stub(run: true)
@runner = Spinach::Runner::FeatureRunner.new(@feature, "15")
@runner.run
end
end
+
+ describe "when running for specific tags configured" do
+
+ before do
+ @feature = stub('feature', name: 'Feature')
+ Spinach.stubs(:find_step_definitions).returns(true)
+ @scenario = stub(line: 4, tags: [])
+ @feature.stubs(:scenarios).returns [@scenario]
+ end
+
+ it "runs matching scenario" do
+ Spinach::TagsMatcher.stubs(:match).returns true
+ Spinach::Runner::ScenarioRunner.expects(:new).with(@scenario).returns stub(run: true)
+
+ @runner = Spinach::Runner::FeatureRunner.new(@feature)
+ @runner.run
+ end
+
+ it "skips scenarios that do not match" do
+ Spinach::TagsMatcher.stubs(:match).returns false
+ Spinach::Runner::ScenarioRunner.expects(:new).never
+
+ @runner = Spinach::Runner::FeatureRunner.new(@feature)
+ @runner.run
+ end
+ end
end
end
@@ -0,0 +1,118 @@
+require_relative '../test_helper'
+
+describe Spinach::TagsMatcher do
+
+ describe '#match' do
+
+ before do
+ @config = Spinach::Config.new
+ Spinach.stubs(:config).returns(@config)
+ end
+
+ subject { Spinach::TagsMatcher }
+
+ describe "when matching against a single tag" do
+
+ before { @config.tags = [['wip']] }
+
+ it "matches the same tag" do
+ subject.match(['wip']).must_equal true
+ end
+
+ it "does not match a different tag" do
+ subject.match(['important']).must_equal false
+ end
+
+ it "does not match when no tags are present" do
+ subject.match([]).must_equal false
+ end
+ end
+
+ describe 'when matching against a single negated tag' do
+
+ before { @config.tags = [['~wip']] }
+
+ it "returns false for the same tag" do
+ subject.match(['wip']).must_equal false
+ end
+
+ it "returns true for a different tag" do
+ subject.match(['important']).must_equal true
+ end
+
+ it "returns true when no tags are present" do
+ subject.match([]).must_equal true
+ end
+ end
+
+ describe "when matching against ANDed tags" do
+
+ before { @config.tags = [['wip'], ['important']] }
+
+ it "returns true when all tags match" do
+ subject.match(['wip', 'important']).must_equal true
+ end
+
+ it "returns false when one tag matches" do
+ subject.match(['important']).must_equal false
+ end
+
+ it "returns false when no tags match" do
+ subject.match(['foo']).must_equal false
+ end
+
+ it "returns false when no tags are present" do
+ subject.match([]).must_equal false
+ end
+ end
+
+ describe "when matching against ORed tags" do
+
+ before { @config.tags = [['wip', 'important']] }
+
+ it "returns true when all tags match" do
+ subject.match(['wip', 'important']).must_equal true
+ end
+
+ it "returns true when one tag matches" do
+ subject.match(['important']).must_equal true
+ end
+
+ it "returns false when no tags match" do
+ subject.match(['foo']).must_equal false
+ end
+
+ it "returns false when no tags are present" do
+ subject.match([]).must_equal false
+ end
+ end
+
+ describe 'when matching against combined ORed and ANDed tags' do
+
+ before { @config.tags = [['billing', 'wip'], ['important']] }
+
+ it "returns true when all tags match" do
+ subject.match(['billing', 'wip', 'important']).must_equal true
+ end
+
+ it "returns true when tags from both AND-groups match" do
+ subject.match(['wip', 'important']).must_equal true
+ subject.match(['billing', 'important']).must_equal true
+ end
+
+ it "returns false when tags from one AND-group match" do
+ subject.match(['important']).must_equal false
+ subject.match(['billing']).must_equal false
+ end
+
+ it "returns false when no tags match" do
+ subject.match(['foo']).must_equal false
+ end
+
+ it "returns false when no tags are present" do
+ subject.match([]).must_equal false
+ end
+ end
+
+ end
+end

0 comments on commit eda83c0

Please sign in to comment.