diff --git a/.travis.yml b/.travis.yml index 4ef373393a..aaa037c737 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,4 @@ branches: only: - master - v1.3.x-bugfix - -notifications: - email: - - cukes-devs@googlegroups.com - irc: - - "irc.freenode.org#cucumber" + - add-fail-fast-switch diff --git a/features/docs/cli/fail_fast.feature b/features/docs/cli/fail_fast.feature new file mode 100644 index 0000000000..4c6d3719f3 --- /dev/null +++ b/features/docs/cli/fail_fast.feature @@ -0,0 +1,46 @@ +@spawn +Feature: Fail fast + + The --fail-fast flag causes Cucumber to exit immediately after the first + scenario fails. + + Scenario: When a scenario fails + Given a file named "features/bad.feature" with: + """ + Feature: Bad + Scenario: Failing + Given this step fails + """ + And a file named "features/good.feature" with: + """ + Feature: Good + Scenario: Passing + Given this step passes + """ + And the standard step definitions + When I run `cucumber --fail-fast` + Then it should fail + And the output should contain: + """ + 1 scenario (1 failed) + """ + + Scenario: When all the scenarios pass + Given a file named "features/first.feature" with: + """ + Feature: first feature + Scenario: foo first + Given this step passes + Scenario: bar first + Given this step passes + """ + And a file named "features/second.feature" with: + """ + Feature: second + Scenario: foo second + Given this step passes + Scenario: bar second + Given this step passes + """ + When I run `cucumber --fail-fast` + Then it should pass \ No newline at end of file diff --git a/lib/cucumber/cli/configuration.rb b/lib/cucumber/cli/configuration.rb index 1a2111834c..4b011ba191 100644 --- a/lib/cucumber/cli/configuration.rb +++ b/lib/cucumber/cli/configuration.rb @@ -63,6 +63,10 @@ def expand? @options[:expand] end + def fail_fast? + !!@options[:fail_fast] + end + def snippet_type @options[:snippet_type] || :regexp end @@ -133,8 +137,6 @@ def arrange_formats @options[:formats].uniq! @options.check_formatter_stream_conflicts() end - - end end -end +end \ No newline at end of file diff --git a/lib/cucumber/cli/main.rb b/lib/cucumber/cli/main.rb index 924d5fcfca..c4f8607fd3 100644 --- a/lib/cucumber/cli/main.rb +++ b/lib/cucumber/cli/main.rb @@ -102,4 +102,4 @@ def trap_interrupt end end end -end +end \ No newline at end of file diff --git a/lib/cucumber/cli/options.rb b/lib/cucumber/cli/options.rb index 3e0abef900..9cae5ac623 100644 --- a/lib/cucumber/cli/options.rb +++ b/lib/cucumber/cli/options.rb @@ -43,6 +43,7 @@ class Options NO_PROFILE_SHORT_FLAG = '-P' PROFILE_LONG_FLAG = '--profile' NO_PROFILE_LONG_FLAG = '--no-profile' + FAIL_FAST_FLAG = '--fail-fast' OPTIONS_WITH_ARGS = ['-r', '--require', '--i18n', '-f', '--format', '-o', '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude', PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, @@ -126,6 +127,9 @@ def parse!(args) list_keywords_and_exit(lang) end end + opts.on(FAIL_FAST_FLAG, "Exit immediately following the first failing scenario") do |v| + options[:fail_fast] = true + end opts.on("-f FORMAT", "--format FORMAT", "How to format features (Default: pretty). Available formats:", *FORMAT_HELP) do |v| @@ -425,4 +429,4 @@ def default_options end end -end +end \ No newline at end of file diff --git a/lib/cucumber/configuration.rb b/lib/cucumber/configuration.rb index 89b64f7302..5329413fa6 100644 --- a/lib/cucumber/configuration.rb +++ b/lib/cucumber/configuration.rb @@ -41,6 +41,10 @@ def dry_run? @options[:dry_run] end + def fail_fast? + @options[:fail_fast] + end + def guess? @options[:guess] end @@ -182,6 +186,7 @@ def default_options :strict => false, :require => [], :dry_run => false, + :fail_fast => false, :formats => [], :excludes => [], :tag_expressions => [], diff --git a/lib/cucumber/formatter/fail_fast.rb b/lib/cucumber/formatter/fail_fast.rb new file mode 100644 index 0000000000..5a29d8d97d --- /dev/null +++ b/lib/cucumber/formatter/fail_fast.rb @@ -0,0 +1,21 @@ +require 'cucumber/formatter/io' +require 'cucumber/formatter/console' + +module Cucumber + module Formatter + class FailFast + def initialize(configuration) + @configuration = configuration + end + + def after_test_case(test_case, result) + Cucumber.wants_to_quit = true unless result.ok? @configuration.strict? + end + + def done; end + def before_test_case *args; end + def before_test_step *args; end + def after_test_step *args; end + end + end +end \ No newline at end of file diff --git a/lib/cucumber/runtime.rb b/lib/cucumber/runtime.rb index 4d9b5d95e0..1bf1d3c87f 100644 --- a/lib/cucumber/runtime.rb +++ b/lib/cucumber/runtime.rb @@ -180,9 +180,12 @@ def set_encoding require 'cucumber/formatter/legacy_api/runtime_facade' require 'cucumber/formatter/legacy_api/results' require 'cucumber/formatter/ignore_missing_messages' + require 'cucumber/formatter/fail_fast' require 'cucumber/core/report/summary' def report - @report ||= Formatter::Fanout.new([summary_report] + formatters) + args = [summary_report] + formatters + args << fail_fast_report if @configuration.fail_fast? + @report ||= Formatter::Fanout.new(args) end def summary_report @@ -200,6 +203,10 @@ def formatters } end + def fail_fast_report + @fail_fast_report ||= Formatter::FailFast.new configuration + end + def failure? if @configuration.wip? summary_report.test_cases.total_passed > 0 @@ -248,4 +255,4 @@ def log end -end +end \ No newline at end of file diff --git a/spec/cucumber/formatter/fail_fast_spec.rb b/spec/cucumber/formatter/fail_fast_spec.rb new file mode 100644 index 0000000000..467d9576d1 --- /dev/null +++ b/spec/cucumber/formatter/fail_fast_spec.rb @@ -0,0 +1,100 @@ +require 'cucumber/formatter/fail_fast' +require 'cucumber/core' +require 'cucumber/core/gherkin/writer' +require 'cucumber/core/test/result' +require 'cucumber/core/filter' +require 'cucumber/core/ast' +require 'cucumber' + +module Cucumber::Formatter + describe FailFast do + include Cucumber::Core + include Cucumber::Core::Gherkin::Writer + + class WithStepsFake < Cucumber::Core::Filter.new + def test_case(test_case) + test_steps = test_case.test_steps.map do |step| + case step.name + when /fail/ + step.with_action { raise Failure } + when /pass/ + step.with_action {} + else + step + end + end + + test_case.with_steps(test_steps).describe_to(receiver) + end + end + + let(:report) { FailFast.new(double.as_null_object) } + + context 'failing scenario' do + before(:each) do + @gherkin = gherkin('foo.feature') do + feature do + scenario do + step 'failing' + end + + scenario do + step 'failing' + end + end + end + end + + after(:each) do + Cucumber.wants_to_quit = false + end + + it 'sets Cucumber.wants_to_quit' do + execute([@gherkin], report, [WithStepsFake.new]) + expect(Cucumber.wants_to_quit).to be true + end + end + + context 'passing scenario' do + before(:each) do + @gherkin = gherkin('foo.feature') do + feature do + scenario do + step 'passing' + end + end + end + end + + it 'doesn\'t set Cucumber.wants_to_quit' do + execute([@gherkin], report, [WithStepsFake.new]) + expect(Cucumber.wants_to_quit).to be_falsey + end + end + + describe 'after_test_case method' do + context 'failing scenario' do + it 'sets Cucumber.wants_to_quit' do + result = Cucumber::Core::Test::Result::Failed.new(double('duration'), double('exception')) + + test_case = double('test_case') + allow(test_case).to receive(:location) { Cucumber::Core::Ast::Location.new('foo.feature')} + report.after_test_case(test_case, result) + expect(Cucumber.wants_to_quit).to be true + end + end + + context 'passing scenario' do + let(:result) { Cucumber::Core::Test::Result::Passed.new(double) } + + it 'doesn\'t raise an error' do + expect{ report.after_test_case(double, result) }.not_to raise_error + end + + it 'returns nil' do + expect(report.after_test_case(double, result)).to eql nil + end + end + end + end +end \ No newline at end of file