diff --git a/Guardfile b/Guardfile index 6f5fb755..4433f010 100644 --- a/Guardfile +++ b/Guardfile @@ -1,4 +1,4 @@ -guard 'rspec', :cli => "-c -f doc" do +guard 'rspec', :version => 2 do watch(%r{^spec/.+_spec\.rb}) watch(%r{^lib/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } diff --git a/README.rdoc b/README.rdoc index 3e4a837c..0ef28b35 100644 --- a/README.rdoc +++ b/README.rdoc @@ -77,6 +77,7 @@ Former :color, :drb, :fail_fast and :formatter false # don't display Growl (or Libnotify) notification after the specs are done running, default: true :all_after_pass => false # don't run all specs after changed specs pass, default: true :all_on_start => false # don't run all the specs at startup, default: true + :keep_failed => true # keep failed specs untill them pass (useful with {:focus RSpec option}[http://relishapp.com/rspec/rspec-core/v/2-6-rc/dir/filtering/inclusion-filters]), default: true == Notification diff --git a/lib/guard/rspec.rb b/lib/guard/rspec.rb index 3a022810..d7997a9a 100644 --- a/lib/guard/rspec.rb +++ b/lib/guard/rspec.rb @@ -3,41 +3,50 @@ module Guard class RSpec < Guard - autoload :Runner, 'guard/rspec/runner' autoload :Inspector, 'guard/rspec/inspector' def initialize(watchers=[], options={}) super - @all_after_pass = options.delete(:all_after_pass) - @all_on_start = options.delete(:all_on_start) + @options = { + :all_after_pass => true, + :all_on_start => true, + :keep_failed => true + }.update(options) + @last_failed = false + @failed_paths = [] + Runner.set_rspec_version(options) end # Call once when guard starts def start UI.info "Guard::RSpec is running, with RSpec #{Runner.rspec_version}!" - run_all unless @all_on_start == false + run_all if @options[:all_on_start] end def run_all - @last_failed = !Runner.run(["spec"], options.merge(:message => "Running all specs")) + passed = Runner.run(["spec"], options.merge(:message => "Running all specs")) + + @failed_paths = [] if passed + @last_failed = !passed end def run_on_change(paths) paths = Inspector.clean(paths) - passed = Runner.run(paths, options) + paths += @failed_paths if @options[:keep_failed] + passed = Runner.run(paths.uniq, options) - if @all_after_pass == false - passed - else + if passed + # clean failed paths memory + @failed_paths -= paths if @options[:keep_failed] # run all the specs if the changed specs failed, like autotest - if passed && @last_failed - run_all - else - # track whether the changed specs failed for the next change - @last_failed = !passed - end + run_all if @last_failed && @options[:all_after_pass] + else + # remember failed paths for the next change + @failed_paths += paths if @options[:keep_failed] + # track whether the changed specs failed for the next change + @last_failed = true end end diff --git a/spec/guard/rspec_spec.rb b/spec/guard/rspec_spec.rb index 6aa4e2b4..9b2b8bf8 100644 --- a/spec/guard/rspec_spec.rb +++ b/spec/guard/rspec_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Guard::RSpec do + let(:default_options) { { :all_after_pass => true, :all_on_start => true, :keep_failed => true } } subject { Guard::RSpec.new } describe '#initialize' do @@ -12,12 +13,12 @@ describe "#start" do it "calls #run_all" do - Guard::RSpec::Runner.should_receive(:run).with(["spec"], :message => "Running all specs") + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs")) subject.start end it "doesn't call #run_all if the :all_on_start option is false" do - Guard::RSpec::Runner.should_not_receive(:run).with(["spec"], :message => "Running all specs") + Guard::RSpec::Runner.should_not_receive(:run).with(["spec"], default_options.merge(:all_on_start => false, :message => "Running all specs")) subject = Guard::RSpec.new([], :all_on_start => false) subject.start end @@ -25,48 +26,67 @@ describe "#run_all" do it "runs all specs" do - Guard::RSpec::Runner.should_receive(:run).with(["spec"], :message => "Running all specs") + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs")) subject.run_all end it "directly passes :cli option to runner" do subject = Guard::RSpec.new([], { :cli => "--color" }) - Guard::RSpec::Runner.should_receive(:run).with(["spec"], :message => "Running all specs", :cli => "--color") + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs", :cli => "--color")) subject.run_all end + + it "should clean failed memory if passed" do + Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], default_options).and_return(false) + subject.run_on_change(["spec/foo"]) + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs")).and_return(true) + subject.run_all + Guard::RSpec::Runner.should_receive(:run).with(["spec/bar"], default_options).and_return(true) + subject.run_on_change(["spec/bar"]) + end end describe "#run_on_change" do it "runs rspec with paths" do - Guard::RSpec::Runner.should_receive(:run).with(["spec"], {}) + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options) subject.run_on_change(["spec"]) end it "directly passes :cli option to runner" do subject = Guard::RSpec.new([], { :cli => "--color" }) - Guard::RSpec::Runner.should_receive(:run).with(["spec"], :cli => "--color") + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:cli => "--color")) subject.run_on_change(["spec"]) end it "calls #run_all if the changed specs pass after failing" do - Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], {}).and_return(false, true) - Guard::RSpec::Runner.should_receive(:run).with(["spec"], :message => "Running all specs") + Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], default_options).and_return(false, true) + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs")) subject.run_on_change(["spec/foo"]) subject.run_on_change(["spec/foo"]) end it "doesn't call #run_all if the changed specs pass after failing but the :all_after_pass option is false" do subject = Guard::RSpec.new([], :all_after_pass => false) - Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], {}).and_return(false, true) - Guard::RSpec::Runner.should_not_receive(:run).with(["spec"], :message => "Running all specs") + Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], default_options.merge(:all_after_pass => false)).and_return(false, true) + Guard::RSpec::Runner.should_not_receive(:run).with(["spec"], default_options.merge(:all_after_pass => false, :message => "Running all specs")) subject.run_on_change(["spec/foo"]) subject.run_on_change(["spec/foo"]) end it "doesn't call #run_all if the changed specs pass without failing" do - Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], {}).and_return(true) - Guard::RSpec::Runner.should_not_receive(:run).with(["spec"], :message => "Running all specs") + Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], default_options).and_return(true) + Guard::RSpec::Runner.should_not_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs")) + subject.run_on_change(["spec/foo"]) + end + + it "should keep failed spec and rerun later" do + Guard::RSpec::Runner.should_receive(:run).with(["spec/foo"], default_options).and_return(false) subject.run_on_change(["spec/foo"]) + Guard::RSpec::Runner.should_receive(:run).with(["spec/bar", "spec/foo"], default_options).and_return(true) + Guard::RSpec::Runner.should_receive(:run).with(["spec"], default_options.merge(:message => "Running all specs")).and_return(true) + subject.run_on_change(["spec/bar"]) + Guard::RSpec::Runner.should_receive(:run).with(["spec/bar"], default_options).and_return(true) + subject.run_on_change(["spec/bar"]) end end