Permalink
Browse files

Refactor darwin handler

Also added specs
  • Loading branch information...
1 parent c220ae1 commit 0aaa732844bbf9cfc0e2eea3d26d7002034194dc mynyml committed May 28, 2010
Showing with 130 additions and 212 deletions.
  1. +2 −2 lib/watchr.rb
  2. +0 −5 lib/watchr/controller.rb
  3. +45 −81 lib/watchr/event_handlers/darwin.rb
  4. +76 −121 test/event_handlers/test_darwin.rb
  5. +4 −0 test/test_helper.rb
  6. +3 −3 test/test_watchr.rb
View
4 lib/watchr.rb
@@ -37,7 +37,7 @@ module EventHandler
autoload :Base, 'watchr/event_handlers/base'
autoload :Portable, 'watchr/event_handlers/portable'
autoload :Unix, 'watchr/event_handlers/unix' if ::Watchr::HAVE_REV
- autoload :FSE, 'watchr/event_handlers/darwin' if ::Watchr::HAVE_FSE
+ autoload :Darwin, 'watchr/event_handlers/darwin' if ::Watchr::HAVE_FSE
end
class << self
@@ -111,7 +111,7 @@ def handler
case ENV['HANDLER'] || Config::CONFIG['host_os']
when /darwin|mach|osx|fsevents?/i
if Watchr::HAVE_FSE
- Watchr::EventHandler::FSE
+ Watchr::EventHandler::Darwin
else
Watchr.debug "fsevent not found. `gem install ruby-fsevent` to get evented handler"
Watchr::EventHandler::Portable
View
5 lib/watchr/controller.rb
@@ -31,7 +31,6 @@ class Controller
def initialize(script, handler)
@script, @handler = script, handler
@handler.add_observer(self)
- @handler.controller = self
Watchr.debug "using %s handler" % handler.class.name
end
@@ -67,10 +66,6 @@ def update(path, event_type = nil)
end
end
- def update_monitored_paths
- @handler.refresh(monitored_paths)
- end
-
# List of paths the script is monitoring.
#
# Basically this means all paths below current directoly recursivelly that
View
126 lib/watchr/event_handlers/darwin.rb
@@ -1,105 +1,69 @@
module Watchr
module EventHandler
- class FSEWatcher < ::FSEvent
- attr_reader :handler
-
- def initialize(handler)
- super()
- @handler = handler
- self.latency = 0.2
- end
-
- def on_change(dirs)
- handler.on_change(dirs)
- end
- end
-
- class FSE
+ class Darwin < FSEvent
include Base
- attr_reader :watcher, :path_stats, :monitored_paths
- attr_accessor :controller
-
def initialize
- @watcher = FSEWatcher.new(self)
- @path_stats = {}
- end
-
- def on_change(dirs)
- update_monitored_paths
- watch_monitored_paths
- dirs.each do |dir|
- #Watchr.debug "change in #{dir}"
- changed_pathname = Pathname(dir)
- monitored_paths.each do |pathname|
- if pathname.dirname.basename == changed_pathname.basename
- type = detect_change(pathname)
- if type
- #Watchr.debug type
- notify(pathname, type)
- update_path_stats(pathname) unless type == :deleted
- end
- end
- end
- end
+ super
+ self.latency = 0.2
end
def listen(monitored_paths)
- @monitored_paths = monitored_paths
- watch_monitored_paths
- @watcher.start
+ register_paths(monitored_paths)
+ start
end
def refresh(monitored_paths)
- @monitored_paths = monitored_paths
- watch_monitored_paths
+ register_paths(monitored_paths)
+ restart
end
- protected
-
- def watch_monitored_paths
- init_path_stats
- paths = monitored_paths.map {|p| p.dirname.to_s}.uniq
- @watcher.watch_directories(paths)
- end
+ private
+ def on_change(dirs)
+ dirs.each {|dir| notify(*detect_change_in(dir)) }
+ end
- def init_path_stats
- now = Time.now
- monitored_paths.each do |pathname|
- unless path_stats[pathname]
- path_stats[pathname] = {:mtime => now, :atime => now, :ctime => now}
+ def detect_change_in(dir)
+ paths = monitored_paths_for(dir)
+ paths.each do |path|
+ type = event_type(path)
+ return [path, type] if type
end
end
- end
-
- def detect_change(pathname)
- return :deleted if !pathname.exist?
- return :modified if pathname.mtime > mtime(pathname)
- return :accessed if pathname.atime > atime(pathname)
- return :changed if pathname.ctime > ctime(pathname)
- end
- def update_monitored_paths
- @monitored_paths = controller.monitored_paths
- end
+ def event_type(path)
+ return :deleted if !path.exist?
+ return :modified if path.mtime > @reference_times[path][:mtime]
+ return :accessed if path.atime > @reference_times[path][:atime]
+ return :changed if path.ctime > @reference_times[path][:ctime]
+ nil
+ end
- def update_path_stats(pathname)
- path_stats[pathname][:mtime] = pathname.mtime
- path_stats[pathname][:atime] = pathname.atime
- path_stats[pathname][:ctime] = pathname.ctime
- end
+ def monitored_paths_for(dir)
+ dir = Pathname(dir).expand_path
+ @paths.select {|path| path.dirname.expand_path == dir }
+ end
- def mtime(pathname)
- path_stats[pathname][:mtime]
- end
+ def register_paths(paths)
+ @paths = paths
+ watch_directories(dirs_for(@paths))
+ update_reference_times
+ end
- def atime(pathname)
- path_stats[pathname][:atime]
- end
+ def dirs_for(paths)
+ paths.map {|path| path.dirname.to_s }.uniq
+ end
- def ctime(pathname)
- path_stats[pathname][:ctime]
- end
+ def update_reference_times
+ @reference_times = {}
+ now = Time.now
+ @paths.each do |path|
+ @reference_times[path] = {}
+ @reference_times[path][:atime] = now
+ @reference_times[path][:mtime] = now
+ @reference_times[path][:ctime] = now
+ end
+ end
end
end
end
View
197 test/event_handlers/test_darwin.rb
@@ -2,161 +2,116 @@
if Watchr::HAVE_FSE
-class Watchr::EventHandler::Unix::SingleFileWatcher
- public :type
+class Watchr::EventHandler::Darwin
+ attr_accessor :paths
+
+ def start() end #noop
+ def restart() end #noop
+
+ public :on_change, :registered_directories
end
-class UnixEventHandlerTest < MiniTest::Unit::TestCase
+class DarwinEventHandlerTest < MiniTest::Unit::TestCase
include Watchr
- SingleFileWatcher = EventHandler::Unix::SingleFileWatcher
+ private
- def setup
- @now = Time.now
- pathname = Pathname.new('foo/bar')
- pathname.stubs(:atime ).returns(@now)
- pathname.stubs(:mtime ).returns(@now)
- pathname.stubs(:ctime ).returns(@now)
- pathname.stubs(:exist?).returns(true)
- SingleFileWatcher.any_instance.stubs(:pathname).returns(pathname)
-
- @loop = Rev::Loop.default
- @handler = EventHandler::Unix.new
- @watcher = SingleFileWatcher.new('foo/bar')
- @loop.stubs(:run)
+ def tempfile(name)
+ file = Tempfile.new(name, tmpdir.to_s)
+ Pathname(file.path)
+ ensure
+ file.close
end
- def teardown
- SingleFileWatcher.handler = nil
- Rev::Loop.default.watchers.every.detach
+ # TODO clean up tmpdirs after tests run
+ def tmpdir
+ @@_tmpdir ||= Pathname(Dir.mktmpdir("watchrspecs_"))
end
+ alias :root :tmpdir
- test "triggers listening state" do
- @loop.expects(:run)
- @handler.listen([])
- end
+ #at_exit { @@_tmpdir.delete }
- ## SingleFileWatcher
+ public
- test "watcher pathname" do
- assert_instance_of Pathname, @watcher.pathname
- assert_equal @watcher.path, @watcher.pathname.to_s
- end
-
- test "stores reference times" do
- @watcher.pathname.stubs(:atime).returns(:time)
- @watcher.pathname.stubs(:mtime).returns(:time)
- @watcher.pathname.stubs(:ctime).returns(:time)
+ def setup
+ @now = Time.now
+ @handler = EventHandler::Darwin.new
- @watcher.send(:update_reference_times)
- assert_equal :time, @watcher.instance_variable_get(:@reference_atime)
- assert_equal :time, @watcher.instance_variable_get(:@reference_mtime)
- assert_equal :time, @watcher.instance_variable_get(:@reference_ctime)
+ @foo = tempfile('foo').expand_path
+ @bar = tempfile('bar').expand_path
end
- test "stores initial reference times" do
- SingleFileWatcher.any_instance.expects(:update_reference_times)
- SingleFileWatcher.new('foo')
+ test "listening triggers listening state" do
+ @handler.expects(:start)
+ @handler.listen([])
end
- test "updates reference times on change" do
- @watcher.expects(:update_reference_times)
- @watcher.on_change
+ test "listens for events on monitored files" do
+ @handler.listen [ @foo, @bar ]
+ assert_includes @handler.paths, @foo
+ assert_includes @handler.paths, @bar
end
- test "detects event type" do
- trigger_event @watcher, @now, :atime
- assert_equal :accessed, @watcher.type
-
- trigger_event @watcher, @now, :mtime
- assert_equal :modified, @watcher.type
-
- trigger_event @watcher, @now, :ctime
- assert_equal :changed, @watcher.type
-
- trigger_event @watcher, @now, :atime, :mtime
- assert_equal :modified, @watcher.type
-
- trigger_event @watcher, @now, :mtime, :ctime
- assert_equal :modified, @watcher.type
-
- trigger_event @watcher, @now, :atime, :ctime
- assert_equal :accessed, @watcher.type
-
- trigger_event @watcher, @now, :atime, :mtime, :ctime
- assert_equal :modified, @watcher.type
-
- @watcher.pathname.stubs(:exist?).returns(false)
- assert_equal :deleted, @watcher.type
+ test "reattaches to new monitored files" do
+ @baz = tempfile('baz').expand_path
+ @bax = tempfile('bax').expand_path
+
+ @handler.listen [ @foo, @bar ]
+ assert_includes @handler.paths, @foo
+ assert_includes @handler.paths, @bar
+
+ @handler.refresh [ @baz, @bax ]
+ assert_includes @handler.paths, @baz
+ assert_includes @handler.paths, @bax
+ refute_includes @handler.paths, @foo
+ refute_includes @handler.paths, @bar
end
- ## monitoring file events
+ ## event types
- test "listens for events on monitored files" do
- @handler.listen %w( foo bar )
- assert_equal 2, @loop.watchers.size
- assert_equal %w( foo bar ).to_set, @loop.watchers.every.path.to_set
- assert_equal [SingleFileWatcher], @loop.watchers.every.class.uniq
- end
+ test "deleted file event" do
+ @foo.stubs(:exist?).returns(false)
- test "notifies observers on file event" do
- @watcher.stubs(:path).returns('foo')
- @handler.expects(:notify).with('foo', anything)
- @watcher.on_change
+ @handler.listen [ @foo, @bar ]
+ @handler.expects(:notify).with(@foo, :deleted)
+ @handler.on_change [root]
end
- test "notifies observers of event type" do
- trigger_event @watcher, @now, :atime
- @handler.expects(:notify).with('foo/bar', :accessed)
- @watcher.on_change
-
- trigger_event @watcher, @now, :mtime
- @handler.expects(:notify).with('foo/bar', :modified)
- @watcher.on_change
+ test "modified file event" do
+ @foo.stubs(:mtime).returns(@now + 100)
+ @handler.expects(:notify).with(@foo, :modified)
- trigger_event @watcher, @now, :ctime
- @handler.expects(:notify).with('foo/bar', :changed)
- @watcher.on_change
+ @handler.listen [ @foo, @bar ]
+ @handler.on_change [root]
+ end
- trigger_event @watcher, @now, :atime, :mtime, :ctime
- @handler.expects(:notify).with('foo/bar', :modified)
- @watcher.on_change
+ test "accessed file event" do
+ @foo.stubs(:atime).returns(@now + 100)
+ @handler.expects(:notify).with(@foo, :accessed)
- @watcher.pathname.stubs(:exist?).returns(false)
- @handler.expects(:notify).with('foo/bar', :deleted)
- @watcher.on_change
+ @handler.listen [ @foo, @bar ]
+ @handler.on_change [root]
end
- ## on the fly updates of monitored files list
+ test "changed file event" do
+ @foo.stubs(:ctime).returns(@now + 100)
+ @handler.expects(:notify).with(@foo, :changed)
- test "reattaches to new monitored files" do
- @handler.listen %w( foo bar )
- assert_equal 2, @loop.watchers.size
- assert_includes @loop.watchers.every.path, 'foo'
- assert_includes @loop.watchers.every.path, 'bar'
-
- @handler.refresh %w( baz bax )
- assert_equal 2, @loop.watchers.size
- assert_includes @loop.watchers.every.path, 'baz'
- assert_includes @loop.watchers.every.path, 'bax'
- refute_includes @loop.watchers.every.path, 'foo'
- refute_includes @loop.watchers.every.path, 'bar'
+ @handler.listen [ @foo, @bar ]
+ @handler.on_change [root]
end
- private
+ ## internal
+
+ test "registers directories" do
+ @handler.listen [ @foo, @bar ]
- def trigger_event(watcher, now, *types)
- watcher.pathname.stubs(:atime).returns(now)
- watcher.pathname.stubs(:mtime).returns(now)
- watcher.pathname.stubs(:ctime).returns(now)
- watcher.instance_variable_set(:@reference_atime, now)
- watcher.instance_variable_set(:@reference_mtime, now)
- watcher.instance_variable_set(:@reference_ctime, now)
-
- types.each do |type|
- watcher.pathname.stubs(type).returns(now+10)
- end
+ assert_equal @foo.dirname, @bar.dirname # make sure all tempfiles are in same dir
+ assert_equal 1, @handler.registered_directories.size
+ assert_includes @handler.registered_directories, @foo.dirname.to_s
+ assert_includes @handler.registered_directories, @bar.dirname.to_s
end
end
end # if Watchr::HAVE_FSE
+
View
4 test/test_helper.rb
@@ -30,3 +30,7 @@ def xtest(*args) end
puts "Skipping Unix handler tests. Install Rev (gem install rev) to properly test full suite"
end
+unless Watchr::HAVE_FSE
+ puts "Skipping Darwin handler tests. Install FSEvent (gem install ruby-fsevent) to properly test full suite (osx only)"
+end
+
View
6 test/test_watchr.rb
@@ -44,15 +44,15 @@ def setup
Watchr.handler = nil
ENV['HANDLER'] = 'darwin'
- assert_equal Watchr::EventHandler::FSE, Watchr.handler
+ assert_equal Watchr::EventHandler::Darwin, Watchr.handler
Watchr.handler = nil
ENV['HANDLER'] = 'osx'
- assert_equal Watchr::EventHandler::FSE, Watchr.handler
+ assert_equal Watchr::EventHandler::Darwin, Watchr.handler
Watchr.handler = nil
ENV['HANDLER'] = 'fsevent'
- assert_equal Watchr::EventHandler::FSE, Watchr.handler
+ assert_equal Watchr::EventHandler::Darwin, Watchr.handler
end

0 comments on commit 0aaa732

Please sign in to comment.