spinach 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -75,8 +75,26 @@ module Spinach
75
75
  reporter_options[:backtrace] = show_backtrace
76
76
  end
77
77
 
78
+ opts.on('-t', '--tags TAG',
79
+ 'Run all scenarios for given tags.') do |tag|
80
+ config[:tags] ||= []
81
+ tags = tag.delete('@').split(',')
82
+
83
+ references_wip = lambda { |tag_groups|
84
+ tag_groups.any? { |tag_group|
85
+ tag_group.any? { |tag| tag =~ /wip$/ }
86
+ }
87
+ }
88
+
89
+ unless references_wip.(config[:tags]) || references_wip.([tags])
90
+ tags.unshift '~wip'
91
+ end
92
+
93
+ config[:tags] << tags
94
+ end
95
+
78
96
  opts.on('-g', '--generate',
79
- 'Auto-generate the feeature steps files') do
97
+ 'Auto-generate the feature steps files') do
80
98
  Spinach::Generators.bind
81
99
  end
82
100
 
@@ -20,8 +20,14 @@ module Spinach
20
20
  # to run.
21
21
  #
22
22
  class Config
23
- attr_writer :features_path, :step_definitions_path, :default_reporter, :support_path,
24
- :failure_exceptions, :config_path, :save_and_open_page_on_failure
23
+ attr_writer :features_path,
24
+ :step_definitions_path,
25
+ :default_reporter,
26
+ :support_path,
27
+ :failure_exceptions,
28
+ :config_path,
29
+ :tags,
30
+ :save_and_open_page_on_failure
25
31
 
26
32
  # The "features path" holds the place where your features will be
27
33
  # searched for. Defaults to 'features'
@@ -106,13 +112,23 @@ module Spinach
106
112
  @config_path ||= 'config/spinach.yml'
107
113
  end
108
114
 
109
- # When using capybara, it automatically shows the current page when there's
115
+ # When using capybara, it automatically shows the current page when there's
110
116
  # a failure
111
117
  #
112
118
  def save_and_open_page_on_failure
113
119
  @save_and_open_page_on_failure ||= false
114
120
  end
115
121
 
122
+ # Tags to tell Spinach that you only want to run scenarios that have (or
123
+ # don't have) certain tags.
124
+ #
125
+ # @return [Array]
126
+ # The tags.
127
+ #
128
+ def tags
129
+ @tags ||= []
130
+ end
131
+
116
132
  # Parse options from the config file
117
133
  #
118
134
  # @return [Boolean]
@@ -1,3 +1,5 @@
1
+ require_relative '../tags_matcher'
2
+
1
3
  module Spinach
2
4
  class Runner
3
5
  # A feature runner handles a particular feature run.
@@ -55,20 +57,25 @@ module Spinach
55
57
 
56
58
  def run_scenarios!
57
59
  scenarios.each_with_index do |scenario, current_scenario_index|
58
- if match(current_scenario_index)
60
+ if run_scenario?(scenario, current_scenario_index)
59
61
  success = ScenarioRunner.new(scenario).run
60
62
  @failed = true unless success
61
63
  end
62
64
  end
63
65
  end
64
66
 
65
- def match(current_scenario_index)
67
+ def run_scenario?(scenario, current_scenario_index)
68
+ match_line(current_scenario_index) && TagsMatcher.match(scenario.tags)
69
+ end
70
+
71
+ def match_line(current_scenario_index)
66
72
  return true unless @line
67
- return false if @line<scenarios[current_scenario_index].line
73
+ return false if @line < scenarios[current_scenario_index].line
68
74
  next_scenario = scenarios[current_scenario_index+1]
69
- !next_scenario || @line<next_scenario.line
75
+ !next_scenario || @line < next_scenario.line
70
76
  end
71
77
 
78
+
72
79
  def undefined_steps!
73
80
  Spinach.hooks.run_on_undefined_feature @feature
74
81
  @failed = true
@@ -0,0 +1,47 @@
1
+ module Spinach
2
+ module TagsMatcher
3
+
4
+ NEGATION_SIGN = '~'
5
+
6
+ class << self
7
+
8
+ # Matches an array of tags (e.g. of a scenario) against the tags present
9
+ # in Spinach' runtime options.
10
+ #
11
+ # Spinach' tag option is an array which consists of (possibly) multiple
12
+ # arrays containing tags provided by the user running the features and
13
+ # scenarios. Each of these arrays is considered a tag group.
14
+ #
15
+ # When matching tags against the tags groups, the tags inside a tag group
16
+ # are OR-ed and the tag groups themselves are AND-ed.
17
+ def match(tags)
18
+ return true if tag_groups.empty?
19
+
20
+ tag_groups.all? { |tag_group| match_tag_group(tag_group, tags) }
21
+ end
22
+
23
+ private
24
+
25
+ def tag_groups
26
+ Spinach.config.tags
27
+ end
28
+
29
+ def match_tag_group(tag_group, tags)
30
+ tag_group.any? do |tag|
31
+ tag_matched = tags.include?(tag.delete(NEGATION_SIGN))
32
+
33
+ if tag_negated?(tag)
34
+ !tag_matched
35
+ else
36
+ tag_matched
37
+ end
38
+ end
39
+ end
40
+
41
+ def tag_negated?(tag)
42
+ tag.start_with? NEGATION_SIGN
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -1,4 +1,4 @@
1
1
  module Spinach
2
2
  # Spinach version.
3
- VERSION = "0.3.4"
3
+ VERSION = "0.4.0"
4
4
  end
@@ -30,6 +30,42 @@ describe Spinach::Cli do
30
30
  end
31
31
  end
32
32
 
33
+ describe 'tags' do
34
+ %w{-t --tags}.each do |opt|
35
+ it 'sets the given tag' do
36
+ config = Spinach::Config.new
37
+ Spinach.stubs(:config).returns(config)
38
+ cli = Spinach::Cli.new([opt,'wip'])
39
+ cli.options
40
+ config.tags.must_equal [['wip']]
41
+ end
42
+
43
+ it 'sets OR-ed tags' do
44
+ config = Spinach::Config.new
45
+ Spinach.stubs(:config).returns(config)
46
+ cli = Spinach::Cli.new([opt,'wip,javascript'])
47
+ cli.options
48
+ config.tags.must_equal [['wip', 'javascript']]
49
+ end
50
+
51
+ it 'adds ~wip by default' do
52
+ config = Spinach::Config.new
53
+ Spinach.stubs(:config).returns(config)
54
+ cli = Spinach::Cli.new([opt,'javascript'])
55
+ cli.options
56
+ config.tags.must_equal [['~wip', 'javascript']]
57
+ end
58
+ end
59
+
60
+ it 'sets AND-ed tags' do
61
+ config = Spinach::Config.new
62
+ Spinach.stubs(:config).returns(config)
63
+ cli = Spinach::Cli.new(['-t','javascript', '-t', 'wip'])
64
+ cli.options
65
+ config.tags.must_equal [['~wip', 'javascript'],['wip']]
66
+ end
67
+ end
68
+
33
69
  describe 'generate' do
34
70
  %w{-g --generate}.each do |opt|
35
71
  it 'inits the generator if #{opt}' do
@@ -9,7 +9,7 @@ describe Spinach::Config do
9
9
  it 'returns a default' do
10
10
  subject[:features_path].must_be_kind_of String
11
11
  end
12
-
12
+
13
13
  it 'can be overwritten' do
14
14
  subject[:features_path] = 'test'
15
15
  subject[:features_path].must_equal 'test'
@@ -81,4 +81,14 @@ describe Spinach::Config do
81
81
  end
82
82
  end
83
83
 
84
+ describe '#tags' do
85
+ it 'returns a default' do
86
+ subject[:tags].must_be_kind_of Array
87
+ end
88
+
89
+ it 'can be overwritten' do
90
+ subject[:tags] = ['wip']
91
+ subject[:tags].must_equal ['wip']
92
+ end
93
+ end
84
94
  end
@@ -37,8 +37,8 @@ describe Spinach::Runner::FeatureRunner do
37
37
  @feature = stub('feature', name: 'Feature')
38
38
  Spinach.stubs(:find_step_definitions).returns(true)
39
39
  @scenarios = [
40
- scenario = stub,
41
- another_scenario = stub
40
+ scenario = stub(tags: []),
41
+ another_scenario = stub(tags: [])
42
42
  ]
43
43
  @feature.stubs(:scenarios).returns @scenarios
44
44
  @runner = Spinach::Runner::FeatureRunner.new(@feature)
@@ -80,31 +80,61 @@ describe Spinach::Runner::FeatureRunner do
80
80
  @feature = stub('feature', name: 'Feature')
81
81
  Spinach.stubs(:find_step_definitions).returns(true)
82
82
  @scenarios = [
83
- scenario = stub(line: 4),
84
- another_scenario = stub(line: 12)
83
+ scenario = stub(line: 4, tags: []),
84
+ another_scenario = stub(line: 12, tags: [])
85
85
  ]
86
86
  @feature.stubs(:scenarios).returns @scenarios
87
87
  end
88
+
88
89
  it "runs exactly matching scenario" do
89
90
  Spinach::Runner::ScenarioRunner.expects(:new).with(@scenarios[1]).returns stub(run: true)
90
91
  @runner = Spinach::Runner::FeatureRunner.new(@feature, "12")
91
92
  @runner.run
92
93
  end
94
+
93
95
  it "runs no scenario and returns false" do
94
96
  Spinach::Runner::ScenarioRunner.expects(:new).never
95
97
  @runner = Spinach::Runner::FeatureRunner.new(@feature, "3")
96
98
  @runner.run
97
99
  end
100
+
98
101
  it "runs matching scenario" do
99
102
  Spinach::Runner::ScenarioRunner.expects(:new).with(@scenarios[0]).returns stub(run: true)
100
103
  @runner = Spinach::Runner::FeatureRunner.new(@feature, "8")
101
104
  @runner.run
102
105
  end
106
+
103
107
  it "runs last scenario" do
104
108
  Spinach::Runner::ScenarioRunner.expects(:new).with(@scenarios[1]).returns stub(run: true)
105
109
  @runner = Spinach::Runner::FeatureRunner.new(@feature, "15")
106
110
  @runner.run
107
111
  end
108
112
  end
113
+
114
+ describe "when running for specific tags configured" do
115
+
116
+ before do
117
+ @feature = stub('feature', name: 'Feature')
118
+ Spinach.stubs(:find_step_definitions).returns(true)
119
+ @scenario = stub(line: 4, tags: [])
120
+ @feature.stubs(:scenarios).returns [@scenario]
121
+ end
122
+
123
+ it "runs matching scenario" do
124
+ Spinach::TagsMatcher.stubs(:match).returns true
125
+ Spinach::Runner::ScenarioRunner.expects(:new).with(@scenario).returns stub(run: true)
126
+
127
+ @runner = Spinach::Runner::FeatureRunner.new(@feature)
128
+ @runner.run
129
+ end
130
+
131
+ it "skips scenarios that do not match" do
132
+ Spinach::TagsMatcher.stubs(:match).returns false
133
+ Spinach::Runner::ScenarioRunner.expects(:new).never
134
+
135
+ @runner = Spinach::Runner::FeatureRunner.new(@feature)
136
+ @runner.run
137
+ end
138
+ end
109
139
  end
110
140
  end
@@ -0,0 +1,118 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe Spinach::TagsMatcher do
4
+
5
+ describe '#match' do
6
+
7
+ before do
8
+ @config = Spinach::Config.new
9
+ Spinach.stubs(:config).returns(@config)
10
+ end
11
+
12
+ subject { Spinach::TagsMatcher }
13
+
14
+ describe "when matching against a single tag" do
15
+
16
+ before { @config.tags = [['wip']] }
17
+
18
+ it "matches the same tag" do
19
+ subject.match(['wip']).must_equal true
20
+ end
21
+
22
+ it "does not match a different tag" do
23
+ subject.match(['important']).must_equal false
24
+ end
25
+
26
+ it "does not match when no tags are present" do
27
+ subject.match([]).must_equal false
28
+ end
29
+ end
30
+
31
+ describe 'when matching against a single negated tag' do
32
+
33
+ before { @config.tags = [['~wip']] }
34
+
35
+ it "returns false for the same tag" do
36
+ subject.match(['wip']).must_equal false
37
+ end
38
+
39
+ it "returns true for a different tag" do
40
+ subject.match(['important']).must_equal true
41
+ end
42
+
43
+ it "returns true when no tags are present" do
44
+ subject.match([]).must_equal true
45
+ end
46
+ end
47
+
48
+ describe "when matching against ANDed tags" do
49
+
50
+ before { @config.tags = [['wip'], ['important']] }
51
+
52
+ it "returns true when all tags match" do
53
+ subject.match(['wip', 'important']).must_equal true
54
+ end
55
+
56
+ it "returns false when one tag matches" do
57
+ subject.match(['important']).must_equal false
58
+ end
59
+
60
+ it "returns false when no tags match" do
61
+ subject.match(['foo']).must_equal false
62
+ end
63
+
64
+ it "returns false when no tags are present" do
65
+ subject.match([]).must_equal false
66
+ end
67
+ end
68
+
69
+ describe "when matching against ORed tags" do
70
+
71
+ before { @config.tags = [['wip', 'important']] }
72
+
73
+ it "returns true when all tags match" do
74
+ subject.match(['wip', 'important']).must_equal true
75
+ end
76
+
77
+ it "returns true when one tag matches" do
78
+ subject.match(['important']).must_equal true
79
+ end
80
+
81
+ it "returns false when no tags match" do
82
+ subject.match(['foo']).must_equal false
83
+ end
84
+
85
+ it "returns false when no tags are present" do
86
+ subject.match([]).must_equal false
87
+ end
88
+ end
89
+
90
+ describe 'when matching against combined ORed and ANDed tags' do
91
+
92
+ before { @config.tags = [['billing', 'wip'], ['important']] }
93
+
94
+ it "returns true when all tags match" do
95
+ subject.match(['billing', 'wip', 'important']).must_equal true
96
+ end
97
+
98
+ it "returns true when tags from both AND-groups match" do
99
+ subject.match(['wip', 'important']).must_equal true
100
+ subject.match(['billing', 'important']).must_equal true
101
+ end
102
+
103
+ it "returns false when tags from one AND-group match" do
104
+ subject.match(['important']).must_equal false
105
+ subject.match(['billing']).must_equal false
106
+ end
107
+
108
+ it "returns false when no tags match" do
109
+ subject.match(['foo']).must_equal false
110
+ end
111
+
112
+ it "returns false when no tags are present" do
113
+ subject.match([]).must_equal false
114
+ end
115
+ end
116
+
117
+ end
118
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spinach
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2012-03-24 00:00:00.000000000 Z
15
+ date: 2012-04-02 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: gherkin-ruby
@@ -293,6 +293,7 @@ files:
293
293
  - lib/spinach/scenario.rb
294
294
  - lib/spinach/step.rb
295
295
  - lib/spinach/support.rb
296
+ - lib/spinach/tags_matcher.rb
296
297
  - lib/spinach/version.rb
297
298
  - spinach.gemspec
298
299
  - test/spinach/background_test.rb
@@ -319,6 +320,7 @@ files:
319
320
  - test/spinach/scenario_test.rb
320
321
  - test/spinach/step_test.rb
321
322
  - test/spinach/support_test.rb
323
+ - test/spinach/tags_matcher_test.rb
322
324
  - test/spinach_test.rb
323
325
  - test/test_helper.rb
324
326
  homepage: http://github.com/codegram/spinach
@@ -392,6 +394,7 @@ test_files:
392
394
  - test/spinach/scenario_test.rb
393
395
  - test/spinach/step_test.rb
394
396
  - test/spinach/support_test.rb
397
+ - test/spinach/tags_matcher_test.rb
395
398
  - test/spinach_test.rb
396
399
  - test/test_helper.rb
397
400
  has_rdoc: