From b1b69924a7f921a643974c6a23b3ba84854c0a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Coutable?= Date: Fri, 16 Sep 2011 01:01:58 +0200 Subject: [PATCH 01/21] First implementation of #97 "Guard dependencies". --- lib/guard.rb | 49 +++++++++++++++++++--------- lib/guard/dsl.rb | 4 ++- spec/guard/dsl_spec.rb | 24 ++++++++++---- spec/guard_spec.rb | 73 ++++++++++++++++++++++++++++++++++++------ spec/spec_helper.rb | 1 + 5 files changed, 119 insertions(+), 32 deletions(-) diff --git a/lib/guard.rb b/lib/guard.rb index 8f225ee..1e14a16 100644 --- a/lib/guard.rb +++ b/lib/guard.rb @@ -16,7 +16,7 @@ module Guard def setup(options = {}) @options = options @guards = [] - @groups = [:default] + @groups = [{ :name => :default, :options => {} }] @interactor = Interactor.new @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd) @@ -39,7 +39,8 @@ module Guard end UI.info "Guard is now watching at '#{listener.directory}'" - guards.each { |guard| supervised_task(guard, :start) } + + execute_supervised_task_for_all_guards(:start) interactor.start listener.start @@ -48,19 +49,19 @@ module Guard def stop UI.info "Bye bye...", :reset => true listener.stop - guards.each { |guard| supervised_task(guard, :stop) } + execute_supervised_task_for_all_guards(:stop) abort end def reload run do - guards.each { |guard| supervised_task(guard, :reload) } + execute_supervised_task_for_all_guards(:reload) end end def run_all run do - guards.each { |guard| supervised_task(guard, :run_all) } + execute_supervised_task_for_all_guards(:run_all) end end @@ -77,13 +78,7 @@ module Guard def run_on_change(files) run do - guards.each do |guard| - paths = Watcher.match_files(guard, files) - unless paths.empty? - UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}" - supervised_task(guard, :run_on_change, paths) - end - end + execute_supervised_task_for_all_guards(:run_on_change, files) end end @@ -99,13 +94,37 @@ module Guard listener.unlock end + def execute_supervised_task_for_all_guards(task, files = nil) + groups.each do |group_hash| + catch :task_has_failed do + guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard| + paths = Watcher.match_files(guard, files) if files + if paths && !paths.empty? + UI.debug "#{guard.class.name}##{task} with #{paths.inspect}" + supervised_task(guard, task, paths) + else + supervised_task(guard, task) + end + end + end + end + end + # Let a guard execute its task but # fire it if his work leads to a system failure def supervised_task(guard, task_to_supervise, *args) guard.hook("#{task_to_supervise}_begin", *args) result = guard.send(task_to_supervise, *args) guard.hook("#{task_to_supervise}_end", result) - result + + group = @groups.find { |group| group[:name] == guard.group } + if result === false && group[:options][:halt_on_fail] == true + UI.error "#{guard.class.name}##{task_to_supervise} failed." + throw :task_has_failed + else + result + end + rescue Exception => ex UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" + "\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}") @@ -124,8 +143,8 @@ module Guard end end - def add_group(name) - @groups << name.to_sym unless name.nil? + def add_group(name, options = {}) + @groups << { :name => name.to_sym, :options => options } unless name.nil? || @groups.find { |group| group[:name] == name } end def get_guard_class(name) diff --git a/lib/guard/dsl.rb b/lib/guard/dsl.rb index fdf736b..8c72324 100644 --- a/lib/guard/dsl.rb +++ b/lib/guard/dsl.rb @@ -15,6 +15,7 @@ module Guard def reevaluate_guardfile ::Guard.guards.clear + ::Guard.groups.clear @@options.delete(:guardfile_contents) Dsl.evaluate_guardfile(@@options) msg = "Guardfile has been re-evaluated." @@ -112,11 +113,12 @@ module Guard end - def group(name, &guard_definition) + def group(name, options = {}, &guard_definition) @groups = @@options[:group] || [] name = name.to_sym if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name)) + ::Guard.add_group(name.to_s.downcase.to_sym, options) @current_group = name guard_definition.call @current_group = nil diff --git a/spec/guard/dsl_spec.rb b/spec/guard/dsl_spec.rb index 2050ee6..ed2c049 100644 --- a/spec/guard/dsl_spec.rb +++ b/spec/guard/dsl_spec.rb @@ -9,6 +9,7 @@ describe Guard::Dsl do @local_guardfile_path = File.join(Dir.pwd, 'Guardfile') @home_guardfile_path = File.expand_path(File.join("~", ".Guardfile")) @user_config_path = File.expand_path(File.join("~", ".guard.rb")) + ::Guard.setup ::Guard.stub!(:options).and_return(:debug => true) ::Guard.stub!(:guards).and_return([mock('Guard')]) end @@ -220,16 +221,16 @@ describe Guard::Dsl do describe "#ignore_paths" do disable_user_config - + it "adds the paths to the listener's ignore_paths" do ::Guard.stub!(:listener).and_return(mock('Listener')) ::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz']) - + subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'") ignore_paths.should == ['faz', 'foo', 'bar'] end end - + describe "#group" do disable_user_config @@ -238,6 +239,8 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w']) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] end it "evaluates only the specified symbol group" do @@ -245,15 +248,19 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] end - it "evaluates only the specified groups" do + it "evaluates only the specified groups (with their options)" do ::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with(:rspec, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with(:ronn, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y]) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :x, :options => { :halt_on_fail => true } }, { :name => :y, :options => {} }] end it "evaluates always guard outside any group (even when a group is given)" do @@ -261,9 +268,11 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] end - it "evaluates all groups when no group option is specified" do + it "evaluates all groups when no group option is specified (with their options)" do ::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with(:rspec, [], [], { :group => :x }) @@ -271,6 +280,9 @@ describe Guard::Dsl do ::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y }) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) + + ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }, { :name => :x, :options => { :halt_on_fail => true } }, { :name => :y, :options => {} }] + end end @@ -365,7 +377,7 @@ private guard 'test' end - group :x do + group :x, :halt_on_fail => true do guard 'rspec' guard :ronn end diff --git a/spec/guard_spec.rb b/spec/guard_spec.rb index 1a556b4..7a97e46 100644 --- a/spec/guard_spec.rb +++ b/spec/guard_spec.rb @@ -15,7 +15,7 @@ describe Guard do end it "initializes @groups" do - Guard.groups.should eql [:default] + Guard.groups.should eql [{ :name => :default, :options => {} }] end it "initializes the options" do @@ -129,22 +129,25 @@ describe Guard do end end - describe ".add_group" do - before(:each) do - Guard.setup - end + subject { ::Guard.setup } it "accepts group name as string" do - Guard.add_group('backend') + subject.add_group('backend') - Guard.groups.should eql [:default, :backend] + subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => {} }] end it "accepts group name as symbol" do - Guard.add_group(:backend) + subject.add_group(:backend) - Guard.groups.should eql [:default, :backend] + subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => {} }] + end + + it "accepts options" do + subject.add_group(:backend, { :halt_on_fail => true }) + + subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => { :halt_on_fail => true } }] end end @@ -223,12 +226,54 @@ describe Guard do end end + describe ".execute_supervised_task_for_all_guards" do + subject { ::Guard.setup } + + before do + class Guard::Dummy < Guard::Guard; end + + subject.add_group(:foo, { :halt_on_fail => true }) + subject.add_group(:bar) + subject.add_guard(:dummy, [], [], { :group => :foo }) + subject.add_guard(:dummy, [], [], { :group => :foo }) + subject.add_guard(:dummy, [], [], { :group => :bar }) + subject.add_guard(:dummy, [], [], { :group => :bar }) + @sum = { :foo => 0, :bar => 0} + end + + context "all tasks succeed" do + before do + subject.guards.each { |guard| guard.stub!(:task) { @sum[guard.group] += 1; true } } + end + + it "executes the task for each guard in each group" do + subject.execute_supervised_task_for_all_guards(:task) + + @sum.all? { |k, v| v == 2 }.should be_true + end + end + + context "one guard fails (by returning false)" do + before do + subject.guards.each_with_index { |g, i| g.stub!(:task) { @sum[g.group] += i+1; i != 0 && i != 2 } } + end + + it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do + subject.execute_supervised_task_for_all_guards(:task) + + @sum[:foo].should eql 1 + @sum[:bar].should eql 7 + end + end + end + describe ".supervised_task" do subject { ::Guard.setup } - before(:each) do + before do @g = mock(Guard::Guard).as_null_object subject.guards.push(@g) + subject.groups.push({ :name => :foo, :options => { :halt_on_fail => true } }) end context "with a task that succeed" do @@ -278,6 +323,14 @@ describe Guard do end end + context "with a task that return false and guard's group has the :halt_on_fail option == true" do + before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { false } } + + it "throws :task_has_failed" do + expect { subject.supervised_task(@g, :failing) }.to throw_symbol(:task_has_failed) + end + end + context "with a task that raises an exception" do before(:each) { @g.stub!(:failing) { raise "I break your system" } } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1520849..a041ca6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,6 +12,7 @@ RSpec.configure do |config| config.color_enabled = true config.filter_run :focus => true + config.treat_symbols_as_metadata_keys_with_true_values = true config.run_all_when_everything_filtered = true config.before(:each) do From 078d55f13ce6f63e3347cb170d7bc5fa3c754218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Coutable?= Date: Fri, 16 Sep 2011 01:20:22 +0200 Subject: [PATCH 02/21] Actually halt guards' execution in a group only when the guard's task throw :task_has_failed (not when it returns false). --- lib/guard.rb | 11 ++--------- spec/guard_spec.rb | 13 +++++++++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/guard.rb b/lib/guard.rb index 1e14a16..995b3ad 100644 --- a/lib/guard.rb +++ b/lib/guard.rb @@ -96,7 +96,7 @@ module Guard def execute_supervised_task_for_all_guards(task, files = nil) groups.each do |group_hash| - catch :task_has_failed do + catch group_hash[:options][:halt_on_fail] == true ? :task_has_failed : nil do guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard| paths = Watcher.match_files(guard, files) if files if paths && !paths.empty? @@ -116,14 +116,7 @@ module Guard guard.hook("#{task_to_supervise}_begin", *args) result = guard.send(task_to_supervise, *args) guard.hook("#{task_to_supervise}_end", result) - - group = @groups.find { |group| group[:name] == guard.group } - if result === false && group[:options][:halt_on_fail] == true - UI.error "#{guard.class.name}##{task_to_supervise} failed." - throw :task_has_failed - else - result - end + result rescue Exception => ex UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" + diff --git a/spec/guard_spec.rb b/spec/guard_spec.rb index 7a97e46..714df2c 100644 --- a/spec/guard_spec.rb +++ b/spec/guard_spec.rb @@ -255,7 +255,16 @@ describe Guard do context "one guard fails (by returning false)" do before do - subject.guards.each_with_index { |g, i| g.stub!(:task) { @sum[g.group] += i+1; i != 0 && i != 2 } } + subject.guards.each_with_index do |g, i| + g.stub!(:task) do + @sum[g.group] += i+1 + if i % 2 == 0 + throw :task_has_failed + else + true + end + end + end end it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do @@ -324,7 +333,7 @@ describe Guard do end context "with a task that return false and guard's group has the :halt_on_fail option == true" do - before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { false } } + before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { throw :task_has_failed } } it "throws :task_has_failed" do expect { subject.supervised_task(@g, :failing) }.to throw_symbol(:task_has_failed) From 1608b17501b41f374256419faa48794d0a6579a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Coutable?= Date: Fri, 16 Sep 2011 01:20:42 +0200 Subject: [PATCH 03/21] Build only master and guard_dependencies branches on Travis --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea7380f..5cc9a5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,7 @@ rvm: branches: only: - master - - hook - - stdin - - jruby_travis_ctime_bug + - guard_dependencies notifications: recipients: - thibaud@thibaud.me From f9521fe0feea48096338ccee47cb05aae04ea906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Coutable?= Date: Fri, 16 Sep 2011 01:26:27 +0200 Subject: [PATCH 04/21] nil is not accepted for catch's arg in other implementation than 1.9.2 --- lib/guard.rb | 2 +- man/guard | 119 +++++++++++++++++++++ man/guard.html | 277 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 397 insertions(+), 1 deletion(-) diff --git a/lib/guard.rb b/lib/guard.rb index 995b3ad..459f2ac 100644 --- a/lib/guard.rb +++ b/lib/guard.rb @@ -96,7 +96,7 @@ module Guard def execute_supervised_task_for_all_guards(task, files = nil) groups.each do |group_hash| - catch group_hash[:options][:halt_on_fail] == true ? :task_has_failed : nil do + catch group_hash[:options][:halt_on_fail] == true ? :task_has_failed : :no_catch do guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard| paths = Watcher.match_files(guard, files) if files if paths && !paths.empty? diff --git a/man/guard b/man/guard index b1a93f0..0973f56 100644 --- a/man/guard +++ b/man/guard @@ -17,6 +17,89 @@ . .nf +NAME +NAME +NAME +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • guard
  • +
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +\fBguard\fR +. +.P +\fI!DOCTYPE html\fR +. +.P +. +.P +. +.IP "" 4 +. +.nf + +NAME +NAME +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • guard
  • +
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +\fBguard\fR +. +.P +\fI!DOCTYPE html\fR +. +.P +. +.P +. +.IP "" 4 +. +.nf + NAME . .fi @@ -66,4 +149,40 @@ .P . .P +. +.P +. +.IP "" 4 +. +.nf + +
  • +
  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P +. +.P +. +.IP "" 4 +. +.nf + +
  • +
  • September 2011
  • +
  • guard
  • +. +.fi +. +.IP "" 0 +. +.P +. +.P diff --git a/man/guard.html b/man/guard.html index ecd1ac7..1c7dd12 100644 --- a/man/guard.html +++ b/man/guard.html @@ -55,6 +55,8 @@
      @@ -127,6 +129,8 @@