spinach 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: