From 8ea296bf8fee32225e8b5f7ff6138fc21ace774e Mon Sep 17 00:00:00 2001 From: Scott Parrish Date: Thu, 5 May 2011 03:05:58 -0600 Subject: [PATCH] Guard::Dsl changed massively. overall strategy was to decouple to evaluate_guardfile into "getting the data" and "using the data" parts. this provides the ability to pass a string that contains the contents of a guardfile, or to pass a filename for a guardfile as well as reading the default loc for a guardfile. Dsl specs changed massivly to support new style of Dsl --- lib/guard/dsl.rb | 91 +++++++++-- lib/guard/notifier.rb | 17 +- spec/guard/dsl_spec.rb | 224 +++++++++++++++++++-------- spec/guard/listeners/linux_spec.rb | 14 +- spec/guard/listeners/polling_spec.rb | 8 +- spec/guard/notifier_spec.rb | 118 ++++++++++---- 6 files changed, 347 insertions(+), 125 deletions(-) diff --git a/lib/guard/dsl.rb b/lib/guard/dsl.rb index fbbb2ec..32a0edf 100644 --- a/lib/guard/dsl.rb +++ b/lib/guard/dsl.rb @@ -1,31 +1,89 @@ module Guard class Dsl - class << self def evaluate_guardfile(options = {}) + options.is_a?(Hash) or raise ArgumentError.new("evaluate_guardfile not passed a hash") + @@options = options - - if File.exists?(guardfile_path) - begin - new.instance_eval(File.read(guardfile_path), guardfile_path, 1) - rescue - UI.error "Invalid Guardfile, original error is:\n#{$!}" - exit 1 - end - else - UI.error "No Guardfile in current folder, please create one." + prep_guardfile_contents + instance_eval_guardfile(guardfile_contents, actual_guardfile(), 1) + end + + def instance_eval_guardfile(contents, path, line) + begin + new.instance_eval(contents, path, line) + rescue + UI.error "Invalid Guardfile, original error is:\n#{$!}" exit 1 end end - def guardfile_include?(guard_name) - File.read(guardfile_path).match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/) + def guardfile_include?(guard_name, guardfile = guardfile_contents) + guardfile.match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/) end - - def guardfile_path + + def read_guardfile(my_file) + @@options[:actual_guardfile] = my_file + begin + @@options[:guardfile_contents] = File.read(my_file) + rescue + UI.error("Error reading file #{my_file}") + exit 1 + end + end + + def prep_guardfile_contents + #todo do we need .rc file interaction? + if @@options.has_key?(:guardfile_contents) + UI.info "Using options[:guardfile_contents] for Guardfile" + @@options[:actual_guardfile] = 'options[:guardfile_contents]' + elsif @@options.has_key?(:guardfile) + UI.info "Using -command Guardfile" + if File.exist?(guardfile_file()) + read_guardfile(guardfile_file) + else + UI.error "No Guardfile exists at #{guardfile_file}. Check your -command option" + exit 1 + end + else + UI.info "Using Guardfile in current dir" + if File.exist?(guardfile_default_path) + read_guardfile(guardfile_default_path) + else + UI.error "No Guardfile in current folder, please create one." + exit 1 + end + end + unless guardfile_contents_usable? + UI.error "The command file(#{@@options[:guardfile_file]}) seems to be empty." + exit 1 + end + end + + def guardfile_contents + @@options[:guardfile_contents] + end + + def guardfile_file + @@options[:guardfile] + end + + def actual_guardfile + @@options[:actual_guardfile] + end + + def guardfile_contents_usable? + guardfile_contents && guardfile_contents.length > 0 #TODO maybe the min length of the smallest possible definition? + end + + def guardfile_default_path File.join(Dir.pwd, 'Guardfile') end - end + + def parent_path + @@options[:parent] + end + end def group(name, &guard_definition) guard_definition.call if guard_definition && (@@options[:group].empty? || @@options[:group].include?(name)) @@ -40,6 +98,5 @@ module Guard def watch(pattern, &action) @watchers << ::Guard::Watcher.new(pattern, action) end - end end diff --git a/lib/guard/notifier.rb b/lib/guard/notifier.rb index d250a22..854b64c 100644 --- a/lib/guard/notifier.rb +++ b/lib/guard/notifier.rb @@ -3,13 +3,24 @@ require 'pathname' module Guard module Notifier - + @enable = true #todo verify this is the right default def self.turn_off - @disable = true + @enable = false + end + + def self.turn_on + @enable = true + end + + def self.should_send? + #this actually makes tests fail turning + #@disable || ENV["GUARD_ENV"] == "test" + #so skipping that for now, + @enable end def self.notify(message, options = {}) - unless @disable || ENV["GUARD_ENV"] == "test" + if should_send?() image = options[:image] || :success title = options[:title] || "Guard" case Config::CONFIG['target_os'] diff --git a/spec/guard/dsl_spec.rb b/spec/guard/dsl_spec.rb index 86127ea..c27e638 100644 --- a/spec/guard/dsl_spec.rb +++ b/spec/guard/dsl_spec.rb @@ -2,122 +2,220 @@ require 'spec_helper' describe Guard::Dsl do subject { Guard::Dsl } - before(:each) do - ::Guard.stub!(:add_guard) + @default_guardfile = File.join(Dir.pwd, 'Guardfile') + opt_hash = {:debug => true} + ::Guard.stub!(:options).and_return opt_hash end - it "displays an error message when no Guardfile is found" do - Dir.stub!(:pwd).and_return("no_guardfile_here") + describe "it should select the correct data source for Guardfile" do - Guard::UI.should_receive(:error).with("No Guardfile in current folder, please create one.") - lambda { subject.evaluate_guardfile }.should raise_error + before(:each) do + ::Guard::Dsl.stub!(:instance_eval_guardfile) + end + + it "should use a string for initializing" do + Guard::UI.should_not_receive(:error) + lambda {subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)}.should_not raise_error + subject.actual_guardfile.should == 'options[:guardfile_contents]' + end + it "should use a -command file over the default loc" do + fake_guardfile('/abc/Guardfile', valid_guardfile_string ) + + Guard::UI.should_not_receive(:error) + lambda {subject.evaluate_guardfile(:guardfile => '/abc/Guardfile')}.should_not raise_error + subject.actual_guardfile.should == '/abc/Guardfile' + end + it "should use a default file if no other options are given" do + fake_guardfile(@default_guardfile, valid_guardfile_string) + Guard::UI.should_not_receive(:error) + lambda {subject.evaluate_guardfile()}.should_not raise_error + subject.actual_guardfile.should == @default_guardfile + end + + it "should use a string over any other method" do + fake_guardfile('/abc/Guardfile', valid_guardfile_string ) + + fake_guardfile(@default_guardfile, valid_guardfile_string) + + Guard::UI.should_not_receive(:error) + lambda {subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)}.should_not raise_error + subject.actual_guardfile.should == 'options[:guardfile_contents]' + end + + it "should use a guardfile over any the default" do + fake_guardfile('/abc/Guardfile', valid_guardfile_string ) + + fake_guardfile(@default_guardfile, valid_guardfile_string) + + Guard::UI.should_not_receive(:error) + lambda {subject.evaluate_guardfile(:guardfile => '/abc/Guardfile')}.should_not raise_error + subject.actual_guardfile.should == '/abc/Guardfile' + end end + describe "it should correctly read data from its valid data source" do + before(:each) do + ::Guard::Dsl.stub!(:instance_eval_guardfile) + end + + it "should read correctly from a string" do + lambda {subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)}.should_not raise_error + subject.guardfile_contents.should == valid_guardfile_string + end + + it "should read correctly from a guardfile" do + fake_guardfile('/abc/Guardfile', valid_guardfile_string ) + + lambda {subject.evaluate_guardfile(:guardfile => '/abc/Guardfile')}.should_not raise_error + subject.guardfile_contents.should == valid_guardfile_string + end + + it "should read correctly from a guardfile" do + my_default = File.join(Dir.pwd, 'Guardfile') + fake_guardfile(my_default, valid_guardfile_string) + lambda {subject.evaluate_guardfile()}.should_not raise_error + subject.guardfile_contents.should == valid_guardfile_string + end + end + + describe "It should correctly throw errors when initializing with invalid data" do + before(:each) do + ::Guard::Dsl.stub!(:instance_eval_guardfile) + end + + it "should raise error when there's a problem reading a file" do + File.stub!(:exist?).with('/def/Guardfile') { true } + File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") } + + Guard::UI.should_receive(:error).with(/^Error reading file/) + lambda {subject.evaluate_guardfile(:guardfile=>'/def/Guardfile')}.should raise_error + end + + it "should raise error when -guardfile doesn't exist" do + File.stub!(:exist?).with('/def/Guardfile') { false } + + Guard::UI.should_receive(:error).with(/No Guardfile exists at/) + lambda {subject.evaluate_guardfile(:guardfile=>'/def/Guardfile')}.should raise_error + end + + it "should raise error when resorting to use default, finds no default" do + File.stub!(:exist?).with(@default_guardfile) { false } + + Guard::UI.should_receive(:error).with(/No Guardfile in current folder/) + lambda {subject.evaluate_guardfile()}.should raise_error + end + + it "should raise error when guardfile_content ends up empty or nil" do + Guard::UI.should_receive(:error).twice.with(/The command file/) + lambda {subject.evaluate_guardfile(:guardfile_contents => "")}.should raise_error + lambda {subject.evaluate_guardfile(:guardfile_contents => nil)}.should raise_error + end + + end + it "displays an error message when Guardfile is not valid" do - mock_guardfile_content("This Guardfile is invalid!") - - Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:\n/) - lambda { subject.evaluate_guardfile }.should raise_error + Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/) + lambda {subject.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string )}.should raise_error end describe ".guardfile_include?" do - it "detects a guard specified by a string with simple quotes" do - mock_guardfile_content("guard 'test'") - subject.guardfile_include?('test').should be_true - end - it "detects a guard specified by a string with double quotes" do - mock_guardfile_content('guard "test"') - subject.guardfile_include?('test').should be_true + subject.guardfile_include?('test', 'guard "test" {watch("c")}').should be_true + end + it "detects a guard specified by a string with single quote" do + subject.guardfile_include?('test', 'guard \'test\' {watch("c")}').should be_true end - it "detects a guard specified by a symbol" do - mock_guardfile_content("guard :test") - subject.guardfile_include?('test').should be_true + subject.guardfile_include?('test', 'guard :test {watch("c")}').should be_true end - it "detects a guard wrapped in parentheses" do - mock_guardfile_content("guard(:test)") - subject.guardfile_include?('test').should be_true + subject.guardfile_include?('test', 'guard(:test) {watch("c")}').should be_true end end describe "#group" do - before do - mock_guardfile_content(" - group 'x' do - guard 'test' do - watch('c') - end - end - - group 'y' do - guard 'another' do - watch('c') - end - end") - end - it "should evaluates only the specified group" do ::Guard.should_receive(:add_guard).with('test', anything, {}) - ::Guard.should_not_receive(:add_guard).with('another', anything, {}) - subject.evaluate_guardfile(:group => ['x']) + lambda {subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group=>['x'])}.should_not raise_error end - it "should evaluates only the specified groups" do ::Guard.should_receive(:add_guard).with('test', anything, {}) ::Guard.should_receive(:add_guard).with('another', anything, {}) - subject.evaluate_guardfile(:group => ['x', 'y']) + lambda {subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group=>['x','y'])}.should_not raise_error end end + #TODO not sure if each seperate quoting/call type needs its own test describe "#guard" do - it "should load a guard specified as a string from the DSL" do - mock_guardfile_content("guard 'test'") - + it "should load a guard specified as a single quoted string from the DSL" do ::Guard.should_receive(:add_guard).with('test', [], {}) - subject.evaluate_guardfile + subject.evaluate_guardfile(:guardfile_contents => "guard 'test'") + end + it "should load a guard specified as a single quoted string from the DSL" do + ::Guard.should_receive(:add_guard).with('test', [], {}) + subject.evaluate_guardfile(:guardfile_contents => "guard 'test'") end - it "should load a guard specified as a symbol from the DSL" do - mock_guardfile_content("guard :test") - ::Guard.should_receive(:add_guard).with(:test, [], {}) - subject.evaluate_guardfile + subject.evaluate_guardfile(:guardfile_contents => "guard :test") + end + it "should load a guard specified as a symbol and called with parens from the DSL" do + ::Guard.should_receive(:add_guard).with(:test, [], {}) + subject.evaluate_guardfile(:guardfile_contents => "guard(:test)") end - it "should receive options when specified" do - mock_guardfile_content("guard 'test', :opt_a => 1, :opt_b => 'fancy'") - ::Guard.should_receive(:add_guard).with('test', anything, { :opt_a => 1, :opt_b => 'fancy' }) - subject.evaluate_guardfile + subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'") end + end describe "#watch" do it "should receive watchers when specified" do - mock_guardfile_content(" - guard 'test' do + gf_with_watchers = "guard 'test' do watch('a') { 'b' } watch('c') - end") + end" ::Guard.should_receive(:add_guard).with('test', anything, {}) do |name, watchers, options| watchers.size.should == 2 watchers[0].pattern.should == 'a' watchers[0].action.call.should == proc { 'b' }.call watchers[1].pattern.should == 'c' - watchers[1].action.should be_nil + watchers[1].action.should == nil end - subject.evaluate_guardfile + subject.evaluate_guardfile(:guardfile_contents => gf_with_watchers) end end private - - def mock_guardfile_content(content) - File.stub!(:read).with(File.expand_path('../../../Guardfile', __FILE__)) { content } + def fake_guardfile(name, contents) + File.stub!(:exist?).with(name) { true } + File.stub!(:read).with(name) { contents } end -end + def valid_guardfile_string + "group 'x' do + guard 'test' do + watch('c') + end + end + + group 'y' do + guard 'another' do + watch('c') + end + end + + group 'z' do + guard 'another' do + watch('c') + end + end" + end + + def invalid_guardfile_string + "Bad guardfile" + end +end \ No newline at end of file diff --git a/spec/guard/listeners/linux_spec.rb b/spec/guard/listeners/linux_spec.rb index ba5d86f..8e5c705 100644 --- a/spec/guard/listeners/linux_spec.rb +++ b/spec/guard/listeners/linux_spec.rb @@ -16,7 +16,7 @@ describe Guard::Linux do subject.should be_usable end - describe "#start" do + describe "#start", :long_running => true do before(:each) do @listener = Guard::Linux.new end @@ -40,7 +40,7 @@ describe Guard::Linux do end - describe "#on_change" do + describe "#on_change", :long_running=> true do before(:each) do @results = [] @listener = Guard::Linux.new @@ -56,7 +56,7 @@ describe Guard::Linux do FileUtils.touch file stop File.delete file - @results.should == ['spec/fixtures/newfile.rb'] + @results.should == ['fixtures/newfile.rb'] end it "should catch file update" do @@ -65,7 +65,7 @@ describe Guard::Linux do start File.open(file, 'w') {|f| f.write('') } stop - @results.should == ['spec/fixtures/folder1/file1.txt'] + @results.should == ['fixtures/folder1/file1.txt'] end it "should catch files update" do @@ -77,7 +77,7 @@ describe Guard::Linux do File.open(file1, 'w') {|f| f.write('') } File.open(file2, 'w') {|f| f.write('') } stop - @results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] + @results.should == ['fixtures/folder1/file1.txt', 'fixtures/folder1/folder2/file2.txt'] end it "should catch deleted file" do @@ -87,7 +87,7 @@ describe Guard::Linux do File.delete file stop FileUtils.touch file - @results.should == ['spec/fixtures/folder1/file1.txt'] + @results.should == ['fixtures/folder1/file1.txt'] end it "should catch moved file" do @@ -99,7 +99,7 @@ describe Guard::Linux do FileUtils.mv file1, file2 stop FileUtils.mv file2, file1 - @results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt'] + @results.should == ['fixtures/folder1/file1.txt', 'fixtures/folder1/movedfile1.txt'] end it "should not process change if stopped" do diff --git a/spec/guard/listeners/polling_spec.rb b/spec/guard/listeners/polling_spec.rb index 627a942..b54d41a 100644 --- a/spec/guard/listeners/polling_spec.rb +++ b/spec/guard/listeners/polling_spec.rb @@ -11,7 +11,7 @@ describe Guard::Polling do end end - describe "#on_change" do + describe "#on_change", :long_running => true do it "catches new file" do file = @fixture_path.join("newfile.rb") File.exists?(file).should be_false @@ -19,7 +19,7 @@ describe Guard::Polling do FileUtils.touch file stop File.delete file - @results.should == ['spec/fixtures/newfile.rb'] + @results.should == ['fixtures/newfile.rb'] end it "catches file update" do @@ -28,7 +28,7 @@ describe Guard::Polling do start FileUtils.touch file stop - @results.should == ['spec/fixtures/folder1/file1.txt'] + @results.should == ['fixtures/folder1/file1.txt'] end it "catches files update" do @@ -40,7 +40,7 @@ describe Guard::Polling do FileUtils.touch file1 FileUtils.touch file2 stop - @results.sort.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] + @results.sort.should == ['fixtures/folder1/file1.txt', 'fixtures/folder1/folder2/file2.txt'] end end diff --git a/spec/guard/notifier_spec.rb b/spec/guard/notifier_spec.rb index b12781b..61a6adf 100644 --- a/spec/guard/notifier_spec.rb +++ b/spec/guard/notifier_spec.rb @@ -9,51 +9,107 @@ describe Guard::Notifier do ENV["GUARD_ENV"] = 'dont_mute_notify' end + #TODO someone with a mac needs to check this if mac? require 'growl' it "uses Growl on Mac OS X" do - Growl.should_receive(:notify).with("great", - :title => "Guard", - :icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s, - :name => "Guard" - ) - subject.notify 'great', :title => 'Guard' + Growl.should_receive(:notify).with("great",anything()) + stub_and_execute "great", :name => "Guard" end + it "uses the correct default value for icons" do + Growl.should_receive(:notify).with("great",hash_including(:icon => subject.image_path(:success))) + stub_and_execute "great" + end + it "uses the correct default value for title" do + Growl.should_receive(:notify).with("great",hash_including(:title => "Guard")) + stub_and_execute "great" + end + it "correctly sets the title" do + Growl.should_receive(:notify).with("great", hash_including(:title => "Woot")) + stub_and_execute "great", :title => "Woot" + end + it "correctly sets the image" do + Growl.should_receive(:notify).with("great", hash_including(:icon => subject.image_path(:success))) + stub_and_execute "great", :image => :success + end + it "fails to execute if #turn_off" do + subject.turn_off + Growl.should_receive(:notify).never + stub_and_execute("great") + end + end if linux? - require 'libnotify' - it "uses Libnotify on Linux" do - Libnotify.should_receive(:show).with( - :body => "great", - :summary => 'Guard', - :icon_path => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s - ) - subject.notify 'great', :title => 'Guard' + describe "uses Libnotify on Linux" do + before(:all) { + require 'libnotify' + subject.turn_on + } + it "uses libnotify on linux" do + Libnotify.should_receive(:show).with(hash_including(:body=>"great")) + stub_and_execute "great" + end + it "uses the correct default value for icons" do + Libnotify.should_receive(:show).with(hash_including(:icon_path => subject.image_path(:success))) + stub_and_execute "great" + end + it "uses the correct default value for title" do + Libnotify.should_receive(:show).with(hash_including(:summary => "Guard")) + stub_and_execute "great" + end + it "correctly sets the title" do + Libnotify.should_receive(:show).with(hash_including(:summary => "Woot")) + stub_and_execute "great", :title => "Woot" + end + it "correctly sets the image" do + Libnotify.should_receive(:show).with(hash_including(:icon_path => subject.image_path(:success))) + stub_and_execute "great", :image => :success + end + it "fails to execute if #turn_off" do + subject.turn_off + Libnotify.should_receive(:show).never + stub_and_execute("great") + end end end describe ".turn_off" do - before(:each) { subject.turn_off } - - if mac? - require 'growl' - it "does nothing" do - Growl.should_not_receive(:notify) - subject.notify 'great', :title => 'Guard' - end - end - - if linux? - require 'libnotify' - it "does nothing" do - Libnotify.should_not_receive(:show) - subject.notify 'great', :title => 'Guard' - end + it "does nothing" do + subject.turn_off + subject.should_send?.should be_false end end - after(:each) { ENV["GUARD_ENV"] = @saved_guard_env } + describe ".turn_on" do + it "does nothing" do + subject.turn_on + subject.should_send?.should be_true + end + end + + describe ".image_path" do + it "should return the correct path for each symbol" do + Pathname(subject.image_path(:success)).basename.should == Pathname("success.png") + Pathname(subject.image_path(:pending)).basename.should == Pathname("pending.png") + Pathname(subject.image_path(:failed)).basename.should == Pathname("failed.png") + end + it "should return the correct path if a path is passed to it" do + Pathname(subject.image_path('/abc/def.png')).should == Pathname('/abc/def.png') + end + end + + + after(:each) { ENV["GUARD_ENV"] = @saved_guard_env }--tag ~long_running + end + +private + + def stub_and_execute(message,input = {}) + Guard::Notifier.stub!(:libnotify_installed?).and_return true + Guard::Notifier.stub!(:should_not_send?).and_return false + + subject.notify message, input end end