Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit b60a1ca

Browse files
committed
Merge pull request #2065 from jackscotti/configurable-fail-fast
Make --fail-fast configurable
2 parents a8aed17 + 37cdf42 commit b60a1ca

File tree

9 files changed

+169
-30
lines changed

9 files changed

+169
-30
lines changed

features/command_line/fail_fast.feature

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,29 @@ Feature: `--fail-fast` option
33
Use the `--fail-fast` option to tell RSpec to stop running the test suite on
44
the first failed test.
55

6-
You may also specify `--no-fail-fast` to turn it off (default behaviour).
6+
You may add a parameter to tell RSpec to stop running the test suite after N
7+
failed tests, for example: `--fail-fast=3`.
8+
9+
You can also specify `--no-fail-fast` to turn it off (default behaviour).
710

811
Background:
912
Given a file named "fail_fast_spec.rb" with:
1013
"""ruby
1114
RSpec.describe "fail fast" do
1215
it "passing test" do; end
13-
it "failing test" do
16+
it "1st failing test" do
17+
fail
18+
end
19+
it "2nd failing test" do
20+
fail
21+
end
22+
it "3rd failing test" do
1423
fail
1524
end
16-
it "this should not be run" do; end
25+
it "4th failing test" do
26+
fail
27+
end
28+
it "passing test" do; end
1729
end
1830
"""
1931

@@ -22,6 +34,11 @@ Feature: `--fail-fast` option
2234
Then the output should contain ".F"
2335
Then the output should not contain ".F."
2436

37+
Scenario: Using `--fail-fast=3`
38+
When I run `rspec . --fail-fast=3`
39+
Then the output should contain ".FFF"
40+
Then the output should not contain ".FFFF."
41+
2542
Scenario: Using `--no-fail-fast`
2643
When I run `rspec . --no-fail-fast`
27-
Then the output should contain ".F."
44+
Then the output should contain ".FFFF."

features/configuration/fail_fast.feature

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
Feature: fail fast
22

3-
Use the `fail_fast` option to tell RSpec to abort the run on first failure:
3+
Use the `fail_fast` option to tell RSpec to abort the run on after N failures:
44

5-
```ruby
6-
RSpec.configure { |c| c.fail_fast = true }
7-
```
8-
9-
Background:
5+
Scenario: `fail_fast` with no failures (runs all examples)
106
Given a file named "spec/spec_helper.rb" with:
117
"""ruby
12-
RSpec.configure {|c| c.fail_fast = true}
8+
RSpec.configure {|c| c.fail_fast = 1}
139
"""
14-
15-
Scenario: `fail_fast` with no failures (runs all examples)
16-
Given a file named "spec/example_spec.rb" with:
10+
And a file named "spec/example_spec.rb" with:
1711
"""ruby
1812
RSpec.describe "something" do
1913
it "passes" do
@@ -27,7 +21,11 @@ Feature: fail fast
2721
Then the examples should all pass
2822

2923
Scenario: `fail_fast` with first example failing (only runs the one example)
30-
Given a file named "spec/example_spec.rb" with:
24+
Given a file named "spec/spec_helper.rb" with:
25+
"""ruby
26+
RSpec.configure {|c| c.fail_fast = 1}
27+
"""
28+
And a file named "spec/example_spec.rb" with:
3129
"""ruby
3230
require "spec_helper"
3331
RSpec.describe "something" do
@@ -43,7 +41,11 @@ Feature: fail fast
4341
Then the output should contain "1 example, 1 failure"
4442

4543
Scenario: `fail_fast` with multiple files, second example failing (only runs the first two examples)
46-
Given a file named "spec/example_1_spec.rb" with:
44+
Given a file named "spec/spec_helper.rb" with:
45+
"""ruby
46+
RSpec.configure {|c| c.fail_fast = 1}
47+
"""
48+
And a file named "spec/example_1_spec.rb" with:
4749
"""ruby
4850
require "spec_helper"
4951
RSpec.describe "something" do
@@ -77,3 +79,31 @@ Feature: fail fast
7779
"""
7880
When I run `rspec spec`
7981
Then the output should contain "2 examples, 1 failure"
82+
83+
84+
Scenario: `fail_fast 2` with 1st and 3rd examples failing (only runs the first 3 examples)
85+
Given a file named "spec/spec_helper.rb" with:
86+
"""ruby
87+
RSpec.configure {|c| c.fail_fast = 2}
88+
"""
89+
And a file named "spec/example_spec.rb" with:
90+
"""ruby
91+
require "spec_helper"
92+
RSpec.describe "something" do
93+
it "fails once" do
94+
fail
95+
end
96+
97+
it "passes once" do
98+
end
99+
100+
it "fails twice" do
101+
fail
102+
end
103+
104+
it "passes" do
105+
end
106+
end
107+
"""
108+
When I run `rspec spec/example_spec.rb -fd`
109+
Then the output should contain "3 examples, 2 failures"

lib/rspec/core/configuration.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ def only_failures_but_not_configured?
186186
end
187187

188188
# @macro add_setting
189-
# Clean up and exit after the first failure (default: `false`).
189+
# If specified, indicates the number of failures required before cleaning
190+
# up and exit (default: `false`).
190191
add_setting :fail_fast
191192

192193
# @macro add_setting

lib/rspec/core/example_group.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,9 @@ def self.run_examples(reporter)
579579
instance = new(example.inspect_output)
580580
set_ivars(instance, before_context_ivars)
581581
succeeded = example.run(instance, reporter)
582-
RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
582+
if !succeeded && fail_fast? && reporter.fail_fast_limit_met?
583+
RSpec.world.wants_to_quit = true
584+
end
583585
succeeded
584586
end.all?
585587
end

lib/rspec/core/option_parser.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,18 @@ def parser(options)
7171
bisect_and_exit(argument)
7272
end
7373

74-
parser.on('--[no-]fail-fast', 'Abort the run on first failure.') do |value|
74+
parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument|
75+
if argument == true
76+
value = 1
77+
elsif argument == false || argument == 0
78+
value = false
79+
else
80+
begin
81+
value = Integer(argument)
82+
rescue ArgumentError
83+
RSpec.warning "Non integer specified as fail count."
84+
end
85+
end
7586
set_fail_fast(options, value)
7687
end
7788

@@ -176,7 +187,7 @@ def parser(options)
176187
parser.on("--next-failure", "Apply `--only-failures` and abort after one failure.",
177188
" (Equivalent to `--only-failures --fail-fast --order defined`)") do
178189
configure_only_failures(options)
179-
set_fail_fast(options, true)
190+
set_fail_fast(options, 1)
180191
options[:order] ||= 'defined'
181192
end
182193

lib/rspec/core/reporter.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ def abort_with(msg, exit_status)
197197
exit!(exit_status)
198198
end
199199

200+
# @private
201+
def fail_fast_limit_met?
202+
failures_required <= @failed_examples.size
203+
end
204+
205+
# @private
206+
def failures_required
207+
@configuration.fail_fast == true ? 1 : @configuration.fail_fast
208+
end
209+
200210
private
201211

202212
def close

spec/rspec/core/example_group_spec.rb

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,34 +1342,76 @@ def extract_execution_results(group)
13421342
end
13431343

13441344
describe "#run" do
1345-
let(:reporter) { double("reporter").as_null_object }
13461345

1347-
context "with fail_fast? => true" do
1346+
context "with fail_fast and failures_required == 1" do
13481347
let(:group) do
13491348
group = RSpec.describe
13501349
allow(group).to receive(:fail_fast?) { true }
13511350
group
13521351
end
1352+
let(:config) { Configuration.new }
1353+
let(:reporter) do
1354+
the_reporter = Reporter.new config
1355+
allow(the_reporter).to receive(:failures_required) { 1 }
1356+
the_reporter
1357+
end
13531358

13541359
it "does not run examples after the failed example" do
13551360
examples_run = []
1356-
self.group.example('example 1') { examples_run << self }
1357-
self.group.example('example 2') { examples_run << self; fail; }
1358-
self.group.example('example 3') { examples_run << self }
1361+
group().example('example 1') { examples_run << self }
1362+
group().example('example 2') { examples_run << self; fail; }
1363+
group().example('example 3') { examples_run << self }
13591364

1360-
self.group.run
1365+
group().run(reporter)
13611366

13621367
expect(examples_run.length).to eq(2)
13631368
end
13641369

13651370
it "sets RSpec.world.wants_to_quit flag if encountering an exception in before(:all)" do
1366-
self.group.before(:all) { raise "error in before all" }
1367-
self.group.example("equality") { expect(1).to eq(2) }
1368-
expect(self.group.run).to be_falsey
1371+
group().before(:all) { raise "error in before all" }
1372+
group().example("equality") { expect(1).to eq(2) }
1373+
expect(group().run).to be_falsey
13691374
expect(RSpec.world.wants_to_quit).to be_truthy
13701375
end
13711376
end
13721377

1378+
context "with fail_fast and failures_required = 3" do
1379+
let(:group) do
1380+
group = RSpec.describe
1381+
allow(group).to receive(:fail_fast?) { true }
1382+
group
1383+
end
1384+
let(:config) { Configuration.new }
1385+
1386+
let(:reporter) do
1387+
the_reporter = Reporter.new config
1388+
allow(the_reporter).to receive(:failures_required) { 3 }
1389+
the_reporter
1390+
end
1391+
1392+
it "does not run examples after 3 failed examples" do
1393+
examples_run = []
1394+
group().example('example 1') { examples_run << self }
1395+
group().example('example 2') { examples_run << self; fail; }
1396+
group().example('example 3') { examples_run << self; fail; }
1397+
group().example('example 4') { examples_run << self; fail; }
1398+
group().example('example 5') { examples_run << self }
1399+
1400+
group().run(reporter)
1401+
1402+
expect(examples_run.length).to eq(4)
1403+
end
1404+
1405+
it "sets RSpec.world.wants_to_quit flag if encountering an exception in before(:all)" do
1406+
group().before(:all) { raise "error in before all" }
1407+
group().example("equality") { expect(1).to eq(2) }
1408+
expect(group().run).to be_falsey
1409+
expect(RSpec.world.wants_to_quit).to be_truthy
1410+
end
1411+
end
1412+
1413+
let(:reporter) { double("reporter").as_null_object }
1414+
13731415
context "with RSpec.world.wants_to_quit=true" do
13741416
let(:group) { RSpec.describe }
13751417

@@ -1379,7 +1421,7 @@ def extract_execution_results(group)
13791421

13801422
it "returns without starting the group" do
13811423
expect(reporter).not_to receive(:example_group_started)
1382-
self.group.run(reporter)
1424+
group().run(reporter)
13831425
end
13841426
end
13851427

spec/rspec/core/option_parser_spec.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,15 @@ def generate_help_text
341341
end
342342
end
343343

344+
describe '--fail-fast' do
345+
it 'warns when a non-integer is specified as fail count' do
346+
expect(::Kernel).to receive(:warn) do |message|
347+
expect(message).to match "Non integer specified as fail count"
348+
end
349+
Parser.parse(%w[--fail-fast=three])
350+
end
351+
end
352+
344353
describe '--warning' do
345354
around do |ex|
346355
verbose = $VERBOSE

spec/rspec/core/reporter_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,5 +279,22 @@ module RSpec::Core
279279
reporter.finish
280280
end
281281
end
282+
283+
describe "#failures_required" do
284+
it "returns 1 when RSpec.configuration.fail_fast == true" do
285+
config.fail_fast = true
286+
expect(reporter.failures_required).to eq 1
287+
end
288+
289+
it "returns 1 when RSpec.configuration.fail_fast == 1" do
290+
config.fail_fast = 1
291+
expect(reporter.failures_required).to eq 1
292+
end
293+
294+
it "returns RSpec.configuration.fail_fast when RSpec.configuration.fail_fast > 1" do
295+
config.fail_fast = 3
296+
expect(reporter.failures_required).to eq 3
297+
end
298+
end
282299
end
283300
end

0 commit comments

Comments
 (0)