Skip to content

Commit

Permalink
[mac] Add support for non-recursive watches in FSEventsEmitter (#779)
Browse files Browse the repository at this point in the history
* Add support for non-recursive watches in FSEventsEmitter

The underlying `fseventsd` service always observes recursively. As such we're adding a filter in `FSEventEmitter` to support the non-recursive behaviour that is supported by other emitters.

* Add more test cases

Test a few more scenarios for non-recursive watches. Do note that at least on macOS we'll get a `DirModifiedEvent` for the created subdirectory.

* Update changelog

* Limit new test scenarios to macOS

We're fixing a defect on macOS after all, and addressing the failures on other platforms looks like opening a can of worms that is not related to the original issue.

* Add tests for move events
  • Loading branch information
CCP-Aporia committed May 3, 2021
1 parent cd18dfc commit 1c67997
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 5 deletions.
4 changes: 2 additions & 2 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Changelog

2021-0x-xx • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.0.3...master>`__

-
- Thanks to our beloved contributors: @
- [mac] Add support for non-recursive watches in FSEventsEmitter (`#779 <https://github.com/gorakhargosh/watchdog/pull/779>`_)
- Thanks to our beloved contributors: @CCP-Aporia, @


2.0.3
Expand Down
27 changes: 25 additions & 2 deletions src/watchdog/observers/fsevents.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,31 @@ def on_thread_stop(self):
self._watch = None

def queue_event(self, event):
logger.debug("queue_event %s", event)
EventEmitter.queue_event(self, event)
# fsevents defaults to be recursive, so if the watch was meant to be non-recursive then we need to drop
# all the events here which do not have a src_path / dest_path that matches the watched path
if self._watch.is_recursive:
logger.debug("queue_event %s", event)
EventEmitter.queue_event(self, event)
else:
if not self._is_recursive_event(event):
logger.debug("queue_event %s", event)
EventEmitter.queue_event(self, event)
else:
logger.debug("drop event %s", event)

def _is_recursive_event(self, event):
src_path = event.src_path if event.is_directory else os.path.dirname(event.src_path)
if src_path == self._watch.path:
return False

if isinstance(event, (FileMovedEvent, DirMovedEvent)):
# when moving something into the watch path we must always take the dirname,
# otherwise we miss out on `DirMovedEvent`s
dest_path = os.path.dirname(event.dest_path)
if dest_path == self._watch.path:
return False

return True

def _queue_created_event(self, event, src_path, dirname):
cls = DirCreatedEvent if event.is_directory else FileCreatedEvent
Expand Down
29 changes: 28 additions & 1 deletion tests/test_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,6 @@ def test_recursive_on():


@pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter)
@pytest.mark.skipif(platform.is_darwin(), reason="macOS watches are always recursive")
def test_recursive_off():
mkdir(p('dir1'))
start_watching(recursive=False)
Expand All @@ -428,6 +427,34 @@ def test_recursive_off():
with pytest.raises(Empty):
event_queue.get(timeout=5)

mkfile(p('b'))
expect_event(FileCreatedEvent(p('b')))
if not platform.is_windows():
expect_event(DirModifiedEvent(p()))

if platform.is_linux():
expect_event(FileClosedEvent(p('b')))

# currently limiting these additional events to macOS only, see https://github.com/gorakhargosh/watchdog/pull/779
if platform.is_darwin():
mkdir(p('dir1', 'dir2'))
with pytest.raises(Empty):
event_queue.get(timeout=5)
mkfile(p('dir1', 'dir2', 'somefile'))
with pytest.raises(Empty):
event_queue.get(timeout=5)

mkdir(p('dir3'))
expect_event(DirModifiedEvent(p())) # the contents of the parent directory changed

mv(p('dir1', 'dir2', 'somefile'), p('somefile'))
expect_event(FileMovedEvent(p('dir1', 'dir2', 'somefile'), p('somefile')))
expect_event(DirModifiedEvent(p()))

mv(p('dir1', 'dir2'), p('dir2'))
expect_event(DirMovedEvent(p('dir1', 'dir2'), p('dir2')))
expect_event(DirModifiedEvent(p()))


@pytest.mark.skipif(platform.is_windows(),
reason="Windows create another set of events for this test")
Expand Down

0 comments on commit 1c67997

Please sign in to comment.