diff --git a/lib/rspec/core/formatters/base_formatter.rb b/lib/rspec/core/formatters/base_formatter.rb index f00e1916d3..d9e3b3a53e 100644 --- a/lib/rspec/core/formatters/base_formatter.rb +++ b/lib/rspec/core/formatters/base_formatter.rb @@ -12,6 +12,13 @@ class BaseFormatter attr_reader :example_count, :pending_count, :failure_count attr_reader :failed_examples, :pending_examples + def self.relative_path(line) + line = line.sub(File.expand_path("."), ".") + line = line.sub(/\A([^:]+:\d+)$/, '\\1') + return nil if line == '-e:1' + line + end + def initialize(output) @output = output || StringIO.new @example_count = @pending_count = @failure_count = 0 @@ -113,10 +120,7 @@ def configuration def backtrace_line(line) return nil if configuration.cleaned_from_backtrace?(line) - line = line.sub(File.expand_path("."), ".") - line = line.sub(/\A([^:]+:\d+)$/, '\\1') - return nil if line == '-e:1' - line + self.class.relative_path(line) end def read_failed_line(exception, example) diff --git a/lib/rspec/core/formatters/base_text_formatter.rb b/lib/rspec/core/formatters/base_text_formatter.rb index 3c6cfccb69..f2f09039de 100644 --- a/lib/rspec/core/formatters/base_text_formatter.rb +++ b/lib/rspec/core/formatters/base_text_formatter.rb @@ -39,6 +39,18 @@ def dump_summary(duration, example_count, failure_count, pending_count) dump_profile if profile_examples? && failure_count == 0 output.puts "\nFinished in #{format_seconds(duration)} seconds\n" output.puts colorise_summary(summary_line(example_count, failure_count, pending_count)) + dump_commands_to_rerun_failed_examples + end + + def dump_commands_to_rerun_failed_examples + return if failed_examples.empty? + output.puts + output.puts("Failed examples:") + output.puts + + failed_examples.each do |example| + output.puts(red("rspec #{BaseFormatter::relative_path(example.location)}") + " " + grey("# #{example.full_description}")) + end end def dump_profile diff --git a/spec/rspec/core/formatters/base_text_formatter_spec.rb b/spec/rspec/core/formatters/base_text_formatter_spec.rb index 59ee65c84e..a34cf9323d 100644 --- a/spec/rspec/core/formatters/base_text_formatter_spec.rb +++ b/spec/rspec/core/formatters/base_text_formatter_spec.rb @@ -1,89 +1,112 @@ require 'spec_helper' require 'rspec/core/formatters/base_text_formatter' -module RSpec::Core::Formatters +describe RSpec::Core::Formatters::BaseTextFormatter do + let(:output) { StringIO.new } + let(:formatter) { RSpec::Core::Formatters::BaseTextFormatter.new(output) } - describe BaseTextFormatter do - let(:output) { StringIO.new } - let(:formatter) { RSpec::Core::Formatters::BaseTextFormatter.new(output) } + describe "#summary_line" do + it "with 0s outputs pluralized (excluding pending)" do + formatter.summary_line(0,0,0).should eq("0 examples, 0 failures") + end - describe "#summary_line" do - context "with 0s" do - it "outputs pluralized (excluding pending)" do - formatter.summary_line(0,0,0).should eq("0 examples, 0 failures") - end - end + it "with 1s outputs singular (including pending)" do + formatter.summary_line(1,1,1).should eq("1 example, 1 failure, 1 pending") + end - context "with 1s" do - it "outputs singular (including pending)" do - formatter.summary_line(1,1,1).should eq("1 example, 1 failure, 1 pending") - end - end + it "with 2s outputs pluralized (including pending)" do + formatter.summary_line(2,2,2).should eq("2 examples, 2 failures, 2 pending") + end + end - context "with 2s" do - it "outputs pluralized (including pending)" do - formatter.summary_line(2,2,2).should eq("2 examples, 2 failures, 2 pending") - end + describe "#dump_commands_to_rerun_failed_examples" do + it "includes command to re-run each failed example" do + group = RSpec::Core::ExampleGroup.describe("example group") do + it("fails") { fail } end + line = __LINE__ - 2 + group.run(formatter) + formatter.dump_commands_to_rerun_failed_examples + output.string.should include("rspec #{RSpec::Core::Formatters::BaseFormatter::relative_path("#{__FILE__}:#{line}")} # example group fails") end + end - describe "#dump_failures" do - let(:group) { RSpec::Core::ExampleGroup.describe("group name") } + describe "#dump_failures" do + let(:group) { RSpec::Core::ExampleGroup.describe("group name") } - before { RSpec.configuration.stub(:color_enabled?) { false } } + before { RSpec.configuration.stub(:color_enabled?) { false } } - def run_all_and_dump_failures - group.run(formatter) - formatter.dump_failures - end + def run_all_and_dump_failures + group.run(formatter) + formatter.dump_failures + end - it "preserves formatting" do - group.example("example name") { "this".should eq("that") } + it "preserves formatting" do + group.example("example name") { "this".should eq("that") } - run_all_and_dump_failures + run_all_and_dump_failures + + output.string.should =~ /group name example name/m + output.string.should =~ /(\s+)expected \"that\"\n\1 got \"this\"/m + end - output.string.should =~ /group name example name/m - output.string.should =~ /(\s+)expected \"that\"\n\1 got \"this\"/m + context "with an exception without a message" do + it "does not throw NoMethodError" do + exception_without_message = Exception.new() + exception_without_message.stub(:message) { nil } + group.example("example name") { raise exception_without_message } + expect { run_all_and_dump_failures }.not_to raise_error(NoMethodError) end + end - context "with an exception without a message" do - it "does not throw NoMethodError" do - exception_without_message = Exception.new() - exception_without_message.stub(:message) { nil } - group.example("example name") { raise exception_without_message } - expect { run_all_and_dump_failures }.not_to raise_error(NoMethodError) - end + context "with an exception class other than RSpec" do + it "does not show the error class" do + group.example("example name") { raise NameError.new('foo') } + run_all_and_dump_failures + output.string.should =~ /NameError/m end + end - context "with an exception class other than RSpec" do - it "does not show the error class" do - group.example("example name") { raise NameError.new('foo') } - run_all_and_dump_failures - output.string.should =~ /NameError/m - end + context "with a failed expectation (rspec-expectations)" do + it "does not show the error class" do + group.example("example name") { "this".should eq("that") } + run_all_and_dump_failures + output.string.should_not =~ /RSpec/m end + end - context "with a failed expectation (rspec-expectations)" do - it "does not show the error class" do - group.example("example name") { "this".should eq("that") } - run_all_and_dump_failures - output.string.should_not =~ /RSpec/m - end + context "with a failed message expectation (rspec-mocks)" do + it "does not show the error class" do + group.example("example name") { "this".should_receive("that") } + run_all_and_dump_failures + output.string.should_not =~ /RSpec/m end + end - context "with a failed message expectation (rspec-mocks)" do - it "does not show the error class" do - group.example("example name") { "this".should_receive("that") } - run_all_and_dump_failures - output.string.should_not =~ /RSpec/m + context 'for #share_examples_for' do + it 'outputs the name and location' do + + share_examples_for 'foo bar' do + it("example name") { "this".should eq("that") } end + + line = __LINE__.next + group.it_should_behave_like('foo bar') + + run_all_and_dump_failures + + output.string.should include( + 'Shared Example Group: "foo bar" called from ' + + "./spec/rspec/core/formatters/base_text_formatter_spec.rb:#{line}" + ) end - context 'for #share_examples_for' do + context 'that contains nested example groups' do it 'outputs the name and location' do - share_examples_for 'foo bar' do - it("example name") { "this".should eq("that") } + describe 'nested group' do + it("example name") { "this".should eq("that") } + end end line = __LINE__.next @@ -96,98 +119,78 @@ def run_all_and_dump_failures "./spec/rspec/core/formatters/base_text_formatter_spec.rb:#{line}" ) end + end + end - context 'that contains nested example groups' do - it 'outputs the name and location' do - share_examples_for 'foo bar' do - describe 'nested group' do - it("example name") { "this".should eq("that") } - end - end + context 'for #share_as' do + it 'outputs the name and location' do - line = __LINE__.next - group.it_should_behave_like('foo bar') + share_as :FooBar do + it("example name") { "this".should eq("that") } + end - run_all_and_dump_failures + line = __LINE__.next + group.send(:include, FooBar) - output.string.should include( - 'Shared Example Group: "foo bar" called from ' + - "./spec/rspec/core/formatters/base_text_formatter_spec.rb:#{line}" - ) - end - end + run_all_and_dump_failures + + output.string.should include( + 'Shared Example Group: "FooBar" called from ' + + "./spec/rspec/core/formatters/base_text_formatter_spec.rb:#{line}" + ) end - context 'for #share_as' do + context 'that contains nested example groups' do it 'outputs the name and location' do - share_as :FooBar do - it("example name") { "this".should eq("that") } + share_as :NestedFoo do + describe 'nested group' do + describe 'hell' do + it("example name") { "this".should eq("that") } + end + end end line = __LINE__.next - group.send(:include, FooBar) + group.send(:include, NestedFoo) run_all_and_dump_failures output.string.should include( - 'Shared Example Group: "FooBar" called from ' + + 'Shared Example Group: "NestedFoo" called from ' + "./spec/rspec/core/formatters/base_text_formatter_spec.rb:#{line}" ) end - - context 'that contains nested example groups' do - it 'outputs the name and location' do - - share_as :NestedFoo do - describe 'nested group' do - describe 'hell' do - it("example name") { "this".should eq("that") } - end - end - end - - line = __LINE__.next - group.send(:include, NestedFoo) - - run_all_and_dump_failures - - output.string.should include( - 'Shared Example Group: "NestedFoo" called from ' + - "./spec/rspec/core/formatters/base_text_formatter_spec.rb:#{line}" - ) - end - end end end + end - describe "#dump_profile" do - before do - formatter.stub(:examples) do - group = RSpec::Core::ExampleGroup.describe("group") do - example("example") - end - group.run(double('reporter').as_null_object) - group.examples + describe "#dump_profile" do + before do + formatter.stub(:examples) do + group = RSpec::Core::ExampleGroup.describe("group") do + example("example") end + group.run(double('reporter').as_null_object) + group.examples end + end - it "names the example" do - formatter.dump_profile - output.string.should =~ /group example/m - end + it "names the example" do + formatter.dump_profile + output.string.should =~ /group example/m + end - it "prints the time" do - formatter.dump_profile - output.string.should =~ /0(\.\d+)? seconds/ - end + it "prints the time" do + formatter.dump_profile + output.string.should =~ /0(\.\d+)? seconds/ + end - it "prints the path" do - formatter.dump_profile - filename = __FILE__.split(File::SEPARATOR).last + it "prints the path" do + formatter.dump_profile + filename = __FILE__.split(File::SEPARATOR).last - output.string.should =~ /#{filename}\:#{__LINE__ - 21}/ - end + output.string.should =~ /#{filename}\:#{__LINE__ - 21}/ end end end