diff --git a/.coverage b/.coverage index ee77b7c..0b058a0 100644 --- a/.coverage +++ b/.coverage @@ -1 +1 @@ -!coverage.py: This is a private format, don't read it directly!{"lines":{"/home/eohm/github/Elias481/PyInotify/inotify/calls.py":[1,2,4,6,8,11,12,13,14,16,18,25,32,33,34,35,37,39,40,41,43,45,46,47,49,51,52,53,55,56],"/home/dustin/development/python/pyinotify/inotify/adapters.py":[1,2,3,4,5,6,8,10,11,15,16,20,22,23,25,26,27,28,31,32,35,36,39,40,41,42,43,44,46,47,49,51,53,56,59,60,61,63,65,66,67,69,70,72,73,75,76,78,84,85,88,89,91,92,93,95,97,98,99,100,101,102,104,105,107,110,112,115,117,118,121,123,124,126,132,134,135,136,138,139,141,142,145,148,150,152,153,154,161,162,163,165,170,171,172,176,177,191,194,195,196,197,199,200,201,202,204,208,209,210,216,217,219,221,230,231,232,234,235,238,239,242,245,247,250,252,253,256,259,261,264,265,267,268,269,271,273,275,276,278,280,281,282,283,285,287,288,289,292,294,295,298,299,301,302,303,305,307,308,310,312,313,314,315,317,319,327,328],"/home/dustin/development/python/pyinotify/inotify/test_support.py":[1,2,3,4,5,7,9,11,13,14,16,17,19,21,22],"/home/eohm/github/Elias481/PyInotify/inotify/test_support.py":[1,2,3,4,5,7,9,11,13,14,16,17,19,21,22],"/home/eohm/github/Elias481/PyInotify/inotify/constants.py":[3,4,8,9,10,11,12,13,14,15,16,17,18,19,23,24,30,34,35,36,40,41,42,43,44,46,47,48,52,53,54,55,56,57,58,59,60,61,62,63,67,68,69,73,74,75,76,77],"/home/dustin/development/python/pyinotify/inotify/constants.py":[3,4,8,9,10,11,12,13,14,15,16,17,18,19,23,24,30,34,35,36,40,41,42,43,44,46,47,48,52,53,54,55,56,57,58,59,60,61,62,63,67,68,69,73,74,75,76,77],"/home/eohm/github/Elias481/PyInotify/inotify/__init__.py":[1],"/home/eohm/github/Elias481/PyInotify/inotify/library.py":[1,2,4,5,8],"/home/dustin/development/python/pyinotify/inotify/calls.py":[1,2,4,6,8,11,12,18,25,32,33,37,39,40,41,43,45,46,47,49,51,52,53,55,56],"/home/dustin/development/python/pyinotify/inotify/__init__.py":[1],"/home/dustin/development/python/pyinotify/inotify/library.py":[8,1,2,4,5],"/home/eohm/github/Elias481/PyInotify/inotify/adapters.py":[1,2,3,4,5,6,8,10,11,12,13,14,15,16,18,19,20,23,24,25,27,28,30,31,32,33,35,36,37,38,39,40,41,44,45,48,49,50,51,52,53,54,55,57,58,59,60,61,62,63,65,66,68,69,71,72,73,75,76,77,78,79,80,81,82,83,85,86,87,89,90,91,93,95,96,97,98,99,101,103,104,106,107,109,110,111,113,114,116,117,118,120,121,122,123,124,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,146,147,149,150,151,152,153,154,155,158,160,161,163,164,165,167,168,169,171,172,173,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190,191,192,193,195,196,197,200,201,203,204,205,209,210,211,212,213,217,218,223,224,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,396,397,398,399]}} \ No newline at end of file +!coverage.py: This is a private format, don't read it directly!{"lines":{"/home/eohm/github/Elias481/PyInotify/inotify/calls.py":[1,2,4,6,8,11,12,13,14,16,18,25,32,33,34,35,37,39,40,41,43,45,46,47,49,51,52,53,55,56],"/home/dustin/development/python/pyinotify/inotify/adapters.py":[1,2,3,4,5,6,8,10,11,15,16,20,22,23,25,26,27,28,31,32,35,36,39,40,41,42,43,44,46,47,49,51,53,56,59,60,61,63,65,66,67,69,70,72,73,75,76,78,84,85,88,89,91,92,93,95,97,98,99,100,101,102,104,105,107,110,112,115,117,118,121,123,124,126,132,134,135,136,138,139,141,142,145,148,150,152,153,154,161,162,163,165,170,171,172,176,177,191,194,195,196,197,199,200,201,202,204,208,209,210,216,217,219,221,230,231,232,234,235,238,239,242,245,247,250,252,253,256,259,261,264,265,267,268,269,271,273,275,276,278,280,281,282,283,285,287,288,289,292,294,295,298,299,301,302,303,305,307,308,310,312,313,314,315,317,319,327,328],"/home/dustin/development/python/pyinotify/inotify/test_support.py":[1,2,3,4,5,7,9,11,13,14,16,17,19,21,22],"/home/eohm/github/Elias481/PyInotify/inotify/test_support.py":[1,2,3,4,5,7,9,11,13,14,16,17,19,21,22],"/home/eohm/github/Elias481/PyInotify/inotify/constants.py":[3,4,8,9,10,11,12,13,14,15,16,17,18,19,23,24,30,34,35,36,40,41,42,43,44,46,47,48,52,53,54,55,56,57,58,59,60,61,62,63,67,68,69,73,74,75,76,77],"/home/dustin/development/python/pyinotify/inotify/constants.py":[3,4,8,9,10,11,12,13,14,15,16,17,18,19,23,24,30,34,35,36,40,41,42,43,44,46,47,48,52,53,54,55,56,57,58,59,60,61,62,63,67,68,69,73,74,75,76,77],"/home/eohm/github/Elias481/PyInotify/inotify/__init__.py":[1],"/home/eohm/github/Elias481/PyInotify/inotify/library.py":[1,2,4,5,8],"/home/dustin/development/python/pyinotify/inotify/calls.py":[1,2,4,6,8,11,12,18,25,32,33,37,39,40,41,43,45,46,47,49,51,52,53,55,56],"/home/dustin/development/python/pyinotify/inotify/__init__.py":[1],"/home/dustin/development/python/pyinotify/inotify/library.py":[8,1,2,4,5],"/home/eohm/github/Elias481/PyInotify/inotify/adapters.py":[1,2,3,4,5,6,8,10,11,12,13,14,15,16,18,19,20,23,24,25,27,28,30,31,32,33,35,36,37,38,39,40,41,44,45,48,49,50,51,52,53,54,55,57,58,59,60,61,62,63,65,66,68,69,71,72,73,75,76,77,78,79,80,81,82,83,85,86,87,89,90,91,93,95,96,97,98,99,101,103,104,106,107,109,110,111,113,114,116,117,118,120,121,122,123,124,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,143,144,146,147,149,150,151,152,153,154,155,158,160,161,163,164,165,167,168,169,171,172,173,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190,191,192,193,195,196,197,200,201,203,204,205,209,210,211,212,213,217,218,223,224,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,396,397,398,399]}} diff --git a/.travis.yml b/.travis.yml index 0289023..ba0179b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,18 +2,43 @@ language: python matrix: include: - os: linux + dist: precise python: 2.6 cache: pip - os: linux + dist: precise + python: 2.6 + cache: pip + env: USE_SCANDIR=1 + - os: linux + dist: xenial + python: 2.7 + - os: linux + dist: xenial python: 2.7 + env: USE_SCANDIR=1 - os: linux + dist: precise python: 3.3 - os: linux + dist: trusty python: 3.4 - os: linux + dist: trusty + python: 3.4 + env: USE_SCANDIR=1 + - os: linux + dist: xenial python: 3.5 + - os: linux + dist: xenial + python: 3.6 + - os: linux + dist: xenial + python: 3.7 install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install pycparser==2.18; pip install unittest2; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install pycparser==2.18 idna==2.7; pip install unittest2; fi + - if [[ $USE_SCANDIR ]]; then pip install scandir; fi - pip install -r requirements.txt - pip install coveralls script: nosetests -s -v --with-coverage --cover-package=inotify diff --git a/inotify/adapters.py b/inotify/adapters.py index 4bf7e5f..faa001f 100644 --- a/inotify/adapters.py +++ b/inotify/adapters.py @@ -7,11 +7,14 @@ if hasattr(os, 'scandir'): from os import walk + scandirmode = 'builtin' else: try: from scandir import walk + scandirmode = 'external' except ImportError: from os import walk + scandirmode = 'unavailable' from errno import EINTR @@ -23,6 +26,9 @@ _DEFAULT_EPOLL_BLOCK_DURATION_S = 1 _HEADER_STRUCT_FORMAT = 'iIII' +# todo: the real terminal event beside IN_Q_OVERFLOW is IN_IGNORED +# ohterwise the IN_DELETE_SELF would count as much as IN_UNMOUNT +# where both could be handled differently, depending on context _DEFAULT_TERMINAL_EVENTS = ( 'IN_Q_OVERFLOW', 'IN_UNMOUNT', @@ -31,6 +37,7 @@ # Globals. _LOGGER = logging.getLogger(__name__) +_LOGGER.debug("Inotify initialized with scandir state '%s'.", scandirmode) _INOTIFY_EVENT = collections.namedtuple( '_INOTIFY_EVENT', @@ -89,12 +96,17 @@ def __del__(self): def add_watch(self, path_unicode, mask=inotify.constants.IN_ALL_EVENTS): _LOGGER.debug("Adding watch: [%s]", path_unicode) + # todo: handle removes for same object (with possible different pathnames) + # more outcome-oriented + + # todo: cope with the mentioned race-conditions # Because there might be race-conditions in the recursive handling (see # the notes in the documentation), we recommend to add watches using # data from a secondary channel, if possible, which means that we might # then be adding it, yet again, if we then receive it in the normal # fashion afterward. if path_unicode in self.__watches: + # to consider: a raise would be more appropriate for most cases _LOGGER.warning("Path already being watched: [%s]", path_unicode) return @@ -108,49 +120,61 @@ def add_watch(self, path_unicode, mask=inotify.constants.IN_ALL_EVENTS): return wd + def _remove_watch(self, wd, path, superficial=False): + _LOGGER.debug("Removing watch for watch-handle (%d): [%s]", + wd, path) + + if superficial is not None: + del self.__watches[path] + del self.__watches_r[wd] + inotify.adapters._LOGGER.debug(".. removed from adaptor") + if superficial is not False: + return + inotify.calls.inotify_rm_watch(self.__inotify_fd, wd) + _LOGGER.debug(".. removed from inotify") + + def remove_watch(self, path, superficial=False): """Remove our tracking information and call inotify to stop watching the given path. When a directory is removed, we'll just have to remove our tracking since inotify already cleans-up the watch. + With superficial set to None it is also possible to remove only inotify + watch to be able to wait for the final IN_IGNORED event received for + the wd (useful for example in threaded applications). """ + # todo: handle removes for same object (with possible different pathnames) + # more outcome-oriented + wd = self.__watches.get(path) if wd is None: + _LOGGER.warning("Path not in watch list: [%s]", path) + #todo: returning always None but no success indicator is not fine + # to consider: a raise would be more appropriate for most cases return - - _LOGGER.debug("Removing watch for watch-handle (%d): [%s]", - wd, path) - - del self.__watches[path] - - self.remove_watch_with_id(wd) + self._remove_watch(wd, path, superficial) def remove_watch_with_id(self, wd, superficial=False): - del self.__watches_r[wd] - - if superficial is False: - _LOGGER.debug("Removing watch for watch-handle (%d).", wd) - - inotify.calls.inotify_rm_watch(self.__inotify_fd, wd) + """Same as remove_watch but does the same by id""" + path = self.__watches_r.get(wd) + if path is None: + #todo: returning always None but no success indicator is not fine + # to consider: a raise would be more appropriate for most cases + _LOGGER.warning("Watchdescriptor not in watch list: [%d]", wd) + return + self._remove_watch(wd, path, superficial) def _get_event_names(self, event_type): - names = [] - for bit, name in inotify.constants.MASK_LOOKUP.items(): - if event_type & bit: - names.append(name) - event_type -= bit - - if event_type == 0: - break - - assert event_type == 0, \ - "We could not resolve all event-types: (%d)" % (event_type,) - - return names + try: + return inotify.constants.MASK_LOOKUP_COMB[event_type][:] + except KeyError as ex: + raise AssertionError("We could not resolve all event-types (%x)" % event_type) def _handle_inotify_event(self, wd): """Handle a series of events coming-in from inotify.""" + # to consider: inotify should always return only complete events + # for a single read, so implementation could be optimized b = os.read(wd, 1024) if not b: return @@ -187,6 +211,8 @@ def _handle_inotify_event(self, wd): self.__buffer = self.__buffer[event_length:] + #todo: proper accounting for renames missing (it's possible to leave + # that up to the user but the user currently cannot rename a watch) path = self.__watches_r.get(header.wd) if path is not None: filename_unicode = filename_bytes.decode('utf8') @@ -203,6 +229,13 @@ def event_gen( break when no event is received for that many seconds. """ + # todo: implement proper one-shot mechanism + # currently for that a function (! :-() is needed to temporary set + # block_duration which can only be specified for the whole instance + # to zero - in addition we would need to enable (or keep default :-() + # yield_nones and check for two nones in succession to then break iteration + # thats very inefficent and requires to much code for such a simple request + # We will either return due to the optional filter or because of a # timeout. The former will always set this. The latter will never set # this. @@ -273,10 +306,18 @@ def __init__(self, mask=inotify.constants.IN_ALL_EVENTS, # No matter what we actually received as the mask, make sure we have # the minimum that we require to curate our list of watches. + # + # todo: we really should have two masks... the combined one (requested|needed) + # and the user specified mask for the events he wants to receive from tree... + # + # todo: we shouldn't allow the IN_ONESHOT, IN_MASK_* flags here + # while IN_DONT_FOLLOW, IN_EXCL_UNLINK possibly need special implementation self._mask = mask | \ inotify.constants.IN_ISDIR | \ inotify.constants.IN_CREATE | \ - inotify.constants.IN_DELETE + inotify.constants.IN_MOVED_TO | \ + inotify.constants.IN_DELETE | \ + inotify.constants.IN_MOVED_FROM ignored_dirs_lookup = {} for parent, child in (os.path.split(ignored.rstrip('/')) for ignored in ignored_dirs): @@ -312,6 +353,14 @@ def event_gen(self, ignore_missing_new_folders=False, **kwargs): ) and \ ( os.path.exists(full_path) is True or + # todo: as long as the "Path already being watche/not in watch list" warnings + # instead of exceptions are in place, it should really be default to also log + # only a warning if target folder does not exists in tree autodiscover mode. + # - but probably better to implement that with try/catch around add_watch + # when errno fix is merged and also this should normally not be an argument + # to event_gen but to InotifyTree(s) constructor (at least set default there) + # to not steal someones use case to specify this differently for each event_gen + # call?? Even more this expression is simply wrong. ignore_missing_new_folders is False ) and \ ( @@ -325,21 +374,40 @@ def event_gen(self, ignore_missing_new_folders=False, **kwargs): self._load_tree(full_path) - if header.mask & inotify.constants.IN_MOVED_FROM: + if header.mask & inotify.constants.IN_DELETE: _LOGGER.debug("A directory has been removed. We're " "being recursive, but it would have " "automatically been deregistered: [%s]", full_path) + # todo: it would be appropriate to ensure the the watch is not removed + # that far that following events from the child fd are suppressed + # before the watch on the child disappeared + # also we have to take in mind that the subdirectory could be on + # ignore list (currently that is handled by the remove_watch but a + # debug message is emitted than what is not fine) + # The watch would've already been cleaned-up internally. self._i.remove_watch(full_path, superficial=True) elif header.mask & inotify.constants.IN_MOVED_FROM: _LOGGER.debug("A directory has been renamed. We're " - "being recursive, but it would have " - "automatically been deregistered: [%s]", - full_path) - - self._i.remove_watch(full_path, superficial=True) + "being recursive, we will remove watch " + "from it and re-add with IN_MOVED_TO " + "if target parent dir is within " + "our tree: [%s]", full_path) + + # todo: it would be fine if no remove/add action would take place + # if directory is moved within watched tree (so doesn't goes out of scope + # by the move) + # also we have to take in mind that the subdirectory could be on + # ignore list (currently that is handled by the exception handler) + try: + self._i.remove_watch(full_path, superficial=False) + except inotify.calls.InotifyError as ex: + # for the unlikely case the moved diretory is deleted + # and automatically unregistered before we try to + # unregister.... + pass yield event @@ -348,24 +416,32 @@ def inotify(self): return self._i def _load_tree(self, path): + # to be cosnidered: it would be very convenient to emit some "fake" events + # (events that are generated by the implementation and not inotify) for all + # found objects so that consumers do not need to scan directories again to + # generate themself an overview of the tree def filter_dirs_add_watches_gen(inotify, mask, dirpath, subdirs, ignored_subdirs): for subdir in subdirs: if subdir in ignored_subdirs: continue + # todo: this add_watch should include the IN_ONLYDIR flag inotify.add_watch(os.path.join(dirpath, subdir), mask) yield subdir inotify = self._i mask = self._mask + # todo: this add_watch should include the IN_ONLYDIR flag inotify.add_watch(path, mask) ignored_dirs = self._ignored_dirs + # todo: check wheter and how to handle symlinks to directories for dirpath, subdirs, _f in walk(path): ignored_subdirs = ignored_dirs.get(dirpath) if ignored_subdirs: subdirs[:] = filter_dirs_add_watches_gen(inotify, mask, dirpath, subdirs, ignored_subdirs) continue for subdir in subdirs: + # todo: this add_watch should include the IN_ONLYDIR flag inotify.add_watch(os.path.join(dirpath, subdir), mask) class InotifyTree(_BaseTree): diff --git a/inotify/calls.py b/inotify/calls.py index 99de66a..62b7097 100644 --- a/inotify/calls.py +++ b/inotify/calls.py @@ -1,5 +1,6 @@ import logging import ctypes +import os import inotify.library @@ -8,13 +9,18 @@ _LIB = inotify.library.instance +# todo: make this properly a subclass of OSError class InotifyError(Exception): def __init__(self, message, *args, **kwargs): - self.errno = ctypes.get_errno() - message += " ERRNO=(%d)" % (self.errno,) + errnum = ctypes.get_errno() + self.errno = errnum + try: errmsg = os.strerror(errnum) + except ValueError as ex: errmsg = '' + message += " ERRNO=%d %s" % (errnum,errmsg) super(InotifyError, self).__init__(message, *args, **kwargs) +# todo: remove (comment-out) unused checks to avoid dead code and increase test.coverage def _check_zero(result): if result != 0: raise InotifyError("Call failed (should return zero): (%d)" % @@ -22,6 +28,7 @@ def _check_zero(result): return result +# todo: remove (comment-out) unused checks to avoid dead code and increase test.coverage def _check_nonzero(result): if result == 0: raise InotifyError("Call failed (should return nonzero): (%d)" % diff --git a/inotify/constants.py b/inotify/constants.py index 30eaf55..f350a60 100644 --- a/inotify/constants.py +++ b/inotify/constants.py @@ -32,17 +32,28 @@ ## Events sent by kernel. IN_UNMOUNT = 0x00002000 # Backing fs was unmounted. -IN_Q_OVERFLOW = 0x00004000 # Event queued overflowed. -IN_IGNORED = 0x00008000 # File was ignored. +IN_Q_OVERFLOW = 0x00004000 # Event queue overflowed. [inotify fd unusable] +IN_IGNORED = 0x00008000 # File was ignored. (=Watch has been removed. [terminal event]) ## Special flags. +# for add watch: + IN_ONLYDIR = 0x01000000 # Only watch the path if it is a directory. IN_DONT_FOLLOW = 0x02000000 # Do not follow a sym link. +IN_EXCL_UNLINK = 0x04000000 # Exclude events on unlinked objects. +IN_MASK_CREATE = 0x10000000 # Ensure watch is not already established.. IN_MASK_ADD = 0x20000000 # Add to the mask of an already existing watch. -IN_ISDIR = 0x40000000 # Event occurred against dir. IN_ONESHOT = 0x80000000 # Only send event once. +# returned as part of event: + +IN_ISDIR = 0x40000000 # Event occurred against dir. + +## All events sent by kernel + +X_IN_ALL_EVENTS = (IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED) + MASK_LOOKUP = { 0o2000000: 'IN_CLOEXEC', 0o0004000: 'IN_NONBLOCK', @@ -72,7 +83,17 @@ 0x01000000: 'IN_ONLYDIR', 0x02000000: 'IN_DONT_FOLLOW', + 0x04000000: 'IN_EXCL_UNLINK', + 0x10000000: 'IN_MASK_CREATE', 0x20000000: 'IN_MASK_ADD', 0x40000000: 'IN_ISDIR', 0x80000000: 'IN_ONESHOT', } + +# Do not optimize away the unspecified combinations for IN_ISDIR +# as that would be only very minor performance improvement and +# things can change... (IN_ISDIR/IN_ACCESS is also "unspecified" +# but happens on some (newer?) kernels) +MASK_LOOKUP_COMB = dict(((em|dm, [en]+dn) + for em, en in MASK_LOOKUP.items() if em & X_IN_ALL_EVENTS + for dm, dn in ((0, []), (IN_ISDIR, ['IN_ISDIR'])))) diff --git a/inotify/library.py b/inotify/library.py index bb718d3..260e720 100644 --- a/inotify/library.py +++ b/inotify/library.py @@ -5,4 +5,4 @@ if _FILEPATH is None: _FILEPATH = 'libc.so.6' -instance = ctypes.cdll.LoadLibrary(_FILEPATH) +instance = ctypes.CDLL(_FILEPATH, use_errno=True) diff --git a/tests/test_inotify.py b/tests/test_inotify.py index b903029..aefdedd 100644 --- a/tests/test_inotify.py +++ b/tests/test_inotify.py @@ -6,9 +6,12 @@ import unittest2 as unittest else: import unittest +import errno +import shutil import inotify.constants import inotify.adapters +import inotify.calls import inotify.test_support try: @@ -28,28 +31,48 @@ def setUpModule(): i = inotify.adapters.Inotify() i.add_watch(path) - dircontent = os.listdir(inner_path) + i.add_watch(inner_path) + os.rmdir(inner_path) events = list(i.event_gen(timeout_s=1, yield_nones=False)) expected_na = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, subdirname), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, subdirname), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, subdirname), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, subdirname), ] expected_wa = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, subdirname), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, subdirname), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, subdirname), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, subdirname), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, subdirname), + ] + expected_ws = [ + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, subdirname), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], inner_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=32768, cookie=0, len=0), ['IN_IGNORED'], inner_path, ''), + ] + expected_ns = [ + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], inner_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=32768, cookie=0, len=0), ['IN_IGNORED'], inner_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, subdirname), ] global _HAS_DIRECTORY_ACCESS_EVENTS - if events == expected_na: + global _HAS_STRONG_PARENT_AFTER_CHILD + if events == expected_wa + expected_ns: + _HAS_DIRECTORY_ACCESS_EVENTS = True + _HAS_STRONG_PARENT_AFTER_CHILD = True + elif events == expected_na + expected_ws: _HAS_DIRECTORY_ACCESS_EVENTS = False - elif events == expected_wa: + _HAS_STRONG_PARENT_AFTER_CHILD = False + elif events == expected_wa + expected_ws: _HAS_DIRECTORY_ACCESS_EVENTS = True + _HAS_STRONG_PARENT_AFTER_CHILD = False + elif events == expected_na + expected_ns: + _HAS_DIRECTORY_ACCESS_EVENTS = False + _HAS_STRONG_PARENT_AFTER_CHILD = True else: - print('Got unknown list directory pattern:\n%r' %(events,)) - raise AssertionError('Found neighter expected list-directory pattern') + print('Got unknown directory access pattern:\n%r' %(events,)) + raise AssertionError('Found neighter expected directory access pattern') class TestInotify(unittest.TestCase): @@ -62,6 +85,8 @@ def __init__(self, *args, **kwargs): def setUpClass(cls): global _HAS_DIRECTORY_ACCESS_EVENTS cls._HAS_DIRECTORY_ACCESS_EVENTS = _HAS_DIRECTORY_ACCESS_EVENTS + global _HAS_STRONG_PARENT_AFTER_CHILD + cls._HAS_STRONG_PARENT_AFTER_CHILD = _HAS_STRONG_PARENT_AFTER_CHILD def __read_all_events(self, i): events = list(i.event_gen(timeout_s=1, yield_nones=False)) @@ -174,19 +199,35 @@ def test__cycle(self): events = self.__read_all_events(i) self.assertEquals(events, []) - def test__get_event_names(self): - all_mask = 0 - for bit in inotify.constants.MASK_LOOKUP.keys(): - all_mask |= bit - - all_names = inotify.constants.MASK_LOOKUP.values() - all_names = list(all_names) - - i = inotify.adapters.Inotify() - names = i._get_event_names(all_mask) - - self.assertEquals(names, all_names) - +#test disabled because _get_event_names has been refactored +#for better performance and consistent+useful order of eventnames +#and this function now only works for what it is intended for: +#resolve possible event-names for received events +#if that breaks some other test should fail +#todo: ensure all possible returns are triggered by testcases +# def test__get_event_names(self): +# all_mask = 0 +# for bit in inotify.constants.MASK_LOOKUP.keys(): +# all_mask |= bit +# +# all_names = inotify.constants.MASK_LOOKUP.values() +# all_names = list(all_names) +# +# i = inotify.adapters.Inotify() +# names = i._get_event_names(all_mask) +# +# self.assertEquals(names, all_names) + + def test__exception_errno(self): + with inotify.test_support.temp_path() as path: + i = inotify.adapters.Inotify() + errnum = None + path1 = os.path.join(path, 'abc') + try: + i.add_watch(path1) + except inotify.calls.InotifyError as ex: + errnum = ex.errno + self.assertEquals(errnum, errno.ENOENT) class TestInotifyTree(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -198,6 +239,8 @@ def __init__(self, *args, **kwargs): def setUpClass(cls): global _HAS_DIRECTORY_ACCESS_EVENTS cls._HAS_DIRECTORY_ACCESS_EVENTS = _HAS_DIRECTORY_ACCESS_EVENTS + global _HAS_STRONG_PARENT_AFTER_CHILD + cls._HAS_STRONG_PARENT_AFTER_CHILD = _HAS_STRONG_PARENT_AFTER_CHILD def __read_all_events(self, i): events = list(i.event_gen(timeout_s=1, yield_nones=False)) @@ -236,46 +279,46 @@ def test__cycle(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), ] _access_dir_a = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'aa'), - (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'aa'), + (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'aa'), (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'aa'), (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'aa'), - (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'aa'), + (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), ] _access_dir_b = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'bb'), - (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'bb'), + (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'bb'), (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'bb'), (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'bb'), - (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'bb'), + (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), ] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), ] _access_dir_a = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'aa'), - (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'aa'), - (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'aa'), + (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'aa'), + (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), ] _access_dir_b = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'bb'), - (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'bb'), - (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'bb'), + (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'bb'), + (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), ] # we can't be sure about the order the watches were registered @@ -298,15 +341,24 @@ def test__cycle(self): (inotify.adapters._INOTIFY_EVENT(wd=1, mask=512, cookie=0, len=16), ['IN_DELETE'], path, 'seen_new_file1'), (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=512, cookie=0, len=16), ['IN_DELETE'], path1, 'seen_new_file2'), (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=512, cookie=0, len=16), ['IN_DELETE'], path2, 'seen_new_file3'), + ] - (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=32768, cookie=0, len=0), ['IN_IGNORED'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_ISDIR', 'IN_DELETE'], path, 'aa'), + if self._HAS_STRONG_PARENT_AFTER_CHILD: + expected += [ + (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=w2, mask=32768, cookie=0, len=0), ['IN_IGNORED'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, 'aa'), - (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=32768, cookie=0, len=0), ['IN_IGNORED'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_ISDIR', 'IN_DELETE'], path, 'bb'), - ] + (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=w3, mask=32768, cookie=0, len=0), ['IN_IGNORED'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, 'bb'), + ] + else: + expected += [ + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, 'aa'), + + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, 'bb'), + ] self.assertEquals(events, expected) @@ -328,28 +380,28 @@ def test__renames(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], path, 'old_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'old_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], old_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_CREATE', 'IN_ISDIR'], path, 'old_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'old_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], old_path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'old_folder'), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], old_path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'old_folder'), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], old_path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'old_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], old_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'old_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], old_path, ''), ] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], path, 'old_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'old_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], old_path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'old_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], old_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_CREATE', 'IN_ISDIR'], path, 'old_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'old_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], old_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'old_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], old_path, ''), ] self.assertEquals(events1, expected) @@ -363,23 +415,23 @@ def test__renames(self): expected = [ (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741888, cookie=events2[1][0].cookie, len=16), ['IN_MOVED_FROM', 'IN_ISDIR'], path, 'old_folder'), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741952, cookie=events2[0][0].cookie, len=16), ['IN_MOVED_TO', 'IN_ISDIR'], path, 'new_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'new_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], new_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'new_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], new_path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'new_folder'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], new_path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'new_folder'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], new_path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'new_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], new_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'new_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], new_path, ''), ] else: expected = [ (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741888, cookie=events2[1][0].cookie, len=16), ['IN_MOVED_FROM', 'IN_ISDIR'], path, 'old_folder'), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741952, cookie=events2[0][0].cookie, len=16), ['IN_MOVED_TO', 'IN_ISDIR'], path, 'new_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'new_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], new_path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'new_folder'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], new_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'new_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], new_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'new_folder'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], new_path, ''), ] self.assertEquals(events2, expected) @@ -406,12 +458,19 @@ def test__renames(self): (inotify.adapters._INOTIFY_EVENT(wd=3, mask=128, cookie=events3[4][0].cookie, len=16), ['IN_MOVED_TO'], new_path, 'new_filename'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=512, cookie=0, len=16), ['IN_DELETE'], new_path, 'new_filename'), - - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], new_path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=32768, cookie=0, len=0), ['IN_IGNORED'], new_path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_ISDIR', 'IN_DELETE'], path, 'new_folder'), ] + if self._HAS_STRONG_PARENT_AFTER_CHILD: + expected += [ + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1024, cookie=0, len=0), ['IN_DELETE_SELF'], new_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=32768, cookie=0, len=0), ['IN_IGNORED'], new_path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, 'new_folder'), + ] + else: + expected += [ + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742336, cookie=0, len=16), ['IN_DELETE', 'IN_ISDIR'], path, 'new_folder'), + ] + self.assertEquals(events3, expected) def test__automatic_new_watches_on_new_paths(self): @@ -431,28 +490,28 @@ def test__automatic_new_watches_on_new_paths(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_CREATE', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'folder1'), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'folder1'), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), ] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_CREATE', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), ] self.assertEquals(events, expected) @@ -464,23 +523,23 @@ def test__automatic_new_watches_on_new_paths(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073742080, cookie=0, len=16), ['IN_CREATE', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path1, 'folder2'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path1, 'folder2'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), ] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073742080, cookie=0, len=16), ['IN_CREATE', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), ] self.assertEquals(events, expected) @@ -520,41 +579,41 @@ def test__automatic_new_watches_on_existing_paths(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'folder1'), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path, 'folder1'), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path1, 'folder2'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], path1, 'folder2'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=256, cookie=0, len=16), ['IN_CREATE'], path2, 'filename'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=32, cookie=0, len=16), ['IN_OPEN'], path2, 'filename'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], path2, 'filename'), ] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, 'folder1'), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, 'folder2'), - (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, 'folder1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, 'folder2'), + (inotify.adapters._INOTIFY_EVENT(wd=3, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=256, cookie=0, len=16), ['IN_CREATE'], path2, 'filename'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=32, cookie=0, len=16), ['IN_OPEN'], path2, 'filename'), (inotify.adapters._INOTIFY_EVENT(wd=3, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], path2, 'filename'), @@ -603,38 +662,38 @@ def test__exclude_subdirectories(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), ] for dirpath, dirwd in sorted(watches.items(), key=lambda tup: tup[1])[1:]: parentpath, dirname = os.path.split(dirpath) parentwd = watches[parentpath] expects = [ - (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], parentpath, dirname), - (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], dirpath, ''), + (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], parentpath, dirname), + (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], dirpath, ''), (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], parentpath, dirname), (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], dirpath, ''), (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741825, cookie=0, len=16), ['IN_ACCESS', 'IN_ISDIR'], parentpath, dirname), (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], dirpath, ''), - (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], parentpath, dirname), - (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], dirpath, ''), + (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], parentpath, dirname), + (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], dirpath, ''), ] discovered_subdirs_expects[parentpath].append((dirpath, expects)) discovered_subdirs_expects[dirpath] = [] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path, ''), ] for dirpath, dirwd in sorted(watches.items(), key=lambda tup: tup[1])[1:]: parentpath, dirname = os.path.split(dirpath) parentwd = watches[parentpath] expects = [ - (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], parentpath, dirname), - (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], dirpath, ''), - (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], parentpath, dirname), - (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], dirpath, ''), + (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741856, cookie=0, len=16), ['IN_OPEN', 'IN_ISDIR'], parentpath, dirname), + (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], dirpath, ''), + (inotify.adapters._INOTIFY_EVENT(wd=parentwd, mask=1073741840, cookie=0, len=16), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], parentpath, dirname), + (inotify.adapters._INOTIFY_EVENT(wd=dirwd, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], dirpath, ''), ] discovered_subdirs_expects[parentpath].append((dirpath, expects)) discovered_subdirs_expects[dirpath] = [] @@ -648,6 +707,112 @@ def test__exclude_subdirectories(self): self.assertEquals(events, expected) + def test__moving_readded_folder(self): + #test for https://github.com/dsoprea/PyInotify/issues/46 + #doing no checks of genereated events as current master does + #not generate events that should really be expected in this case + #avoid having to adjust this - also not implement chcking for expected + #wd assignment now.. + #just check for no exception and expected watches in the end + #emulate slow mkdir/rmdir/rename... (because of another unfixed bug and + #because this is needed to reproduces issue) + with inotify.test_support.temp_path() as path: + path1 = os.path.join(path, 'org_folder') + path2 = os.path.join(path, 'ren_folder') + + i = inotify.adapters.InotifyTree(path) + os.mkdir(path1) + events = self.__read_all_events(i) + os.rmdir(path1) + events = self.__read_all_events(i) + os.mkdir(path1) + events = self.__read_all_events(i) + os.rename(path1, path2) + events = self.__read_all_events(i) + + watches = i._i._Inotify__watches + watches_reverse = i._i._Inotify__watches_r + + watches_expect = sorted((path,path2)) + watches_reg_names = sorted(watches.keys()) + watches_reg_check = dict((value, key) for key, value in watches.items()) + + self.assertEquals(watches_expect, watches_reg_names) + self.assertEquals(watches_reg_check, watches_reverse) + + def test__readd_deleted_folder(self): + #test for https://github.com/dsoprea/PyInotify/issues/51 + #doing no checks the directory-discovery events as current master does + #not generate events that should really be expected in this case + #avoid having to adjust this - also not implement chcking for expected + #wd assignment now.. + #just check for no exception, file creation events and expected watches + #at the end. emulate slow succession of filesystem actions... (because + #of another unfixed bug and because this is needed to reproduces issue) + with inotify.test_support.temp_path() as path: + path1 = os.path.join(path, 'folder') + file1 = os.path.join(path1, 'file1') + file2 = os.path.join(path1, 'file2') + + i = inotify.adapters.InotifyTree(path) + os.mkdir(path1) + events = self.__read_all_events(i) + with open(file1, 'w'): + pass + with open(file2, 'w'): + pass + events = self.__read_all_events(i) + + expected = [ + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=256, cookie=0, len=16), ['IN_CREATE'], path1, 'file1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=32, cookie=0, len=16), ['IN_OPEN'], path1, 'file1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], path1, 'file1'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=256, cookie=0, len=16), ['IN_CREATE'], path1, 'file2'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=32, cookie=0, len=16), ['IN_OPEN'], path1, 'file2'), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], path1, 'file2'), + ] + self.assertEquals(events, expected) + + shutil.rmtree(path1) + events = self.__read_all_events(i) + + #could do the following asserts here to prove the the assumption of amigian74 in + #his 5th point in issue 51 ("everything until now works fine") false, but that is + #not target of this test, also it is not his reposibility to verify this... + #so to get same issue he describes it's just a comment... + #self.assertEquals(len(i._i._Inotify__watches), 1) + #self.assertEquals(len(i._i._Inotify__watches_r), 1) + #self.assertNotIn(path1, i._i._Inotify__watches) + + os.mkdir(path1) + events = self.__read_all_events(i) + with open(file1, 'w'): + pass + with open(file2, 'w'): + pass + events = self.__read_all_events(i) + + watches = i._i._Inotify__watches + watches_reverse = i._i._Inotify__watches_r + + watches_expect = sorted((path,path1)) + watches_reg_names = sorted(watches.keys()) + watches_reg_check = dict((value, key) for key, value in watches.items()) + + self.assertEquals(watches_expect, watches_reg_names) + self.assertEquals(watches_reg_check, watches_reverse) + + wd = watches[path1] + expected = [ + (inotify.adapters._INOTIFY_EVENT(wd=wd, mask=256, cookie=0, len=16), ['IN_CREATE'], path1, 'file1'), + (inotify.adapters._INOTIFY_EVENT(wd=wd, mask=32, cookie=0, len=16), ['IN_OPEN'], path1, 'file1'), + (inotify.adapters._INOTIFY_EVENT(wd=wd, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], path1, 'file1'), + (inotify.adapters._INOTIFY_EVENT(wd=wd, mask=256, cookie=0, len=16), ['IN_CREATE'], path1, 'file2'), + (inotify.adapters._INOTIFY_EVENT(wd=wd, mask=32, cookie=0, len=16), ['IN_OPEN'], path1, 'file2'), + (inotify.adapters._INOTIFY_EVENT(wd=wd, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], path1, 'file2'), + ] + self.assertEquals(events, expected) + class TestInotifyTrees(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -659,6 +824,8 @@ def __init__(self, *args, **kwargs): def setUpClass(cls): global _HAS_DIRECTORY_ACCESS_EVENTS cls._HAS_DIRECTORY_ACCESS_EVENTS = _HAS_DIRECTORY_ACCESS_EVENTS + global _HAS_STRONG_PARENT_AFTER_CHILD + cls._HAS_STRONG_PARENT_AFTER_CHILD = _HAS_STRONG_PARENT_AFTER_CHILD def __read_all_events(self, i): events = list(i.event_gen(timeout_s=1, yield_nones=False)) @@ -684,19 +851,19 @@ def test__cycle(self): if self._HAS_DIRECTORY_ACCESS_EVENTS: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741825, cookie=0, len=0), ['IN_ACCESS', 'IN_ISDIR'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), ] else: expected = [ - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path1, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_ISDIR', 'IN_OPEN'], path2, ''), - (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path1, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741856, cookie=0, len=0), ['IN_OPEN', 'IN_ISDIR'], path2, ''), + (inotify.adapters._INOTIFY_EVENT(wd=2, mask=1073741840, cookie=0, len=0), ['IN_CLOSE_NOWRITE', 'IN_ISDIR'], path2, ''), ] expected += [