diff --git a/spec/guard/listener_spec.rb b/spec/guard/listener_spec.rb index b0de092..e7b712c 100644 --- a/spec/guard/listener_spec.rb +++ b/spec/guard/listener_spec.rb @@ -2,32 +2,32 @@ require 'spec_helper' describe Guard::Listener do - describe ".select_and_init" do + describe '.select_and_init' do before(:each) { @target_os = RbConfig::CONFIG['target_os'] } after(:each) { RbConfig::CONFIG['target_os'] = @target_os } - it "uses the Darwin listener on Mac OS X" do + it 'uses the Darwin listener on Mac OS X' do RbConfig::CONFIG['target_os'] = 'darwin10.4.0' Guard::Darwin.stub(:usable?).and_return(true) Guard::Darwin.should_receive(:new) described_class.select_and_init end - it "uses the Windows listener on Windows" do + it 'uses the Windows listener on Windows' do RbConfig::CONFIG['target_os'] = 'mingw' Guard::Windows.stub(:usable?).and_return(true) Guard::Windows.should_receive(:new) described_class.select_and_init end - it "uses the Linux listener on Linux" do + it 'uses the Linux listener on Linux' do RbConfig::CONFIG['target_os'] = 'linux' Guard::Linux.stub(:usable?).and_return(true) Guard::Linux.should_receive(:new) described_class.select_and_init end - it "forwards its arguments to the constructor" do + it 'forwards its arguments to the constructor' do described_class.stub!(:mac?).and_return(true) Guard::Darwin.stub!(:usable?).and_return(true) @@ -37,246 +37,269 @@ describe Guard::Listener do end end - describe "#all_files" do + describe '#all_files' do subject { described_class.new(@fixture_path) } - it "should return all files" do - subject.all_files.should =~ Dir.glob("#{@fixture_path}/**/*", File::FNM_DOTMATCH).select { |file| File.file?(file) } + it 'should return all files' do + subject.all_files.should =~ Dir.glob("#{ @fixture_path }/**/*", File::FNM_DOTMATCH).select { |file| File.file?(file) } end end - describe "#relativize_paths" do + describe '#relativize_paths' do subject { described_class.new('/tmp') } - before :each do - @paths = %w( /tmp/a /tmp/a/b /tmp/a.b/c.d ) + let(:paths) { %w( /tmp/a /tmp/a/b /tmp/a.b/c.d ) } + + it 'should relativize paths to the configured directory' do + subject.relativize_paths(paths).should =~ %w( a a/b a.b/c.d ) end - it "should relativize paths to the configured directory" do - subject.relativize_paths(@paths).should =~ %w( a a/b a.b/c.d ) - end - - context "when set to false" do + context 'when set to false' do subject { described_class.new('/tmp', :relativize_paths => false) } - it "can be disabled" do - subject.relativize_paths(@paths).should eql @paths + it 'can be disabled' do + subject.relativize_paths(paths).should eql paths end end end - describe "#update_last_event" do + describe '#update_last_event' do subject { described_class.new } - it "updates the last event to the current time" do + it 'updates the last event to the current time' do time = Time.now subject.update_last_event subject.instance_variable_get(:@last_event).to_i.should >= time.to_i end end - describe "#modified_files" do + describe '#modified_files' do subject { described_class.new } - let(:file1) { @fixture_path.join("folder1", "file1.txt") } - let(:file2) { @fixture_path.join("folder1", "folder2", "file2.txt") } - let(:file3) { @fixture_path.join("folder1", "deletedfile1.txt") } - let(:file4) { @fixture_path.join("folder1", "movedfile1.txt") } - let(:file5) { @fixture_path.join("folder1", "folder2", "movedfile1.txt") } + let(:file1) { fixture('folder1', 'file1.txt') } + let(:file2) { fixture('folder1', 'folder2', 'file2.txt') } + let(:file3) { fixture('folder1', 'deletedfile1.txt') } + let(:file4) { fixture('folder1', 'movedfile1.txt') } + let(:file5) { fixture('folder1', 'folder2', 'movedfile1.txt') } - before do - @listener = subject - end + before { listen_to subject } - context "without the :all option" do - it "finds modified files only in the directory supplied" do - start - FileUtils.touch([file1, file2, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"] - stop + context 'without the :all option' do + it 'finds modified files only in the directory supplied' do + watch do + FileUtils.touch([file1, file2, file3]) + subject.modified_files([fixture('folder1')], {}).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt'] + end end end - context "with the :all options" do - it "finds modified files within subdirectories" do - start - FileUtils.touch([file1, file2, file3]) - subject.modified_files([@fixture_path.join("folder1")], { :all => true }).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt", "spec/fixtures/folder1/folder2/file2.txt"] - stop + context 'with the :all options' do + it 'finds modified files within subdirectories' do + watch do + FileUtils.touch([file1, file2, file3]) + subject.modified_files([fixture('folder1')], { :all => true }).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', + 'spec/fixtures/folder1/file1.txt', + 'spec/fixtures/folder1/folder2/file2.txt'] + end end end - context "without updating the content" do - it "ignores the files for the second time" do - start - FileUtils.touch([file1, file2, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"] + context 'without updating the content' do + it 'ignores the files for the second time' do + watch do + FileUtils.touch([file1, file2, file3]) + subject.modified_files([fixture('folder1')], {}).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt'] + + subject.update_last_event + + FileUtils.touch([file1, file2, file3]) + subject.modified_files([fixture('folder1')], {}).should be_empty + end + end + end + + context 'with content that has changed' do + after { File.open(file1, 'w') { |f| f.write('') } } + + it 'identifies the files for the second time' do + watch do + FileUtils.touch([file1, file2, file3]) + subject.modified_files([fixture('folder1')], {}).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt'] + + subject.update_last_event + + FileUtils.touch([file2, file3]) + File.open(file1, 'w') { |f| f.write('changed content') } + subject.modified_files([fixture('folder1')], {}).should =~ + ['spec/fixtures/folder1/file1.txt'] + end + end + end + + context 'without the :watch_all_modifications option' do + after { FileUtils.touch(file3) } + + it 'defaults to false' do + subject.instance_variable_get(:@watch_all_modifications).should eql false + end + + it 'it should not track deleted files' do + watch do + FileUtils.touch([file1, file2, file3]) + subject.modified_files([fixture('folder1')], {}).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt'] + + subject.update_last_event + + FileUtils.rm(file3) + subject.modified_files([fixture('folder1')], {}).should =~ [] + end + end + end + + context 'with the :watch_all_modifications option' do + subject { described_class.new(Dir.pwd, :watch_all_modifications => true) } + + before do + subject.timestamp_files subject.update_last_event - FileUtils.touch([file1, file2, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should be_empty - stop end - end - context "with content that has changed" do - after { File.open(file1, "w") { |f| f.write("") } } - - it "identifies the files for the second time" do - start + after do FileUtils.touch([file1, file2, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"] - subject.update_last_event - FileUtils.touch([file2, file3]) - File.open(file1, "w") { |f| f.write("changed content") } - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/file1.txt"] - stop end - end - context "without watch_all_modifications" do - - after { FileUtils.touch(file3) } + it 'should be true when set' do + subject.instance_variable_get(:@watch_all_modifications).should eql true + end - it "defaults to false" do - subject.instance_variable_get(:@watch_all_modifications).should eql false + it 'should track deleted files' do + watch do + FileUtils.touch([file1, file3]) + subject.modified_files([fixture('folder1')], { }).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt'] + + subject.update_last_event + + FileUtils.remove_file(file3) + subject.modified_files([fixture('folder1')], { }).should =~ + ['!spec/fixtures/folder1/deletedfile1.txt'] end + end - it "it should not track deleted files" do - FileUtils.touch([file1, file2, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"] - subject.update_last_event - FileUtils.rm(file3) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ [] - sleep 1 + it 'should track moved files' do + watch do + FileUtils.touch([file1, file3]) + subject.modified_files([@fixture_path.join('folder1')], { }).should =~ + ['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt'] + + subject.update_last_event + + FileUtils.move(file1, file4) + subject.modified_files([@fixture_path.join('folder1')], { }).should =~ + ['!spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt'] + + FileUtils.move(file4, file1) end - end + end - context "with watch_all_modifications" do - subject { described_class.new(Dir.pwd, :watch_all_modifications=>true) } - - before :each do - subject.timestamp_files - sleep 1 - subject.update_last_event + it 'should track deleted files with all option' do + watch do + FileUtils.touch([file1, file2]) + subject.modified_files([@fixture_path.join('folder1')], { :all => true }).should =~ + ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] + + subject.update_last_event + + FileUtils.remove_file(file2) + subject.modified_files([@fixture_path.join('folder1')], { :all => true }).should =~ + ['!spec/fixtures/folder1/folder2/file2.txt'] end + end - after :each do - FileUtils.touch([file1, file2, file3]) - end - - it "should be true when set" do - subject.instance_variable_get(:@watch_all_modifications).should eql true - end - - it "should track deleted files" do - FileUtils.touch([file1, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ - ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"] - - subject.update_last_event - FileUtils.remove_file(file3) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ - ["!spec/fixtures/folder1/deletedfile1.txt"] - end - - it "should track moved files" do - FileUtils.touch([file1, file3]) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ - ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"] - - subject.update_last_event - FileUtils.move(file1, file4) - subject.modified_files([@fixture_path.join("folder1")], {}).should =~ - ["!spec/fixtures/folder1/file1.txt", "spec/fixtures/folder1/movedfile1.txt"] - FileUtils.move(file4, file1) - end - - it "should track deleted files with all option" do - FileUtils.touch([file1, file2]) - subject.modified_files([@fixture_path.join("folder1")], {:all=>true}).should =~ - ["spec/fixtures/folder1/file1.txt", "spec/fixtures/folder1/folder2/file2.txt"] - - subject.update_last_event - FileUtils.remove_file(file2) - subject.modified_files([@fixture_path.join("folder1")], {:all=>true}).should =~ - ["!spec/fixtures/folder1/folder2/file2.txt"] - end - - it "should track moved files with all option" do - FileUtils.touch([file1, file2]) - subject.modified_files([@fixture_path.join("folder1")], {:all=>true}).should =~ - ["spec/fixtures/folder1/file1.txt", "spec/fixtures/folder1/folder2/file2.txt"] - - subject.update_last_event - FileUtils.move(file1, file5) - subject.modified_files([@fixture_path.join("folder1")], {:all=>true}).should =~ - ["!spec/fixtures/folder1/file1.txt","spec/fixtures/folder1/folder2/movedfile1.txt"] - FileUtils.move(file5, file1) + it 'should track moved files with all option' do + watch do + FileUtils.touch([file1, file2]) + subject.modified_files([@fixture_path.join('folder1')], { :all => true }).should =~ + ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] + + subject.update_last_event + + FileUtils.move(file1, file5) + subject.modified_files([@fixture_path.join('folder1')], { :all => true }).should =~ + ['!spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/movedfile1.txt'] + + FileUtils.move(file5, file1) end + end end end - describe "working directory" do - context "unspecified" do + describe 'working directory' do + context 'unspecified' do subject { described_class.new } - it "defaults to Dir.pwd" do + it 'defaults to Dir.pwd' do subject.instance_variable_get(:@directory).should eql Dir.pwd end - it "can be not changed" do + it 'can be not changed' do subject.should_not respond_to(:directory=) end end - context "specified as first argument to ::new" do - subject { described_class.new @wd } + context 'specified as first argument to ::new' do + let(:working_directory) { fixture('folder1') } - before do - @wd = @fixture_path.join("folder1") - @listener = subject + subject { described_class.new working_directory } + + before { listen_to subject } + + it 'can be inspected' do + subject.instance_variable_get(:@directory).should eql working_directory.to_s end - it "can be inspected" do - subject.instance_variable_get(:@directory).should eql @wd.to_s - end - - it "can be not changed" do + it 'can be not changed' do subject.should_not respond_to(:directory=) end - it "will be used to watch" do - subject.should_receive(:watch).with(@wd.to_s) + it 'will be used to watch' do + subject.should_receive(:watch).with(working_directory.to_s) start stop end end end - describe "#ignore_paths" do - it "defaults to the default ignore paths" do + describe '#ignore_paths' do + it 'defaults to the default ignore paths' do described_class.new.ignore_paths.should == Guard::Listener::DEFAULT_IGNORE_PATHS end - it "can be added to via :ignore_paths option" do + it 'can be added to via :ignore_paths option' do listener = described_class.new 'path', :ignore_paths => ['foo', 'bar'] listener.ignore_paths.should include('foo', 'bar') end end - describe "#exclude_ignored_paths []" do + describe '#exclude_ignored_paths []' do let(:ignore_paths) { nil } - subject { described_class.new(@fixture_path, {:ignore_paths => ignore_paths}) } + subject { described_class.new(@fixture_path, { :ignore_paths => ignore_paths }) } - it "returns children of " do - subject.exclude_ignored_paths(["spec/fixtures"]).should =~ ["spec/fixtures/.dotfile", "spec/fixtures/folder1", "spec/fixtures/Guardfile"] + it 'returns children of ' do + subject.exclude_ignored_paths(['spec/fixtures']).should =~ + ['spec/fixtures/.dotfile', 'spec/fixtures/folder1', 'spec/fixtures/Guardfile'] end - describe "when ignore_paths set to some of children" do + describe 'when ignore_paths set to some of children' do let(:ignore_paths) { ['Guardfile', '.dotfile'] } - it "excludes the ignored paths" do - subject.exclude_ignored_paths(["spec/fixtures"]).should =~ ["spec/fixtures/folder1"] + it 'excludes the ignored paths' do + subject.exclude_ignored_paths(['spec/fixtures']).should =~ ['spec/fixtures/folder1'] end end end diff --git a/spec/support/listener_helper.rb b/spec/support/listener_helper.rb index 3d6bcd3..378bdb2 100644 --- a/spec/support/listener_helper.rb +++ b/spec/support/listener_helper.rb @@ -1,9 +1,27 @@ private + # Set the sleep time around start/stop the listener. This defaults + # to one second but can be overridden by setting the environment + # variable `GUARD_SLEEP`. + # def sleep_time @sleep_time ||= ENV['GUARD_SLEEP'] ? ENV['GUARD_SLEEP'].to_f : 1 end + # Make the spec listen to a specific listener. + # This automatically starts to record results for the supplied listener. + # + # @param [Guard::Listener] listener the Guard listener + # + def listen_to(listener) + @listener = listener + record_results + end + + # Start the listener. Normally you use {#watch} to wrap + # the code block that should be listen to instead of starting + # it manually. + # def start sleep(sleep_time) @listener.update_last_event @@ -11,131 +29,197 @@ private sleep(sleep_time) end - def record_results - noise = %r|\.sw.$| # don't fail specs due to editor swap files, etc. - - @results = [] - @listener.on_change do |files| - @results += files.reject { |f| f =~ noise } - end - end - + # Stop the listener. Normally you use {#watch} to wrap + # the code block that should be listen to instead of stopping + # it manually. + # def stop sleep(sleep_time) @listener.stop sleep(sleep_time) end + # Watch file changes in a code block. + # + # @example Watch file changes + # watch do + # File.mv file1, file2 + # end + # + # @yield The block to listen for file changes + # + def watch + start + yield if block_given? + stop + end + + # Start recording results from the current listener. + # You may want to use {#listen_to} to set a listener + # instead of set it up manually. + # + def record_results + # Don't fail specs due to editor swap files, etc. + noise = %r|\.sw.$| + @results = [] + + @listener.on_change do |files| + @results += files.reject { |f| f =~ noise } + end + end + + # Get the recorded result from the listener. + # + # @return [Array] the result files + # def results @results.flatten end + # Define a file absolute to the fixture path. + # + # @param [String, Array] file the relative file name, separated by segment + # @return [String] the absolute file + # + def fixture(*file) + @fixture_path.join(*file) + end + shared_examples_for 'a listener that reacts to #on_change' do - before(:each) do - @listener = described_class.new - record_results + before do + listen_to described_class.new end - it "catches a new file" do - file = @fixture_path.join("newfile.rb") - if File.exists?(file) - begin - File.delete file - rescue + context 'for a new file' do + let(:file) { fixture('newfile.rb') } + + before { File.delete(file) if File.exists?(file) } + after { File.delete file } + + it 'catches the new file' do + File.exists?(file).should be_false + + watch do + FileUtils.touch file end + + results.should =~ ['spec/fixtures/newfile.rb'] end - File.exists?(file).should be_false - start - FileUtils.touch file - stop - begin - File.delete file - rescue + end + + context 'for a single file update' do + let(:file) { fixture('folder1', 'file1.txt') } + + it 'catches the update' do + File.exists?(file).should be_true + + watch do + File.open(file, 'w') { |f| f.write('') } + end + + results.should =~ ['spec/fixtures/folder1/file1.txt'] end - results.should =~ ['spec/fixtures/newfile.rb'] end - it "catches a single file update" do - file = @fixture_path.join("folder1/file1.txt") - File.exists?(file).should be_true - start - File.open(file, 'w') { |f| f.write('') } - stop - results.should =~ ['spec/fixtures/folder1/file1.txt'] + context 'for a single file chmod update' do + let(:file) { fixture('folder1/file1.txt') } + + it 'does not catch the update' do + File.exists?(file).should be_true + + watch do + File.chmod(0777, file) + end + + results.should =~ [] + end end - it "not catches a single file chmod update" do - file = @fixture_path.join("folder1/file1.txt") - File.exists?(file).should be_true - start - File.chmod(0777, file) - stop - results.should =~ [] + context 'for a dotfile update' do + let(:file) { fixture('.dotfile') } + + it "catches the update" do + File.exists?(file).should be_true + + watch do + File.open(file, 'w') { |f| f.write('') } + end + + results.should =~ ['spec/fixtures/.dotfile'] + end end - it "catches a dotfile update" do - file = @fixture_path.join(".dotfile") - File.exists?(file).should be_true - start - File.open(file, 'w') { |f| f.write('') } - stop - results.should =~ ['spec/fixtures/.dotfile'] + context 'for multiple file updates' do + let(:file1) { fixture('folder1', 'file1.txt') } + let(:file2) { fixture('folder1', 'folder2', 'file2.txt') } + + it 'catches the updates' do + File.exists?(file1).should be_true + File.exists?(file2).should be_true + + watch do + File.open(file1, 'w') { |f| f.write('') } + File.open(file2, 'w') { |f| f.write('') } + end + + results.should =~ ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt'] + end end - it "catches multiple file updates" do - file1 = @fixture_path.join("folder1/file1.txt") - file2 = @fixture_path.join("folder1/folder2/file2.txt") - File.exists?(file1).should be_true - File.exists?(file2).should be_true - start - 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'] + context 'for a deleted file' do + let(:file) { fixture('folder1', 'file1.txt') } + + after { FileUtils.touch file } + + it 'does not catch the deletion' do + File.exists?(file).should be_true + + watch do + File.delete file + end + + results.should =~ [] + end end - it "not catches a deleted file" do - file = @fixture_path.join("folder1/file1.txt") - File.exists?(file).should be_true - start - File.delete file - stop - FileUtils.touch file - results.should =~ [] - end + context 'for a moved file' do + let(:file1) { fixture('folder1', 'file1.txt') } + let(:file2) { fixture('folder1', 'movedfile1.txt') } - it "not catches a moved file" do - file1 = @fixture_path.join("folder1/file1.txt") - file2 = @fixture_path.join("folder1/movedfile1.txt") - File.exists?(file1).should be_true - File.exists?(file2).should be_false - start - FileUtils.mv file1, file2 - stop - FileUtils.mv file2, file1 - results.should =~ [] - end + after { FileUtils.mv file2, file1 } + it 'does not catches the move' do + File.exists?(file1).should be_true + File.exists?(file2).should be_false + + watch do + FileUtils.mv file1, file2 + end + + results.should =~ [] + end + end end shared_examples_for "a listener scoped to a specific directory" do - before :each do - @wd = @fixture_path.join("folder1") - @listener = described_class.new @wd - end - it "should base paths within this directory" do - record_results - new_file = @wd.join("folder2/newfile.rb") - modified = @wd.join("file1.txt") + let(:work_directory) { fixture('folder1') } + + let(:new_file) { work_directory.join('folder2', 'newfile.rb') } + let(:modified) { work_directory.join('file1.txt') } + + before { listen_to described_class.new(work_directory) } + after { File.delete new_file } + + it 'should base paths within this directory' do File.exists?(modified).should be_true File.exists?(new_file).should be_false - start - FileUtils.touch new_file - File.open(modified, 'w') { |f| f.write('') } - stop - File.delete new_file - results.should =~ ["folder2/newfile.rb", 'file1.txt'] + + watch do + FileUtils.touch new_file + File.open(modified, 'w') { |f| f.write('') } + end + + results.should =~ ['folder2/newfile.rb', 'file1.txt'] end end -