First implementation of #97 "Guard dependencies".

This commit is contained in:
Rémy Coutable 2011-09-16 01:01:58 +02:00
parent 22001c5ecd
commit b1b69924a7
5 changed files with 119 additions and 32 deletions

View File

@ -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)
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)

View File

@ -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

View File

@ -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
@ -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

View File

@ -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" } }

View File

@ -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