Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

New adapter for Windows and safer specs for Travis #56

Merged
merged 16 commits into from

7 participants

@Maher4Ever
Collaborator

Hello everyone,

For the last couple of weeks I've been working on a new gem called WDM to fix the performance issue Listen had on Windows. I've benchmarked it against FChange and the results were encouraging for me to keep developing it. WDM has some disadvantages too, so here is the quick run down:

Advantages of WDM:

  • It's 65175% faster than FChange in reporting changes.
  • It can report more changes.
  • It simplified the specs because it can detect changes recursively (changes in sub-directories).
  • It reports the path of the change and the type of the change.

Disadvantages:

  • It only works on ruby > 1.9.2.
  • It's an extension, so it needs Devkit on Windows to be installed. But then again, FChange relies on FFI and that's also an extension.
  • It's an MRI extension, so it won't run on JRuby or the other implementation for the moment.
  • It can't be included as a dependency in the gemspec of Listen as it will blow up while compiling (I still can't find a way in rubygems to specify that a gem can only be compiled on Windows).

WDM has been stable enough for the past 3 days and didn't crash once while I was using it.

As you may know, Listen has had a performance issue on Windows. Sometimes, it didn't even report changes. So I decided to implement the windows adapter in Listen with WDM and see how it works. It worked quite good and all the specs pass.

I also took the opportunity to make an end to the "russian roulette" we were playing with travis (thanks @netzpirat for this great description :) ). Adapters can now report changes on demand instead of using the latency, which allows us to wait until all tests has run and then check the results. The only disadvantage of this is that tests now require the developer to specify how many change he expects to get.

@guard/listen I'm wondering what you think about these changes. I would say WDM is in the alpha phase right now, so do you think it's a good decision to replace FChange with WDM any time soon? Also, what do you think about the fix for the travis problem?

Maher4Ever added some commits
@Maher4Ever Maher4Ever Replace FChange with WDM da06b26
@Maher4Ever Maher4Ever Stop relying on luck when testing adapters
Adapters can now be set to not report changes until explicitly commanded
to. This allows waiting for the expected amount of changes in tests.
2501fc8
@Maher4Ever Maher4Ever Add the ability to skip tests 7dc2c26
@Maher4Ever Maher4Ever Don't silent exceptions inside threads d23621d
@Maher4Ever Maher4Ever Lower tests latency
This latency has another meaning in tests: It's the interval between
checking if the expected amount of changes is reached. So lowering it
translates into faster tests!
aad355e
@Maher4Ever Maher4Ever Add manual reporting of changes to the Linux adapter b1e2607
@Maher4Ever Maher4Ever Add manual reporting of changes to the Darwin adapter 38eec03
@travisbot

This pull request fails (merged 38eec03 into e985b25).

@netzpirat
Owner

Oh wow, great work @Maher4Ever

It only works on ruby > 1.9.2.

Ruby 1.8.7 EOL is in 10 months, so it's not a big disadvantage.

It's an MRI extension, so it won't run on JRuby or the other implementation for the moment.

Oh, I think I can help with this and add Java 7 WatchService API support to listen and JRuby is a first class citizen again on all plattforms. I anyway planned to give some JRuby love to Guard, since it's a confession of failure currently.

It can't be included as a dependency in the gemspec of Listen as it will blow up while compiling (I still can't find a way in rubygems to specify that a gem can only be compiled on Windows).

I do not like these dependencies either and I'm in favor of #54

WDM has been stable enough for the past 3 days and didn't crash once while I was using it.

MERGE!

do you think it's a good decision to replace FChange with WDM any time soon?

Sounds like it is and the ultimate reason to do so is that it's maintained by you.

Also, what do you think about the fix for the travis problem?

I'd not care to specify the expected change count, since we do similar with mocks, but I prefer to have solid specs :P

Great work Maher.

@rymai
Owner

Awesome @Maher4Ever!!! Thanks for taking care of Windows users! :)

@thibaudgg
Owner

Nice work, all green for me :)

@Maher4Ever
Collaborator

Glad you all liked it :). I'll merge this after fixing #54

@travisbot

This pull request fails (merged 2b7e352 into e985b25).

@travisbot

This pull request fails (merged cbb2d50 into e985b25).

@travisbot

This pull request fails (merged d7ca568 into e985b25).

@Maher4Ever Maher4Ever Fix specs on rubinius
Use a patched version of rb-inotify until a new version is released.
ee7167d
@travisbot

This pull request fails (merged ee7167d into e985b25).

@travisbot

This pull request fails (merged 4955b8c into e985b25).

@travisbot

This pull request fails (merged 91e7eb1 into e985b25).

@travisbot

This pull request passes (merged a62e0b1 into e985b25).

@travisbot

This pull request passes (merged ed6a39a into e985b25).

@thibaudgg
Owner

Ready to be merged? :)

@Maher4Ever
Collaborator

Yes it is :). Could you just confirm it works one more time (because of the new changes to WDM) so we don't end up segfaulting on user's machines?

@thibaudgg
Owner

I'll try to give it a try this evening, thanks!

@Maher4Ever Maher4Ever was assigned
@thibaudgg
Owner

@Maher4Ever all specs are green on OS X! Congrats!

@Maher4Ever
Collaborator

@thibaudgg Awesome! Thanks for testing :)

@Maher4Ever Maher4Ever merged commit c37d9aa into master
@prusswan

Hi, how is the new dependency manager supposed to work with guard? I just realized that on Ubuntu, the rb-inotify, rb-fchange, rb-fsevent gems are no longer included, and a warning will be shown:

> [Listen warning]:
  Missing dependency 'rb-inotify' (version '~> 0.8.8')!
  Please add the following to your Gemfile to satisfy the dependency:
    gem 'rb-inotify', '~> 0.8.8'

  For a better performance, it's recommended that you satisfy the missing dependency.
  Listen will be polling changes. Learn more at https://github.com/guard/listen#polling-fallback.

Note that this is not immediately noticeable for those who updated from older versions of guard, since those gems may still be present in Gemfile.lock

Just add

gem 'rb-inotify', '~> 0.8.8'

before gem 'guard' and you'll be good to go.

@coveralls

Coverage Status

Changes Unknown when pulling ed6a39a on wdm into ** on master**.

@coveralls

Coverage Status

Changes Unknown when pulling ed6a39a on wdm into ** on master**.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 22, 2012
  1. @Maher4Ever

    Replace FChange with WDM

    Maher4Ever authored
  2. @Maher4Ever

    Stop relying on luck when testing adapters

    Maher4Ever authored
    Adapters can now be set to not report changes until explicitly commanded
    to. This allows waiting for the expected amount of changes in tests.
  3. @Maher4Ever
  4. @Maher4Ever
  5. @Maher4Ever

    Lower tests latency

    Maher4Ever authored
    This latency has another meaning in tests: It's the interval between
    checking if the expected amount of changes is reached. So lowering it
    translates into faster tests!
Commits on Jul 24, 2012
  1. @Maher4Ever
  2. @Maher4Ever
Commits on Jul 31, 2012
  1. @Maher4Ever
  2. @Maher4Ever
  3. @Maher4Ever

    Fix the specs on jruby

    Maher4Ever authored
Commits on Aug 4, 2012
  1. @Maher4Ever

    Fix specs on rubinius

    Maher4Ever authored
    Use a patched version of rb-inotify until a new version is released.
  2. @Maher4Ever
  3. @Maher4Ever
Commits on Aug 15, 2012
  1. @Maher4Ever
  2. @Maher4Ever
  3. @Maher4Ever

    Update Gemfile

    Maher4Ever authored
This page is out of date. Refresh to see the latest.
View
5 .travis.yml
@@ -11,7 +11,10 @@ rvm:
- rbx-18mode
- rbx-19mode
bundler_args: --without=development
-env: TEST_LATENCY=1.0
+matrix:
+ allow_failures:
+ - rvm: ruby-head
+ - rvm: jruby-head
notifications:
recipients:
- thibaud@thibaud.me
View
8 Gemfile
@@ -1,9 +1,15 @@
+require 'rbconfig'
+
source :rubygems
gemspec
gem 'rake'
+gem 'rb-fsevent', '~> 0.9.1' if RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
+gem 'rb-inotify', '~> 0.8.8', :github => 'mbj/rb-inotify' if RbConfig::CONFIG['target_os'] =~ /linux/i
+gem 'wdm', '~> 0.0.3' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
+
group :development do
platform :ruby do
gem 'coolline'
@@ -20,4 +26,4 @@ end
group :test do
gem 'rspec'
-end
+end
View
1  README.md
@@ -251,7 +251,6 @@ want to force the use of the polling adapter, either use the `:force_polling` op
while initializing the listener or call the `force_polling` method on your listener
before starting it.
-<a name="fallback"/>
## Polling fallback
When a OS-specific adapter doesn't work the Listen gem automatically falls back to the polling adapter.
View
11 lib/listen.rb
@@ -1,10 +1,11 @@
module Listen
- autoload :Turnstile, 'listen/turnstile'
- autoload :Listener, 'listen/listener'
- autoload :MultiListener, 'listen/multi_listener'
- autoload :DirectoryRecord, 'listen/directory_record'
- autoload :Adapter, 'listen/adapter'
+ autoload :Turnstile, 'listen/turnstile'
+ autoload :Listener, 'listen/listener'
+ autoload :MultiListener, 'listen/multi_listener'
+ autoload :DirectoryRecord, 'listen/directory_record'
+ autoload :DependencyManager, 'listen/dependency_manager'
+ autoload :Adapter, 'listen/adapter'
module Adapters
autoload :Darwin, 'listen/adapters/darwin'
View
99 lib/listen/adapter.rb
@@ -10,8 +10,15 @@ class Adapter
# The default delay between checking for changes.
DEFAULT_LATENCY = 0.25
+ # The default warning message when there is a missing dependency.
+ MISSING_DEPENDENCY_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ For a better performance, it's recommended that you satisfy the missing dependency.
+ EOS
+
# The default warning message when falling back to polling adapter.
- POLLING_FALLBACK_MESSAGE = "WARNING: Listen has fallen back to polling, learn more at https://github.com/guard/listen#fallback."
+ POLLING_FALLBACK_MESSAGE = <<-EOS.gsub(/^\s*/, '')
+ Listen will be polling changes. Learn more at https://github.com/guard/listen#polling-fallback.
+ EOS
# Selects the appropriate adapter implementation for the
# current OS and initializes it.
@@ -31,18 +38,26 @@ class Adapter
def self.select_and_initialize(directories, options = {}, &callback)
return Adapters::Polling.new(directories, options, &callback) if options.delete(:force_polling)
- if Adapters::Darwin.usable_and_works?(directories, options)
- Adapters::Darwin.new(directories, options, &callback)
- elsif Adapters::Linux.usable_and_works?(directories, options)
- Adapters::Linux.new(directories, options, &callback)
- elsif Adapters::Windows.usable_and_works?(directories, options)
- Adapters::Windows.new(directories, options, &callback)
- else
- unless options[:polling_fallback_message] == false
- Kernel.warn(options[:polling_fallback_message] || POLLING_FALLBACK_MESSAGE)
+ warning = ''
+
+ begin
+ if Adapters::Darwin.usable_and_works?(directories, options)
+ return Adapters::Darwin.new(directories, options, &callback)
+ elsif Adapters::Linux.usable_and_works?(directories, options)
+ return Adapters::Linux.new(directories, options, &callback)
+ elsif Adapters::Windows.usable_and_works?(directories, options)
+ return Adapters::Windows.new(directories, options, &callback)
end
- Adapters::Polling.new(directories, options, &callback)
+ rescue DependencyManager::Error => e
+ warning += e.message + "\n" + MISSING_DEPENDENCY_MESSAGE
+ end
+
+ unless options[:polling_fallback_message] == false
+ warning += options[:polling_fallback_message] || POLLING_FALLBACK_MESSAGE
+ Kernel.warn "[Listen warning]:\n" + warning.gsub(/^(.*)/, ' \1')
end
+
+ Adapters::Polling.new(directories, options, &callback)
end
# Initializes the adapter.
@@ -50,6 +65,7 @@ def self.select_and_initialize(directories, options = {}, &callback)
# @param [String, Array<String>] directories the directories to watch
# @param [Hash] options the adapter options
# @option options [Float] latency the delay between checking for changes in seconds
+ # @option options [Boolean] report_changes whether or not to automatically report changes (run the callback)
#
# @yield [changed_dirs, options] callback Callback called when a change happens
# @yieldparam [Array<String>] changed_dirs the changed directories
@@ -60,12 +76,13 @@ def self.select_and_initialize(directories, options = {}, &callback)
def initialize(directories, options = {}, &callback)
@directories = Array(directories)
@callback = callback
- @latency ||= DEFAULT_LATENCY
- @latency = options[:latency] if options[:latency]
@paused = false
@mutex = Mutex.new
@changed_dirs = Set.new
@turnstile = Turnstile.new
+ @latency ||= DEFAULT_LATENCY
+ @latency = options[:latency] if options[:latency]
+ @report_changes = options[:report_changes].nil? ? true : options[:report_changes]
end
# Starts the adapter.
@@ -92,12 +109,37 @@ def started?
end
# Blocks the main thread until the poll thread
- # calls the callback.
+ # runs the callback.
#
def wait_for_callback
@turnstile.wait unless @paused
end
+ # Blocks the main thread until N changes are
+ # detected.
+ #
+ def wait_for_changes(goal = 0)
+ changes = 0
+
+ loop do
+ @mutex.synchronize { changes = @changed_dirs.size }
+
+ return if @paused || @stop
+ return if changes >= goal
+
+ sleep(@latency)
+ end
+ end
+
+ # Checks if the adapter is usable on the current OS.
+ #
+ # @return [Boolean] whether usable or not
+ #
+ def self.usable?
+ load_depenencies
+ dependencies_loaded?
+ end
+
# Checks if the adapter is usable and works on the current OS.
#
# @param [String, Array<String>] directories the directories to watch
@@ -140,26 +182,29 @@ def self.works?(directory, options = {})
adapter.stop if adapter && adapter.started?
end
+ # Runs the callback and passes it the changes if there are any.
+ #
+ def report_changes
+ changed_dirs = nil
+
+ @mutex.synchronize do
+ return if @changed_dirs.empty?
+ changed_dirs = @changed_dirs.to_a
+ @changed_dirs.clear
+ end
+
+ @callback.call(changed_dirs, {})
+ end
+
private
# Polls changed directories and reports them back
# when there are changes.
#
- # @option [Boolean] recursive whether or not to pass the recursive option to the callback
- #
- def poll_changed_dirs(recursive = false)
+ def poll_changed_dirs
until @stop
sleep(@latency)
- next if @changed_dirs.empty?
-
- changed_dirs = []
-
- @mutex.synchronize do
- changed_dirs = @changed_dirs.to_a
- @changed_dirs.clear
- end
-
- @callback.call(changed_dirs, recursive ? {:recursive => recursive} : {})
+ report_changes
@turnstile.signal
end
end
View
21 lib/listen/adapters/darwin.rb
@@ -4,6 +4,10 @@ module Adapters
# Adapter implementation for Mac OS X `FSEvents`.
#
class Darwin < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-fsevent', '~> 0.9.1'
LAST_SEPARATOR_REGEX = /\/$/
@@ -25,13 +29,14 @@ def start(blocking = true)
end
@worker_thread = Thread.new { @worker.run }
- @poll_thread = Thread.new { poll_changed_dirs }
# The FSEvent worker needs sometime to startup. Turnstiles can't
# be used to wait for it as it runs in a loop.
# TODO: Find a better way to block until the worker starts.
- sleep @latency
- @poll_thread.join if blocking
+ sleep 0.1
+
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+ @worker_thread.join if blocking
end
# Stops the adapter.
@@ -43,8 +48,8 @@ def stop
end
@worker.stop
- Thread.kill(@worker_thread) if @worker_thread
- @poll_thread.join
+ @worker_thread.join if @worker_thread
+ @poll_thread.join if @poll_thread
end
# Checks if the adapter is usable on the current OS.
@@ -53,11 +58,7 @@ def stop
#
def self.usable?
return false unless RbConfig::CONFIG['target_os'] =~ /darwin(1.+)?$/i
-
- require 'rb-fsevent'
- true
- rescue LoadError
- false
+ super
end
private
View
63 lib/listen/adapters/linux.rb
@@ -4,6 +4,10 @@ module Adapters
# Listener implementation for Linux `inotify`.
#
class Linux < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'rb-inotify', '~> 0.8.8'
# Watched inotify events
#
@@ -41,8 +45,9 @@ def start(blocking = true)
end
@worker_thread = Thread.new { @worker.run }
- @poll_thread = Thread.new { poll_changed_dirs }
- @poll_thread.join if blocking
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+
+ @worker_thread.join if blocking
end
# Stops the adapter.
@@ -55,20 +60,16 @@ def stop
@worker.stop
Thread.kill(@worker_thread) if @worker_thread
- @poll_thread.join
+ @poll_thread.join if @poll_thread
end
- # Check if the adapter is usable on the current OS.
+ # Checks if the adapter is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable?
return false unless RbConfig::CONFIG['target_os'] =~ /linux/i
-
- require 'rb-inotify'
- true
- rescue LoadError
- false
+ super
end
private
@@ -79,29 +80,31 @@ def self.usable?
# @return [INotify::Notifier] initialized worker
#
def init_worker
- worker = INotify::Notifier.new
- @directories.each do |directory|
- worker.watch(directory, *EVENTS.map(&:to_sym)) do |event|
- if @paused || (
- # Event on root directory
- event.name == ""
- ) || (
- # INotify reports changes to files inside directories as events
- # on the directories themselves too.
- #
- # @see http://linux.die.net/man/7/inotify
- event.flags.include?(:isdir) and event.flags & [:close, :modify] != []
- )
- # Skip all of these!
- next
- end
-
- @mutex.synchronize do
- @changed_dirs << File.dirname(event.absolute_name)
- end
+ callback = lambda do |event|
+ if @paused || (
+ # Event on root directory
+ event.name == ""
+ ) || (
+ # INotify reports changes to files inside directories as events
+ # on the directories themselves too.
+ #
+ # @see http://linux.die.net/man/7/inotify
+ event.flags.include?(:isdir) and event.flags & [:close, :modify] != []
+ )
+ # Skip all of these!
+ next
+ end
+
+ @mutex.synchronize do
+ @changed_dirs << File.dirname(event.absolute_name)
+ end
+ end
+
+ INotify::Notifier.new.tap do |worker|
+ @directories.each do |directory|
+ worker.watch(directory, *EVENTS.map(&:to_sym), &callback)
end
end
- worker
end
end
View
1  lib/listen/adapters/polling.rb
@@ -10,6 +10,7 @@ module Adapters
# file IO that the other implementations.
#
class Polling < Adapter
+ extend DependencyManager
# Initialize the Adapter. See {Listen::Adapter#initialize} for more info.
#
View
48 lib/listen/adapters/windows.rb
@@ -3,9 +3,13 @@
module Listen
module Adapters
- # Adapter implementation for Windows `fchange`.
+ # Adapter implementation for Windows `wdm`.
#
class Windows < Adapter
+ extend DependencyManager
+
+ # Declare the adapter's dependencies
+ dependency 'wdm', '~> 0.0.3'
# Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
#
@@ -24,9 +28,15 @@ def start(blocking = true)
super
end
- @worker_thread = Thread.new { @worker.run }
- @poll_thread = Thread.new { poll_changed_dirs(true) }
- @poll_thread.join if blocking
+ @worker_thread = Thread.new { @worker.run! }
+
+ # Wait for the worker to start. This is needed to avoid a deadlock
+ # when stopping immediately after starting.
+ sleep 0.1
+
+ @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
+
+ @worker_thread.join if blocking
end
# Stops the adapter.
@@ -38,8 +48,8 @@ def stop
end
@worker.stop
- Thread.kill(@worker_thread) if @worker_thread
- @poll_thread.join
+ @worker_thread.join if @worker_thread
+ @poll_thread.join if @poll_thread
end
# Checks if the adapter is usable on the current OS.
@@ -48,31 +58,27 @@ def stop
#
def self.usable?
return false unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
-
- require 'rb-fchange'
- true
- rescue LoadError
- false
+ super
end
private
- # Initializes a FChange worker and adds a watcher for
+ # Initializes a WDM monitor and adds a watcher for
# each directory passed to the adapter.
#
- # @return [FChange::Notifier] initialized worker
+ # @return [WDM::Monitor] initialized worker
#
def init_worker
- FChange::Notifier.new.tap do |worker|
- @directories.each do |directory|
- worker.watch(directory, :all_events, :recursive) do |event|
- next if @paused
- @mutex.synchronize do
- @changed_dirs << File.expand_path(event.watcher.path)
- end
- end
+ callback = Proc.new do |change|
+ next if @paused
+ @mutex.synchronize do
+ @changed_dirs << File.dirname(change.path)
end
end
+
+ WDM::Monitor.new.tap do |worker|
+ @directories.each { |d| worker.watch_recursively(d, &callback) }
+ end
end
end
View
126 lib/listen/dependency_manager.rb
@@ -0,0 +1,126 @@
+require 'set'
+
+module Listen
+
+ # The dependency-manager offers a simple DSL which allows
+ # classes to declare their gem dependencies and load them when
+ # needed.
+ # It raises a user-friendly exception when the dependencies
+ # can't be loaded which has the install command in the message.
+ #
+ module DependencyManager
+
+ GEM_LOAD_MESSAGE = <<-EOS.gsub(/^ {6}/, '')
+ Missing dependency '%s' (version '%s')!
+ EOS
+
+ GEM_INSTALL_COMMAND = <<-EOS.gsub(/^ {6}/, '')
+ Please run the following to satisfy the dependency:
+ gem install --version '%s' %s
+ EOS
+
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
+ Please add the following to your Gemfile to satisfy the dependency:
+ gem '%s', '%s'
+ EOS
+
+ Dependency = Struct.new(:name, :version)
+
+ # The error raised when a dependency can't be loaded.
+ class Error < StandardError; end
+
+ # A list of all loaded dependencies in the dependency manager.
+ @_loaded_dependencies = Set.new
+
+ # class methods
+ class << self
+
+ # Initializes the extended class.
+ #
+ # @param [Class] the class for which some dependencies must be managed
+ #
+ def extended(base)
+ base.class_eval do
+ @_dependencies = Set.new
+ end
+ end
+
+ # Adds a loaded dependency to a list so that it doesn't have
+ # to be loaded again by another classes.
+ #
+ # @param [Dependency] dependency
+ #
+ def add_loaded(dependency)
+ @_loaded_dependencies << dependency
+ end
+
+ # Returns whether the dependency is alread loaded or not.
+ #
+ # @param [Dependency] dependency
+ # @return [Boolean]
+ #
+ def already_loaded?(dependency)
+ @_loaded_dependencies.include?(dependency)
+ end
+
+ # Clears the list of loaded dependencies.
+ #
+ def clear_loaded
+ @_loaded_dependencies.clear
+ end
+ end
+
+ # Registers a new dependency.
+ #
+ # @param [String] name the name of the gem
+ # @param [String] version the version of the gem
+ #
+ def dependency(name, version)
+ @_dependencies << Dependency.new(name, version)
+ end
+
+ # Loads the registered dependencies.
+ #
+ # @raise DependencyManager::Error if the dependency can't be loaded.
+ #
+ def load_depenencies
+ @_dependencies.each do |dependency|
+ begin
+ next if DependencyManager.already_loaded?(dependency)
+ gem(dependency.name, dependency.version)
+ require(dependency.name)
+ DependencyManager.add_loaded(dependency)
+ @_dependencies.delete(dependency)
+ rescue Gem::LoadError
+ args = [dependency.name, dependency.version]
+ command = if running_under_bundler?
+ BUNDLER_DECLARE_GEM % args
+ else
+ GEM_INSTALL_COMMAND % args.reverse
+ end
+ message = GEM_LOAD_MESSAGE % args
+
+ raise Error.new(message + command)
+ end
+ end
+ end
+
+ # Returns whether all the dependencies has been loaded or not.
+ #
+ # @return [Boolean]
+ #
+ def dependencies_loaded?
+ @_dependencies.empty?
+ end
+
+ private
+
+ # Returns whether we are running under bundler or not
+ #
+ # @return [Boolean]
+ #
+ def running_under_bundler?
+ !!(File.exists?('Gemfile') && ENV['BUNDLE_GEMFILE'])
+ end
+ end
+end
View
4 listen.gemspec
@@ -15,10 +15,6 @@ Gem::Specification.new do |s|
s.required_rubygems_version = '>= 1.3.6'
s.rubyforge_project = 'listen'
- s.add_dependency 'rb-fsevent', '~> 0.9.1'
- s.add_dependency 'rb-inotify', '~> 0.8.8'
- s.add_dependency 'rb-fchange', '~> 0.0.5'
-
s.add_development_dependency 'bundler'
s.files = Dir.glob('{lib}/**/*') + %w[CHANGELOG.md LICENSE README.md]
View
27 spec/listen/adapter_spec.rb
@@ -31,14 +31,27 @@
described_class.select_and_initialize('dir')
end
- it "warns with the default polling fallback message" do
- Kernel.should_receive(:warn).with(Listen::Adapter::POLLING_FALLBACK_MESSAGE)
+ it 'warns with the default polling fallback message' do
+ Kernel.should_receive(:warn).with(/#{Listen::Adapter::POLLING_FALLBACK_MESSAGE}/)
described_class.select_and_initialize('dir')
end
+ context 'when the dependencies of an adapter are not satisfied' do
+ before do
+ Listen::Adapters::Darwin.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ Listen::Adapters::Linux.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ Listen::Adapters::Windows.stub(:usable_and_works?).and_raise(Listen::DependencyManager::Error)
+ end
+
+ it 'invites the user to satisfy the dependencies of the adapter in the warning' do
+ Kernel.should_receive(:warn).with(/#{Listen::Adapter::MISSING_DEPENDENCY_MESSAGE}/)
+ described_class.select_and_initialize('dir')
+ end
+ end
+
context "with custom polling_fallback_message option" do
it "warns with the custom polling fallback message" do
- Kernel.should_receive(:warn).with('custom')
+ Kernel.should_receive(:warn).with(/custom/)
described_class.select_and_initialize('dir', :polling_fallback_message => 'custom')
end
end
@@ -102,6 +115,14 @@
[Listen::Adapters::Darwin, Listen::Adapters::Linux, Listen::Adapters::Windows].each do |adapter_class|
if adapter_class.usable?
+ describe '.usable?' do
+ it 'checks the dependencies' do
+ adapter_class.should_receive(:load_depenencies)
+ adapter_class.should_receive(:dependencies_loaded?)
+ adapter_class.usable?
+ end
+ end
+
describe '.usable_and_works?' do
it 'checks if the adapter is usable' do
adapter_class.stub(:works?)
View
2  spec/listen/adapters/windows_spec.rb
@@ -7,7 +7,7 @@
end
it_should_behave_like 'a filesystem adapter'
- it_should_behave_like 'an adapter that call properly listener#on_change', :recursive => true, :adapter => :windows
+ it_should_behave_like 'an adapter that call properly listener#on_change'
end
if mac?
View
107 spec/listen/dependency_manager_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+describe Listen::DependencyManager do
+ let(:dependency) { Listen::DependencyManager::Dependency.new('listen', '~> 0.0.1') }
+
+ subject { Class.new { extend Listen::DependencyManager } }
+
+ before { described_class.clear_loaded }
+
+ describe '.add_loaded' do
+ it 'adds a dependency to the list of loaded dependencies' do
+ described_class.add_loaded dependency
+ described_class.already_loaded?(dependency).should be_true
+ end
+ end
+
+ describe '.already_loaded?' do
+ it 'returns false when a dependency is not in the list of loaded dependencies' do
+ described_class.already_loaded?(dependency).should be_false
+ end
+
+ it 'returns true when a dependency is in the list of loaded dependencies' do
+ described_class.add_loaded dependency
+ described_class.already_loaded?(dependency).should be_true
+ end
+ end
+
+ describe '.clear_loaded' do
+ it 'clears the whole list of loaded dependencies' do
+ described_class.add_loaded dependency
+ described_class.already_loaded?(dependency).should be_true
+ described_class.clear_loaded
+ described_class.already_loaded?(dependency).should be_false
+ end
+ end
+
+ describe '#dependency' do
+ it 'registers a new dependency for the managed class' do
+ subject.dependency 'listen', '~> 0.0.1'
+ subject.dependencies_loaded?.should be_false
+ end
+ end
+
+ describe '#load_depenencies' do
+ before { subject.dependency 'listen', '~> 0.0.1' }
+
+ context 'when dependencies can be loaded' do
+ before { subject.stub(:gem, :require) }
+
+ it 'loads all the registerd dependencies' do
+ subject.load_depenencies
+ subject.dependencies_loaded?.should be_true
+ end
+ end
+
+ context 'when dependencies can not be loaded' do
+ it 'raises an error' do
+ expect {
+ subject.load_depenencies
+ }.to raise_error(described_class::Error)
+ end
+
+ context 'when running under bundler' do
+ before { subject.should_receive(:running_under_bundler?).and_return(true) }
+
+ it 'includes the Gemfile declaration to satisfy the dependency' do
+ begin
+ subject.load_depenencies
+ rescue described_class::Error => e
+ e.message.should include("gem 'listen', '~> 0.0.1'")
+ end
+ end
+ end
+
+ context 'when not running under bundler' do
+ before { subject.should_receive(:running_under_bundler?).and_return(false) }
+
+ it 'includes the command to install the dependency' do
+ begin
+ subject.load_depenencies
+ rescue described_class::Error => e
+ e.message.should include("gem install --version '~> 0.0.1' listen")
+ end
+ end
+ end
+ end
+ end
+
+ describe '#dependencies_loaded?' do
+ it 'return false when dependencies are not loaded' do
+ subject.dependency 'listen', '~> 0.0.1'
+ subject.dependencies_loaded?.should be_false
+ end
+
+ it 'return true when dependencies are loaded' do
+ subject.stub(:gem, :require)
+
+ subject.dependency 'listen', '~> 0.0.1'
+ subject.load_depenencies
+ subject.dependencies_loaded?.should be_true
+ end
+
+ it 'return true when there are no dependencies to load' do
+ subject.dependencies_loaded?.should be_true
+ end
+ end
+end
View
4 spec/listen/directory_record_spec.rb
@@ -193,7 +193,7 @@
it 'returns nil when the passed path is not inside the base-directory' do
subject.relative_to_base('/tmp/some_random_path').should be_nil
end
-
+
context 'when containing regexp characters in the base directory' do
before do
fixtures do |path|
@@ -1104,7 +1104,7 @@
end
end
- context 'with symlinks' do
+ context 'with symlinks', :unless => windows? do
it 'looks at symlinks not their targets' do
fixtures do |path|
touch 'target'
View
8 spec/spec_helper.rb
@@ -1,7 +1,5 @@
require 'listen'
-ENV["TEST_LATENCY"] ||= "0.25"
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
@@ -11,8 +9,12 @@
config.filter_run :focus => true
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
+ config.filter_run_excluding :broken => true
end
def test_latency
- ENV["TEST_LATENCY"].to_f
+ 0.1
end
+
+# Crash loud in tests!
+Thread.abort_on_exception = true
View
337 spec/support/adapter_helper.rb
@@ -3,18 +3,29 @@
# @param [Listen::Listener] listener the adapter listener
# @param [String] path the path to watch
#
-def watch(listener, *paths)
- callback = lambda { |changed_dirs, options| @called = true; listener.on_change(changed_dirs, options) }
- @adapter = Listen::Adapter.select_and_initialize(paths, { :latency => test_latency }, &callback)
+def watch(listener, expected_changes, *paths)
+ callback = lambda { |changed_dirs, options| @called = true; listener.on_change(changed_dirs) }
+ @adapter = Listen::Adapter.select_and_initialize(paths, { :report_changes => false, :latency => test_latency }, &callback)
+
+ forced_stop = false
+ prevent_deadlock = Proc.new { sleep(10); puts "Forcing stop"; @adapter.stop; forced_stop = true }
+
@adapter.start(false)
yield
- t = Thread.new { sleep(test_latency * 5); @adapter.stop }
- @adapter.wait_for_callback
+ t = Thread.new(&prevent_deadlock)
+ @adapter.wait_for_changes(expected_changes)
+
+ unless forced_stop
+ Thread.kill(t)
+ @adapter.report_changes
+ end
ensure
- Thread.kill(t) if t
- @adapter.stop
+ unless forced_stop
+ Thread.kill(t) if t
+ @adapter.stop
+ end
end
shared_examples_for 'a filesystem adapter' do
@@ -61,7 +72,7 @@ def watch(listener, *paths)
context 'with a started adapter' do
before { subject.start(false) }
- after { subject.start }
+ after { subject.stop }
it 'returns true' do
subject.should be_started
@@ -79,30 +90,26 @@ def watch(listener, *paths)
context 'when a file is created' do
it 'detects the added file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'new_file.rb'
end
end
end
- context 'given a symlink' do
+ context 'given a symlink', :unless => windows? do
it 'detects the added file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'new_file.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
ln_s 'new_file.rb', 'new_file_symlink.rb'
end
end
@@ -112,18 +119,14 @@ def watch(listener, *paths)
context 'given a new created directory' do
it 'detects the added file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
- watch(listener, path) do
+ watch(listener, 2, path) do
mkdir 'a_directory'
# Needed for INotify, because of :recursive rb-inotify custom flag?
- sleep 0.05 if @adapter.is_a?(Listen::Adapters::Linux)
+ sleep 0.05
touch 'a_directory/new_file.rb'
end
end
@@ -133,15 +136,13 @@ def watch(listener, *paths)
context 'given an existing directory' do
it 'detects the added file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with(["#{path}/a_directory"], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory")
end
mkdir 'a_directory'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'a_directory/new_file.rb'
end
end
@@ -151,15 +152,13 @@ def watch(listener, *paths)
context 'given a directory with subdirectories' do
it 'detects the added file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with(["#{path}/a_directory/subdirectory"], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory")
end
mkdir_p 'a_directory/subdirectory'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'a_directory/subdirectory/new_file.rb'
end
end
@@ -170,33 +169,29 @@ def watch(listener, *paths)
context 'when a file is modified' do
it 'detects the modified file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'existing_file.txt'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'existing_file.txt'
end
end
end
- context 'given a symlink' do
+ context 'given a symlink', :unless => windows? do
it 'detects the modified file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'existing_file.rb'
ln_s 'existing_file.rb', 'existing_file_symlink.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'existing_file.rb'
end
end
@@ -206,36 +201,30 @@ def watch(listener, *paths)
context 'given a hidden file' do
it 'detects the modified file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch '.hidden'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch '.hidden'
end
end
end
end
- unless options[:adapter] == :windows
- context 'given a file mode change' do
- it 'does not detect the mode change' do
- fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
- end
+ context 'given a file mode change', :unless => windows? do
+ it 'does not detect the mode change' do
+ fixtures do |path|
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
+ end
- touch 'run.rb'
+ touch 'run.rb'
- watch(listener, path) do
- chmod 0777, 'run.rb'
- end
+ watch(listener, 1, path) do
+ chmod 0777, 'run.rb'
end
end
end
@@ -244,16 +233,14 @@ def watch(listener, *paths)
context 'given an existing directory' do
it 'detects the modified file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with(["#{path}/a_directory"], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory")
end
mkdir 'a_directory'
touch 'a_directory/existing_file.txt'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'a_directory/existing_file.txt'
end
end
@@ -263,16 +250,14 @@ def watch(listener, *paths)
context 'given a directory with subdirectories' do
it 'detects the modified file' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with(["#{path}/a_directory/subdirectory"], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory")
end
mkdir_p 'a_directory/subdirectory'
touch 'a_directory/subdirectory/existing_file.txt'
- watch(listener, path) do
+ watch(listener, 1, path) do
touch 'a_directory/subdirectory/new_file.rb'
end
end
@@ -283,33 +268,29 @@ def watch(listener, *paths)
context 'when a file is moved' do
it 'detects the file move' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'move_me.txt'
- watch(listener, path) do
+ watch(listener, 1, path) do
mv 'move_me.txt', 'new_name.txt'
end
end
end
- context 'given a symlink' do
+ context 'given a symlink', :unless => windows? do
it 'detects the file move' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'move_me.rb'
ln_s 'move_me.rb', 'move_me_symlink.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
mv 'move_me_symlink.rb', 'new_symlink.rb'
end
end
@@ -319,18 +300,14 @@ def watch(listener, *paths)
context 'given an existing directory' do
it 'detects the file move into the directory' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
mkdir 'a_directory'
touch 'move_me.txt'
- watch(listener, path) do
+ watch(listener, 2, path) do
mv 'move_me.txt', 'a_directory/move_me.txt'
end
end
@@ -338,18 +315,14 @@ def watch(listener, *paths)
it 'detects a file move out of the directory' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
mkdir 'a_directory'
touch 'a_directory/move_me.txt'
- watch(listener, path) do
+ watch(listener, 2, path) do
mv 'a_directory/move_me.txt', 'i_am_here.txt'
end
end
@@ -357,19 +330,15 @@ def watch(listener, *paths)
it 'detects a file move between two directories' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ ["#{path}/from_directory", "#{path}/to_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/from_directory", "#{path}/to_directory")
end
mkdir 'from_directory'
touch 'from_directory/move_me.txt'
mkdir 'to_directory'
- watch(listener, path) do
+ watch(listener, 2, path) do
mv 'from_directory/move_me.txt', 'to_directory/move_me.txt'
end
end
@@ -379,19 +348,15 @@ def watch(listener, *paths)
context 'given a directory with subdirectories' do
it 'detects files movements between subdirectories' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ ["#{path}/a_directory/subdirectory", "#{path}/b_directory/subdirectory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory", "#{path}/b_directory/subdirectory")
end
mkdir_p 'a_directory/subdirectory'
mkdir_p 'b_directory/subdirectory'
touch 'a_directory/subdirectory/move_me.txt'
- watch(listener, path) do
+ watch(listener, 2, path) do
mv 'a_directory/subdirectory/move_me.txt', 'b_directory/subdirectory'
end
end
@@ -402,33 +367,29 @@ def watch(listener, *paths)
context 'when a file is deleted' do
it 'detects the file removal' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'unnecessary.txt'
- watch(listener, path) do
+ watch(listener, 1, path) do
rm 'unnecessary.txt'
end
end
end
- context 'given a symlink' do
+ context 'given a symlink', :unless => windows? do
it 'detects the file removal' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
touch 'unnecessary.rb'
ln_s 'unnecessary.rb', 'unnecessary_symlink.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
rm 'unnecessary_symlink.rb'
end
end
@@ -438,16 +399,14 @@ def watch(listener, *paths)
context 'given an existing directory' do
it 'detects the file removal' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with(["#{path}/a_directory"], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory")
end
mkdir 'a_directory'
touch 'a_directory/do_not_use.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
rm 'a_directory/do_not_use.rb'
end
end
@@ -457,16 +416,14 @@ def watch(listener, *paths)
context 'given a directory with subdirectories' do
it 'detects the file removal' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with(["#{path}/a_directory/subdirectory"], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path}/a_directory/subdirectory")
end
mkdir_p 'a_directory/subdirectory'
touch 'a_directory/subdirectory/do_not_use.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
rm 'a_directory/subdirectory/do_not_use.rb'
end
end
@@ -478,15 +435,11 @@ def watch(listener, *paths)
context 'multiple file operations' do
it 'detects the added files' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).at_least(:once).with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
- watch(listener, path) do
+ watch(listener, 2, path) do
touch 'a_file.rb'
touch 'b_file.rb'
mkdir 'a_directory'
@@ -501,12 +454,8 @@ def watch(listener, *paths)
it 'detects the modified files' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
touch 'a_file.rb'
@@ -515,7 +464,7 @@ def watch(listener, *paths)
touch 'a_directory/a_file.rb'
touch 'a_directory/b_file.rb'
- watch(listener, path) do
+ watch(listener, 2, path) do
touch 'b_file.rb'
touch 'a_directory/a_file.rb'
end
@@ -524,12 +473,8 @@ def watch(listener, *paths)
it 'detects the removed files' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
touch 'a_file.rb'
@@ -538,7 +483,7 @@ def watch(listener, *paths)
touch 'a_directory/a_file.rb'
touch 'a_directory/b_file.rb'
- watch(listener, path) do
+ watch(listener, 2, path) do
rm 'b_file.rb'
rm 'a_directory/a_file.rb'
end
@@ -549,17 +494,15 @@ def watch(listener, *paths)
context 'single directory operations' do
it 'detects a moved directory' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path)
end
mkdir 'a_directory'
touch 'a_directory/a_file.rb'
touch 'a_directory/b_file.rb'
- watch(listener, path) do
+ watch(listener, 1, path) do
mv 'a_directory', 'renamed'
end
end
@@ -567,19 +510,15 @@ def watch(listener, *paths)
it 'detects a removed directory' do
fixtures do |path|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path], :recursive => true)
- else
- listener.should_receive(:on_change).once.with do |array, options|
- array.should =~ [path, "#{path}/a_directory"]
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path, "#{path}/a_directory")
end
mkdir 'a_directory'
touch 'a_directory/a_file.rb'
touch 'a_directory/b_file.rb'
- watch(listener, path) do
+ watch(listener, 2, path) do
rm_rf 'a_directory'
end
end
@@ -590,7 +529,7 @@ def watch(listener, *paths)
context 'when a file is created' do
it "doesn't detects the added file" do
fixtures do |path|
- watch(listener, path) do
+ watch(listener, 1, path) do # The expected changes param is set to one!
@adapter.paused = true
touch 'new_file.rb'
end
@@ -604,13 +543,11 @@ def watch(listener, *paths)
context 'when files are added to one of multiple directories' do
it 'detects added files' do
fixtures(2) do |path1, path2|
- if options[:recursive]
- listener.should_receive(:on_change).once.with([path2], :recursive => true)
- else
- listener.should_receive(:on_change).once.with([path2], {})
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path2)
end
- watch(listener, path1, path2) do
+ watch(listener, 1, path1, path2) do
touch "#{path2}/new_file.rb"
end
end
@@ -620,17 +557,11 @@ def watch(listener, *paths)
context 'when files are added to multiple directories' do
it 'detects added files' do
fixtures(2) do |path1, path2|
- if options[:recursive]
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path1, path2] && options.should == {:recursive => true}
- end
- else
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path1, path2] && options.should == {}
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path1, path2)
end
- watch(listener, path1, path2) do
+ watch(listener, 2, path1, path2) do
touch "#{path1}/new_file.rb"
touch "#{path2}/new_file.rb"
end
@@ -641,20 +572,15 @@ def watch(listener, *paths)
context 'given a new and an existing directory on multiple directories' do
it 'detects the added file' do
fixtures(2) do |path1, path2|
- if options[:recursive]
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path1, path2] && options.should == {:recursive => true}
- end
- else
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path2, "#{path2}/b_directory", "#{path1}/a_directory"] && options.should == {}
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path2, "#{path1}/a_directory", "#{path2}/b_directory")
end
mkdir "#{path1}/a_directory"
- watch(listener, path1, path2) do
+ watch(listener, 3, path1, path2) do
mkdir "#{path2}/b_directory"
+ # Needed for INotify
sleep 0.05
touch "#{path1}/a_directory/new_file.rb"
touch "#{path2}/b_directory/new_file.rb"
@@ -666,21 +592,15 @@ def watch(listener, *paths)
context 'when a file is moved between the multiple watched directories' do
it 'detects the movements of the file' do
fixtures(3) do |path1, path2, path3|
- if options[:recursive]
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path1, path2, path3] && options.should == {:recursive => true}
- end
- else
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ ["#{path1}/from_directory", path2, "#{path3}/to_directory"] && options.should == {}
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include("#{path1}/from_directory", path2, "#{path3}/to_directory")
end
mkdir "#{path1}/from_directory"
touch "#{path1}/from_directory/move_me.txt"
mkdir "#{path3}/to_directory"
- watch(listener, path1, path2, path3) do
+ watch(listener, 3, path1, path2, path3) do
mv "#{path1}/from_directory/move_me.txt", "#{path2}/move_me.txt"
mv "#{path2}/move_me.txt", "#{path3}/to_directory/move_me.txt"
end
@@ -691,20 +611,14 @@ def watch(listener, *paths)
context 'when files are deleted from the multiple watched directories' do
it 'detects the files removal' do
fixtures(2) do |path1, path2|
- if options[:recursive]
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path1, path2] && options.should == {:recursive => true}
- end
- else
- listener.should_receive(:on_change).once.with do |directories, options|
- directories.should =~ [path1, path2] && options.should == {}
- end
+ listener.should_receive(:on_change).once.with do |array|
+ array.should include(path1, path2)
end
touch "#{path1}/unnecessary.txt"
touch "#{path2}/unnecessary.txt"
- watch(listener, path1, path2) do
+ watch(listener, 2, path1, path2) do
rm "#{path1}/unnecessary.txt"
rm "#{path2}/unnecessary.txt"
end
@@ -712,5 +626,4 @@ def watch(listener, *paths)
end
end
end
-
end
Something went wrong with that request. Please try again.