diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 36ac0951ca..80ef1af7b5 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,35 @@ +* Add fail fast to `bin/rails test` + + Adding `--fail-fast` or `-f` when running tests will interrupt the run on + the first failure: + + ``` + # Running: + + ................................................S......E + + ArgumentError: Wups! Bet you didn't expect this! + test/models/bunny_test.rb:19:in `block in ' + + bin/rails test test/models/bunny_test.rb:18 + + ....................................F + + This failed + + bin/rails test test/models/bunny_test.rb:14 + + Interrupted. Exiting... + + + Finished in 0.051427s, 1808.3872 runs/s, 1769.4972 assertions/s. + + ``` + + Note that any unexpected errors don't abort the run. + + *Kasper Timm Hansen* + * Add inline output to `bin/rails test` Any failures or errors (and skips if running in verbose mode) are output diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index b65d67b2a5..3a0a58df88 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -33,6 +33,11 @@ module Minitest options[:output_inline] = false end + opts.on("-f", "--fail-fast", + "Abort test run on first failure") do + options[:fail_fast] = true + end + options[:patterns] = opts.order! end diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index c88e9aa60e..8f1116b6af 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -17,6 +17,10 @@ module Rails io.puts format_rerun_snippet(result) io.puts end + + if fail_fast? && result.failure && !result.error? && !result.skipped? + raise Interrupt + end end def report @@ -48,6 +52,10 @@ module Rails options.fetch(:output_inline, true) end + def fail_fast? + options[:fail_fast] + end + def format_rerun_snippet(result) location, line = result.method(result.name).source_location "#{self.executable} #{relative_path_for(location)}:#{line}" diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 4e747b5253..acfba21f1c 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -355,6 +355,21 @@ module ApplicationTests assert_match %r{Running:\n\nF\n\nwups!\n\nbin/rails test test/models/post_test.rb:4}, output end + def test_fail_fast + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + def test_post + assert false, 'wups!' + end + end + RUBY + + assert_match(/Interrupt/, + capture(:stderr) { run_test_command('test/models/post_test.rb --fail-fast') }) + end + def test_raise_error_when_specified_file_does_not_exist error = capture(:stderr) { run_test_command('test/not_exists.rb') } assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb index b1dd8b5284..59fdf4bc36 100644 --- a/railties/test/test_unit/reporter_test.rb +++ b/railties/test/test_unit/reporter_test.rb @@ -86,6 +86,30 @@ class TestUnitReporterTest < ActiveSupport::TestCase assert_no_match 'Failed tests:', @output.string end + test "fail fast interrupts run on failure" do + fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true + interrupt_raised = false + + # Minitest passes through Interrupt, catch it manually. + begin + fail_fast.record(failed_test) + rescue Interrupt + interrupt_raised = true + ensure + assert interrupt_raised, 'Expected Interrupt to be raised.' + end + end + + test "fail fast does not interrupt run errors or skips" do + fail_fast = Rails::TestUnitReporter.new @output, fail_fast: true + + fail_fast.record(errored_test) + assert_no_match 'Failed tests:', @output.string + + fail_fast.record(skipped_test) + assert_no_match 'Failed tests:', @output.string + end + private def assert_rerun_snippet_count(snippet_count) assert_equal snippet_count, @output.string.scan(%r{^bin/rails test }).size