Skip to content

Commit e5f1380

Browse files
Only run feature hooks for features that will actually be run (#209)
* feature hooks only execute if the feature would run given the tags Previously, all feature hooks were executing no matter what tags were provided. Files + line numbers didn't have this behavior; they actually resulted in only some feature hooks running. Now, we only run a feature (and therefore it's hooks) if, for any of its scenarios, the combination of that feature's tags and that scenario's tags matches the run's tags. This required changes to setup in some Runner tests, as Feature instances with no associated Scenario instances can't be included in a Runner run (because they have no Scenarios to match against). * clarify documentation on filtering runs with tags * fix typos --------- Co-authored-by: Oriol Gual <[email protected]>
1 parent 0946544 commit e5f1380

File tree

6 files changed

+119
-14
lines changed

6 files changed

+119
-14
lines changed

README.markdown

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,19 +238,19 @@ Feature: So something great
238238
Scenario: Ensure no regression on this
239239
```
240240

241-
Then you can run all Scenarios in your suite related to `@feat-1` using:
241+
Then you can run all Scenarios in your suite tagged `@feat-1` using:
242242

243243
```shell
244244
$ spinach --tags @feat-1
245245
```
246246

247-
Or only Scenarios related to `@feat-1` and `@bug-12` using:
247+
Or only Scenarios tagged either `@feat-1` or `@bug-12` using:
248248

249249
```shell
250250
$ spinach --tags @feat-1,@bug-12
251251
```
252252

253-
Or only Scenarios related to `@feat-1` excluding `@bug-12` using:
253+
Or only Scenarios tagged `@feat-1` that aren't tagged `@bug-12` using:
254254

255255
```shell
256256
$ spinach --tags @feat-1,~@bug-12
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Feature: Feature Hooks and Tags
2+
In order to run only the appropriate setup and teardown code
3+
As a developer
4+
I want spinach to only run feature hooks if those features would be run under the tags I provided
5+
6+
Scenario: No tags specified
7+
Given I have a tagged feature with an untagged scenario
8+
And I have an untagged feature with a tagged scenario
9+
When I don't specify tags
10+
Then all the feature hooks should have run
11+
12+
Scenario: Tags specified
13+
Given I have a tagged feature with an untagged scenario
14+
And I have an untagged feature with a tagged scenario
15+
When I specify a tag the features and scenarios are tagged with
16+
Then all the feature hooks should have run
17+
18+
Scenario: Tags excluded
19+
Given I have a tagged feature with an untagged scenario
20+
And I have an untagged feature with a tagged scenario
21+
When I exclude a tag the features and scenarios are tagged with
22+
Then no feature hooks should have run
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
class Spinach::Features::FeatureHooksAndTags < Spinach::FeatureSteps
2+
include Integration::SpinachRunner
3+
4+
step 'I have a tagged feature with an untagged scenario' do
5+
write_file 'features/a.feature', <<-FEATURE
6+
@tag
7+
Feature: A
8+
Scenario: A1
9+
Then a1
10+
FEATURE
11+
12+
write_file 'features/steps/a.rb', <<-STEPS
13+
class Spinach::Features::A < Spinach::FeatureSteps
14+
step 'a1' do; end
15+
end
16+
STEPS
17+
end
18+
19+
step 'I have an untagged feature with a tagged scenario' do
20+
write_file 'features/b.feature', <<-FEATURE
21+
Feature: B
22+
@tag
23+
Scenario: B1
24+
Then b1
25+
FEATURE
26+
27+
write_file 'features/steps/b.rb', <<-STEPS
28+
class Spinach::Features::B < Spinach::FeatureSteps
29+
step 'b1' do; end
30+
end
31+
STEPS
32+
end
33+
34+
step "I don't specify tags" do
35+
run_spinach
36+
end
37+
38+
step 'I specify a tag the features and scenarios are tagged with' do
39+
run_spinach({append: "--tags @tag"})
40+
end
41+
42+
step 'I exclude a tag the features and scenarios are tagged with' do
43+
run_spinach({append: "--tags ~@tag"})
44+
end
45+
46+
step 'all the feature hooks should have run' do
47+
@stdout.must_match("Feature: A")
48+
@stdout.must_match("Feature: B")
49+
end
50+
51+
step 'no feature hooks should have run' do
52+
@stdout.wont_match("Feature: A")
53+
@stdout.wont_match("Feature: B")
54+
end
55+
end

lib/spinach/runner.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def fail_fast?
151151
end
152152

153153
def features_to_run
154-
unordered_features = filenames.map do |filename|
154+
unordered_features = filenames.reduce([]) do |features, filename|
155155
file, *lines = filename.split(":") # little more complex than just a "filename"
156156

157157
# FIXME Feature should be instantiated directly, not through an unrelated class method
@@ -160,7 +160,9 @@ def features_to_run
160160

161161
feature.lines_to_run = lines if lines.any?
162162

163-
feature
163+
features << feature if TagsMatcher.match_feature(feature)
164+
165+
features
164166
end
165167

166168
orderer.order(unordered_features)

lib/spinach/tags_matcher.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ module TagsMatcher
66
class << self
77

88
# Matches an array of tags (e.g. of a scenario) against the tags present
9-
# in Spinach' runtime options.
9+
# in Spinach's runtime options.
1010
#
11-
# Spinach' tag option is an array which consists of (possibly) multiple
11+
# Spinach's tag option is an array which consists of (possibly) multiple
1212
# arrays containing tags provided by the user running the features and
1313
# scenarios. Each of these arrays is considered a tag group.
1414
#
@@ -23,6 +23,15 @@ def match(tags)
2323
}
2424
end
2525

26+
# Matches the tags of a feature (and its scenarios) against the tags present
27+
# in Spinach's runtime options.
28+
#
29+
# A feature matches when, for any of its scenarios, the combination of the
30+
# feature's tags and that scenario's tags match the configured tags.
31+
def match_feature(feature)
32+
feature.scenarios.any? { |scenario| match(feature.tags + scenario.tags) }
33+
end
34+
2635
private
2736

2837
def tag_groups

test/spinach/runner_test.rb

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,21 @@
7777
describe '#run' do
7878
before(:each) do
7979
@feature_runner = stub
80+
@feature_runner.stubs(:run).returns(true)
81+
8082
filenames.each do |filename|
81-
Spinach::Parser.stubs(:open_file).with(filename).returns parser = stub
82-
parser.stubs(:parse).returns feature = Spinach::Feature.new
83+
parser = stub
84+
Spinach::Parser.stubs(:open_file).with(filename).returns(parser)
85+
86+
feature = Spinach::Feature.new
87+
feature.scenarios << Spinach::Scenario.new(feature)
88+
parser.stubs(:parse).returns(feature)
89+
8390
Spinach::Runner::FeatureRunner.stubs(:new).
8491
with(feature, anything).
8592
returns(@feature_runner)
8693
end
8794

88-
@feature_runner.stubs(:run).returns(true)
8995
runner.stubs(required_files: [])
9096
end
9197

@@ -133,12 +139,18 @@
133139
let(:runner) { Spinach::Runner.new(filenames) }
134140

135141
before(:each) do
142+
parser = stub
143+
Spinach::Parser.stubs(:open_file).with(filename).returns(parser)
144+
145+
@feature = Spinach::Feature.new
146+
@feature.scenarios << Spinach::Scenario.new(@feature)
147+
parser.stubs(:parse).returns(@feature)
148+
136149
@feature_runner = stub
137-
Spinach::Parser.stubs(:open_file).with(filename).returns parser = stub
138-
parser.stubs(:parse).returns @feature = Spinach::Feature.new
139150
Spinach::Runner::FeatureRunner.stubs(:new).
140151
with(@feature, anything).
141152
returns(@feature_runner)
153+
142154
runner.stubs(required_files: [])
143155
end
144156

@@ -174,8 +186,13 @@
174186

175187
before(:each) do
176188
filenames.each_with_index do |filename, i|
177-
Spinach::Parser.stubs(:open_file).with(filename).returns parser = stub
178-
parser.stubs(:parse).returns feature = Spinach::Feature.new
189+
parser = stub
190+
Spinach::Parser.stubs(:open_file).with(filename).returns(parser)
191+
192+
feature = Spinach::Feature.new
193+
feature.scenarios << Spinach::Scenario.new(feature)
194+
parser.stubs(:parse).returns(feature)
195+
179196
Spinach::Runner::FeatureRunner.stubs(:new).
180197
with(feature, anything).
181198
returns(feature_runners[i])

0 commit comments

Comments
 (0)