* configure.ac (--with-file-notification): Add kqueue.
(top): Remove special test for "${HAVE_NS}" and
${with_file_notification}, this is handled inside gfilenotify
tests. Add kqueue tests. Use NOTIFY_CFLAGS and NOTIFY_LIBS
instead of library specific variables. Add error message for
gfile on Nextstep.
* doc/lispref/os.texi (File Notifications): Add kqueue as backend.
Fix some glitches in the example.
* etc/NEWS: Mention kqueue.
* lisp/filenotify.el (file-notify--library)
(file-notify-descriptors, file-notify-callback)
(file-notify-add-watch, file-notify-rm-watch)
(file-notify-valid-p): Add kqueue support.
(file-notify--rm-descriptor): Remove WHAT arg.
* src/Makefile.in: Use NOTIFY_CFLAGS and NOTIFY_LIBS.
* src/emacs.c (main): Call globals_of_kqueue and syms_of_kqueue.
* src/inotify.c (inotifyevent_to_event): Extract file name from
watch_object if the event doesn't provide it.
(Finotify_add_watch): Add file name to watch_object.
* src/keyboard.c (make_lispy_event): Check also for HAVE_KQUEUE.
* src/kqueue.c: New file.
* src/lisp.h: Declare extern globals_of_kqueue and syms_of_kqueue.
* test/automated/file-notify-tests.el
(file-notify--test-expected-events): Remove.
(file-notify--test-cleanup): Do not set that variable.
(file-notify--test-timeout) Use different timeouts for
different libraries.
(file-notify--test-library): New defun.
(file-notify--test-event-test): Make stronger checks.
(file-notify--test-with-events): EVENTS can also be a list of
lists. Flush outstanding events before running the body.
Make timeout heuristically depend on the number of events.
(file-notify-test01-add-watch, file-notify-test02-events)
(file-notify-test04-file-validity, file-notify-test05-dir-validity):
Rewrite in order to call file monitors but directory monitors.
(file-notify-test02-events, file-notify-test04-file-validity): Do
not skip cygwin tests. Add additional test for file creation.
Adapt expected result for different backends.
(file-notify-test03-autorevert): Some of the tests don't work for
w32notify.
(file-notify-test06-many-events): New test.
OPTION_DEFAULT_OFF([modules],[compile with dynamic modules support])
AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
- [use a file notification library (LIB one of: yes, gfile, inotify, w32, no)])],
+ [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])],
[ case "${withval}" in
y | ye | yes ) val=yes ;;
n | no ) val=no ;;
- g | gf | gfi | gfil | gfile ) val=gfile ;;
i | in | ino | inot | inoti | inotif | inotify ) val=inotify ;;
+ k | kq | kqu | kque | kqueu | kqueue ) val=kqueue ;;
+ g | gf | gfi | gfil | gfile ) val=gfile ;;
w | w3 | w32 ) val=w32 ;;
* ) AC_MSG_ERROR(['--with-file-notification=$withval' is invalid;
-this option's value should be 'yes', 'no', 'gfile', 'inotify' or 'w32'.
+this option's value should be 'yes', 'no', 'inotify', 'kqueue', 'gfile' or 'w32'.
'yes' is a synonym for 'w32' on MS-Windows, for 'no' on Nextstep,
-otherwise for the first of 'inotify' or 'gfile' that is usable.])
+otherwise for the first of 'inotify', 'kqueue' or 'gfile' that is usable.])
;;
esac
with_file_notification=$val
NOTIFY_OBJ=
NOTIFY_SUMMARY=no
-dnl FIXME? Don't auto-detect on NS, but do allow someone to specify
-dnl a particular library. This doesn't make much sense?
-if test "${HAVE_NS}" = yes && test ${with_file_notification} = yes; then
- with_file_notification=no
-fi
-
dnl MS Windows native file monitor is available for mingw32 only.
case $with_file_notification,$opsys in
w32,cygwin)
fi ;;
esac
+dnl kqueue is available on BSD-like systems.
+case $with_file_notification,$NOTIFY_OBJ in
+ kqueue,* | yes,)
+ EMACS_CHECK_MODULES([KQUEUE], [libkqueue])
+ if test "$HAVE_KQUEUE" = "yes"; then
+ AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.])
+ CPPFLAGS="$CPPFLAGS -I/usr/include/kqueue"
+ NOTIFY_CFLAGS=$KQUEUE_CFLAGS
+ NOTIFY_LIBS=$KQUEUE_LIBS
+ NOTIFY_OBJ=kqueue.o
+ NOTIFY_SUMMARY="yes -lkqueue"
+ else
+ AC_SEARCH_LIBS(kqueue, [])
+ if test "$ac_cv_search_kqueue" != no; then
+ AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.])
+ NOTIFY_OBJ=kqueue.o
+ NOTIFY_SUMMARY="yes (kqueue)"
+ fi
+ fi ;;
+esac
+
dnl g_file_monitor exists since glib 2.18. G_FILE_MONITOR_EVENT_MOVED
dnl has been added in glib 2.24. It has been tested under
dnl GNU/Linux only.
case $with_file_notification,$NOTIFY_OBJ in
gfile,* | yes,)
- EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24])
- if test "$HAVE_GFILENOTIFY" = "yes"; then
- AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.])
- NOTIFY_OBJ=gfilenotify.o
- NOTIFY_SUMMARY="yes -lgio (gfile)"
+ if test "${HAVE_NS}" = yes; then
+ AC_MSG_ERROR(['--with-file-notification=gfile' is not supported in NextStep builds.
+Consider kqueue instead.])
+ else
+ EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24])
+ if test "$HAVE_GFILENOTIFY" = "yes"; then
+ AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.])
+ NOTIFY_CFLAGS=$GFILENOTIFY_CFLAGS
+ NOTIFY_LIBS=$GFILENOTIFY_LIBS
+ NOTIFY_OBJ=gfilenotify.o
+ NOTIFY_SUMMARY="yes -lgio (gfile)"
+ fi
fi ;;
esac
if test -n "$NOTIFY_OBJ"; then
AC_DEFINE(USE_FILE_NOTIFY, 1, [Define to 1 if using file notifications.])
fi
+AC_SUBST(NOTIFY_CFLAGS)
+AC_SUBST(NOTIFY_LIBS)
AC_SUBST(NOTIFY_OBJ)
-AC_SUBST(GFILENOTIFY_CFLAGS)
-AC_SUBST(GFILENOTIFY_LIBS)
dnl Do not put whitespace before the #include statements below.
dnl Older compilers (eg sunos4 cc) choke on it.
OLDLIBS="$LIBS"
CFLAGS="$CFLAGS $GTK_CFLAGS $RSVG_CFLAGS $DBUS_CFLAGS $SETTINGS_CFLAGS"
LIBS="$LIBS $GTK_LIBS $RSVG_LIBS $DBUS_LIBS $SETTINGS_LIBS"
-CFLAGS="$CFLAGS $GFILENOTIFY_CFLAGS $CAIRO_CFLAGS"
-LIBS="$LIBS $GFILENOTIFY_LIBS $CAIRO_LIBS"
+CFLAGS="$CFLAGS $NOTIFY_CFLAGS $CAIRO_CFLAGS"
+LIBS="$LIBS $NOTIFY_LIBS $CAIRO_LIBS"
AC_MSG_CHECKING([whether GLib is linked in])
AC_LINK_IFELSE([AC_LANG_PROGRAM(
[[#include <glib.h>
Several operating systems support watching of filesystems for changes
of files. If configured properly, Emacs links a respective library
-like @file{gfilenotify}, @file{inotify}, or @file{w32notify}
-statically. These libraries enable watching of filesystems on the
-local machine.
+like @file{inotify}, @file{kqueue}, @file{gfilenotify}, or
+@file{w32notify} statically. These libraries enable watching of
+filesystems on the local machine.
It is also possible to watch filesystems on remote machines,
@pxref{Remote Files,, Remote Files, emacs, The GNU Emacs Manual}
Note that the @file{w32notify} library does not report
@code{attribute-changed} events. When some file's attribute, like
permissions or modification time, has changed, this library reports a
-@code{changed} event.
+@code{changed} event. Likewise, the @file{kqueue} library does not
+report reliably file attribute changes when watching a directory.
The @code{stopped} event reports, that watching the file has been
stopped. This could be because @code{file-notify-rm-watch} was called
@group
(write-region "bla" nil "/tmp/foo")
@result{} Event (35025468 created "/tmp/.#foo")
- Event (35025468 changed "/tmp/foo") [2 times]
+ Event (35025468 changed "/tmp/foo")
Event (35025468 deleted "/tmp/.#foo")
@end group
@example
@group
(make-directory "/tmp/foo")
- @result{} nil
+ @result{} Event (35025468 created "/tmp/foo")
@end group
@group
(setq desc
(file-notify-add-watch
"/tmp/foo" '(change) 'my-notify-callback))
- @result{} 35025468
+ @result{} 11359632
@end group
@group
@group
(write-region "bla" nil "/tmp/foo/bla")
- @result{} Event (35025468 created "/tmp/foo/.#bla")
- Event (35025468 created "/tmp/foo/bla")
- Event (35025468 changed "/tmp/foo/bla")
- Event (35025468 changed "/tmp/foo/.#bla")
+ @result{} Event (11359632 created "/tmp/foo/.#bla")
+ Event (11359632 created "/tmp/foo/bla")
+ Event (11359632 changed "/tmp/foo/bla")
+ Event (11359632 deleted "/tmp/foo/.#bla")
@end group
@group
;; Deleting a file in the directory doesn't invalidate the watch.
(delete-file "/tmp/foo/bla")
- @result{} Event (35025468 deleted "/tmp/foo/bla")
+ @result{} Event (11359632 deleted "/tmp/foo/bla")
@end group
@group
(write-region "bla" nil "/tmp/foo/bla")
- @result{} Event (35025468 created "/tmp/foo/.#bla")
- Event (35025468 created "/tmp/foo/bla")
- Event (35025468 changed "/tmp/foo/bla")
- Event (35025468 changed "/tmp/foo/.#bla")
+ @result{} Event (11359632 created "/tmp/foo/.#bla")
+ Event (11359632 created "/tmp/foo/bla")
+ Event (11359632 changed "/tmp/foo/bla")
+ Event (11359632 deleted "/tmp/foo/.#bla")
@end group
@group
;; Deleting the directory invalidates the watch.
+;; Events arrive for different watch descriptors.
(delete-directory "/tmp/foo" 'recursive)
- @result{} Event (35025468 deleted "/tmp/foo/bla")
- Event (35025468 deleted "/tmp/foo")
- Event (35025468 stopped "/tmp/foo")
+ @result{} Event (35025468 deleted "/tmp/foo")
+ Event (11359632 deleted "/tmp/foo/bla")
+ Event (11359632 deleted "/tmp/foo")
+ Event (11359632 stopped "/tmp/foo")
@end group
@group
** 'configure' now prefers inotify to gfile for file notification,
unless gfile is explicitly requested via --with-file-notification='gfile'.
+---
+** 'configure' detects the kqueue file notification library on *BSD
+and Mac OS X machines.
+
---
** The configure option '--with-pkg-config-prog' has been removed.
Use './configure PKG_CONFIG=/full/name/of/pkg-config' if you need to.
** File Notifications
++++
+*** The kqueue library is integrated for *BSD and Mac OS X machines.
+
+++
*** The new event `stopped' signals, that a file notification watch is
not active any longer.
;;; Commentary
;; This package is an abstraction layer from the different low-level
-;; file notification packages `gfilenotify', `inotify' and
+;; file notification packages `inotify', `kqueue', `gfilenotify' and
;; `w32notify'.
;;; Code:
(defconst file-notify--library
(cond
- ((featurep 'gfilenotify) 'gfilenotify)
((featurep 'inotify) 'inotify)
+ ((featurep 'kqueue) 'kqueue)
+ ((featurep 'gfilenotify) 'gfilenotify)
((featurep 'w32notify) 'w32notify))
"Non-nil when Emacs has been compiled with file notification support.
The value is the name of the low-level file notification package
(defvar file-notify-descriptors (make-hash-table :test 'equal)
"Hash table for registered file notification descriptors.
A key in this hash table is the descriptor as returned from
-`gfilenotify', `inotify', `w32notify' or a file name handler.
-The value in the hash table is a list
+`inotify', `kqueue', `gfilenotify', `w32notify' or a file name
+handler. The value in the hash table is a list
(DIR (FILE . CALLBACK) (FILE . CALLBACK) ...)
Several values for a given DIR happen only for `inotify', when
different files from the same directory are watched.")
-(defun file-notify--rm-descriptor (descriptor &optional what)
+(defun file-notify--rm-descriptor (descriptor)
"Remove DESCRIPTOR from `file-notify-descriptors'.
DESCRIPTOR should be an object returned by `file-notify-add-watch'.
-If it is registered in `file-notify-descriptors', a stopped event is sent.
-WHAT is a file or directory name to be removed, needed just for `inotify'."
+If it is registered in `file-notify-descriptors', a stopped event is sent."
(let* ((desc (if (consp descriptor) (car descriptor) descriptor))
(file (if (consp descriptor) (cdr descriptor)))
(registered (gethash desc file-notify-descriptors))
(dir (car registered)))
- (when (and (consp registered) (or (null what) (string-equal dir what)))
+ (when (consp registered)
;; Send `stopped' event.
(dolist (entry (cdr registered))
(funcall (cdr entry)
(remhash desc file-notify-descriptors)
(puthash desc registered file-notify-descriptors))))))
-;; This function is used by `gfilenotify', `inotify' and `w32notify' events.
+;; This function is used by `inotify', `kqueue', `gfilenotify' and
+;; `w32notify' events.
;;;###autoload
(defun file-notify-handle-event (event)
"Handle file system monitoring event.
(setq actions nil))
;; Loop over actions. In fact, more than one action happens only
- ;; for `inotify'.
+ ;; for `inotify' and `kqueue'.
(dolist (action actions)
;; Send pending event, if it doesn't match.
;; Map action. We ignore all events which cannot be mapped.
(setq action
(cond
- ;; gfilenotify.
- ((memq action '(attribute-changed changed created deleted))
+ ((memq action
+ '(attribute-changed changed created deleted renamed))
action)
- ((eq action 'moved)
+ ((memq action '(moved rename))
(setq file1 (file-notify--event-file1-name event))
'renamed)
-
- ;; inotify, w32notify.
((eq action 'ignored)
(setq stopped t actions nil))
- ((eq action 'attrib) 'attribute-changed)
+ ((memq action '(attrib link)) 'attribute-changed)
((memq action '(create added)) 'created)
- ((memq action '(modify modified)) 'changed)
+ ((memq action '(modify modified write)) 'changed)
((memq action '(delete delete-self move-self removed)) 'deleted)
;; Make the event pending.
((memq action '(moved-from renamed-from))
(setq pending-event nil))
;; Check for stopped.
- ;;(message "file-notify-callback %S %S" file registered)
(setq
stopped
(or
(and
(memq action '(deleted renamed))
(= (length (cdr registered)) 1)
- (string-equal
- (file-name-nondirectory file)
- (or (file-name-nondirectory (car registered))
- (car (cadr registered)))))))
+ (or
+ (string-equal
+ (file-name-nondirectory file)
+ (file-name-nondirectory (car registered)))
+ (string-equal
+ (file-name-nondirectory file)
+ (car (cadr registered)))))))
;; Apply callback.
(when (and action
;; File matches.
(string-equal
(nth 0 entry) (file-name-nondirectory file))
+ ;; Directory matches.
+ (string-equal
+ (file-name-nondirectory file)
+ (file-name-nondirectory (car registered)))
;; File1 matches.
(and (stringp file1)
(string-equal
(nth 0 entry) (file-name-nondirectory file1)))))
+ ;;(message
+ ;;"file-notify-callback %S %S %S %S %S"
+ ;;(file-notify--descriptor desc file) action file file1 registered)
(if file1
(funcall
callback
;; Modify `file-notify-descriptors'.
(when stopped
- (file-notify--rm-descriptor
- (file-notify--descriptor desc file) file)))))
+ (file-notify-rm-watch (file-notify--descriptor desc file))))))
-;; `gfilenotify' and `w32notify' return a unique descriptor for every
-;; `file-notify-add-watch', while `inotify' returns a unique
+;; `kqueue', `gfilenotify' and `w32notify' return a unique descriptor
+;; for every `file-notify-add-watch', while `inotify' returns a unique
;; descriptor per inode only.
(defun file-notify-add-watch (file flags callback)
"Add a watch for filesystem events pertaining to FILE.
(if (file-directory-p file)
file
(file-name-directory file))))
- desc func l-flags registered)
+ desc func l-flags registered entry)
(unless (file-directory-p dir)
(signal 'file-notify-error `("Directory does not exist" ,dir)))
;; A file name handler could exist even if there is no local
;; file notification support.
(setq desc (funcall
- handler 'file-notify-add-watch dir flags callback))
+ handler 'file-notify-add-watch
+ ;; kqueue does not report file changes in
+ ;; directory monitor. So we must watch the file
+ ;; itself.
+ (if (eq file-notify--library 'kqueue) file dir)
+ flags callback))
;; Check, whether Emacs has been compiled with file notification
;; support.
;; Determine low-level function to be called.
(setq func
(cond
- ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
((eq file-notify--library 'inotify) 'inotify-add-watch)
+ ((eq file-notify--library 'kqueue) 'kqueue-add-watch)
+ ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
((eq file-notify--library 'w32notify) 'w32notify-add-watch)))
;; Determine respective flags.
(cond
((eq file-notify--library 'inotify)
'(create delete delete-self modify move-self move))
+ ((eq file-notify--library 'kqueue)
+ '(create delete write extend rename))
((eq file-notify--library 'w32notify)
'(file-name directory-name size last-write-time)))))
(when (memq 'attribute-change flags)
(push (cond
((eq file-notify--library 'inotify) 'attrib)
+ ((eq file-notify--library 'kqueue) 'attrib)
((eq file-notify--library 'w32notify) 'attributes))
l-flags)))
;; Call low-level function.
- (setq desc (funcall func dir l-flags 'file-notify-callback)))
+ (setq desc (funcall
+ func (if (eq file-notify--library 'kqueue) file dir)
+ l-flags 'file-notify-callback)))
;; Modify `file-notify-descriptors'.
- (setq registered (gethash desc file-notify-descriptors))
- (puthash
- desc
- `(,dir
- (,(unless (file-directory-p file) (file-name-nondirectory file))
- . ,callback)
- . ,(cdr registered))
- file-notify-descriptors)
+ (setq file (unless (file-directory-p file) (file-name-nondirectory file))
+ desc (if (consp desc) (car desc) desc)
+ registered (gethash desc file-notify-descriptors)
+ entry `(,file . ,callback))
+ (unless (member entry (cdr registered))
+ (puthash desc `(,dir ,entry . ,(cdr registered)) file-notify-descriptors))
;; Return descriptor.
- (file-notify--descriptor
- desc (unless (file-directory-p file) (file-name-nondirectory file)))))
+ (file-notify--descriptor desc file)))
(defun file-notify-rm-watch (descriptor)
"Remove an existing watch specified by its DESCRIPTOR.
(funcall
(cond
- ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
((eq file-notify--library 'inotify) 'inotify-rm-watch)
+ ((eq file-notify--library 'kqueue) 'kqueue-rm-watch)
+ ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
desc))
(file-notify-error nil)))
(funcall handler 'file-notify-valid-p descriptor)
(funcall
(cond
- ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
((eq file-notify--library 'inotify) 'inotify-valid-p)
+ ((eq file-notify--library 'kqueue) 'kqueue-valid-p)
+ ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
((eq file-notify--library 'w32notify) 'w32notify-valid-p))
desc))
t))))
## gtkutil.o if USE_GTK, else empty.
GTK_OBJ=@GTK_OBJ@
-## gfilenotify.o if HAVE_GFILENOTIFY.
## inotify.o if HAVE_INOTIFY.
+## kqueue.o if HAVE_KQUEUE.
+## gfilenotify.o if HAVE_GFILENOTIFY.
## w32notify.o if HAVE_W32NOTIFY.
NOTIFY_OBJ = @NOTIFY_OBJ@
-GFILENOTIFY_CFLAGS = @GFILENOTIFY_CFLAGS@
-GFILENOTIFY_LIBS = @GFILENOTIFY_LIBS@
+NOTIFY_CFLAGS = @NOTIFY_CFLAGS@
+NOTIFY_LIBS = @NOTIFY_LIBS@
## -ltermcap, or -lncurses, or -lcurses, or "".
LIBS_TERMCAP=@LIBS_TERMCAP@
$(WEBKIT_CFLAGS) \
$(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
$(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
- $(LIBGNUTLS_CFLAGS) $(GFILENOTIFY_CFLAGS) $(CAIRO_CFLAGS) \
+ $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \
$(WARN_CFLAGS) $(WERROR_CFLAGS) $(CFLAGS)
ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS)
$(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \
$(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
$(LIBGNUTLS_LIBS) $(LIB_PTHREAD) \
- $(GFILENOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES)
+ $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES)
$(leimdir)/leim-list.el: bootstrap-emacs$(EXEEXT)
$(MAKE) -C ../leim leim-list.el EMACS="$(bootstrap_exe)"
tzset ();
#endif /* MSDOS */
+#ifdef HAVE_KQUEUE
+ globals_of_kqueue ();
+#endif
+
#ifdef HAVE_GFILENOTIFY
globals_of_gfilenotify ();
#endif
syms_of_gnutls ();
-#ifdef HAVE_GFILENOTIFY
- syms_of_gfilenotify ();
-#endif /* HAVE_GFILENOTIFY */
-
#ifdef HAVE_INOTIFY
syms_of_inotify ();
#endif /* HAVE_INOTIFY */
+#ifdef HAVE_KQUEUE
+ syms_of_kqueue ();
+#endif /* HAVE_KQUEUE */
+
+#ifdef HAVE_GFILENOTIFY
+ syms_of_gfilenotify ();
+#endif /* HAVE_GFILENOTIFY */
+
#ifdef HAVE_DBUS
syms_of_dbusbind ();
#endif /* HAVE_DBUS */
static int inotifyfd = -1;
/* Assoc list of files being watched.
- Format:
- (watch-descriptor . callback)
+ Format: (watch-descriptor name callback)
*/
static Lisp_Object watch_list;
name = make_unibyte_string (ev->name, min (len, ev->len));
name = DECODE_FILE (name);
}
+ else
+ name = XCAR (XCDR (watch_object));
return list2 (list4 (make_watch_descriptor (ev->wd),
mask_to_aspects (ev->mask),
name,
make_number (ev->cookie)),
- XCDR (watch_object));
+ Fnth (make_number (2), watch_object));
}
/* This callback is called when the FD is available for read. The inotify
watch_list = Fdelete (watch_object, watch_list);
/* Store watch object in watch list. */
- watch_object = Fcons (watch_descriptor, callback);
+ watch_object = list3 (watch_descriptor, encoded_file_name, callback);
watch_list = Fcons (watch_object, watch_list);
return watch_descriptor;
#endif
-#if defined HAVE_GFILENOTIFY || defined HAVE_INOTIFY
+#if defined HAVE_INOTIFY || defined HAVE_KQUEUE || defined HAVE_GFILENOTIFY
case FILE_NOTIFY_EVENT:
{
return Fcons (Qfile_notify, event->arg);
}
-#endif /* defined HAVE_GFILENOTIFY || defined HAVE_INOTIFY */
+#endif /* HAVE_INOTIFY || HAVE_KQUEUE || HAVE_GFILENOTIFY */
case CONFIG_CHANGED_EVENT:
return list3 (Qconfig_changed_event,
--- /dev/null
+/* Filesystem notifications support with kqueue API.
+
+Copyright (C) 2015-2016 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#ifdef HAVE_KQUEUE
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include "lisp.h"
+#include "keyboard.h"
+#include "process.h"
+
+\f
+/* File handle for kqueue. */
+static int kqueuefd = -1;
+
+/* This is a list, elements are (DESCRIPTOR FILE FLAGS CALLBACK [DIRLIST]). */
+static Lisp_Object watch_list;
+
+/* Generate a list from the directory_files_internal output.
+ Items are (INODE FILE-NAME LAST-MOD LAST-STATUS-MOD SIZE). */
+Lisp_Object
+kqueue_directory_listing (Lisp_Object directory_files)
+{
+ Lisp_Object dl, result = Qnil;
+
+ for (dl = directory_files; ! NILP (dl); dl = XCDR (dl)) {
+ /* We ignore "." and "..". */
+ if ((strcmp (".", SSDATA (XCAR (XCAR (dl)))) == 0) ||
+ (strcmp ("..", SSDATA (XCAR (XCAR (dl)))) == 0))
+ continue;
+
+ result = Fcons
+ (list5 (/* inode. */
+ Fnth (make_number (11), XCAR (dl)),
+ /* filename. */
+ XCAR (XCAR (dl)),
+ /* last modification time. */
+ Fnth (make_number (6), XCAR (dl)),
+ /* last status change time. */
+ Fnth (make_number (7), XCAR (dl)),
+ /* size. */
+ Fnth (make_number (8), XCAR (dl))),
+ result);
+ }
+ return result;
+}
+
+/* Generate a file notification event. */
+static void
+kqueue_generate_event
+(Lisp_Object watch_object, Lisp_Object actions,
+ Lisp_Object file, Lisp_Object file1)
+{
+ Lisp_Object flags, action, entry;
+ struct input_event event;
+
+ /* Check, whether all actions shall be monitored. */
+ flags = Fnth (make_number (2), watch_object);
+ action = actions;
+ do {
+ if (NILP (action))
+ break;
+ entry = XCAR (action);
+ if (NILP (Fmember (entry, flags))) {
+ action = XCDR (action);
+ actions = Fdelq (entry, actions);
+ } else
+ action = XCDR (action);
+ } while (1);
+
+ /* Store it into the input event queue. */
+ if (! NILP (actions)) {
+ EVENT_INIT (event);
+ event.kind = FILE_NOTIFY_EVENT;
+ event.frame_or_window = Qnil;
+ event.arg = list2 (Fcons (XCAR (watch_object),
+ Fcons (actions,
+ NILP (file1)
+ ? Fcons (file, Qnil)
+ : list2 (file, file1))),
+ Fnth (make_number (3), watch_object));
+ kbd_buffer_store_event (&event);
+ }
+}
+
+/* This compares two directory listings in case of a `write' event for
+ a directory. Generate resulting file notification events. The old
+ directory listing is retrieved from watch_object, it will be
+ replaced by the new directory listing at the end of this
+ function. */
+static void
+kqueue_compare_dir_list
+(Lisp_Object watch_object)
+{
+ Lisp_Object dir, pending_dl, deleted_dl;
+ Lisp_Object old_directory_files, old_dl, new_directory_files, new_dl, dl;
+
+ dir = XCAR (XCDR (watch_object));
+ pending_dl = Qnil;
+ deleted_dl = Qnil;
+
+ old_directory_files = Fnth (make_number (4), watch_object);
+ old_dl = kqueue_directory_listing (old_directory_files);
+
+ /* When the directory is not accessible anymore, it has been deleted. */
+ if (NILP (Ffile_directory_p (dir))) {
+ kqueue_generate_event (watch_object, Fcons (Qdelete, Qnil), dir, Qnil);
+ return;
+ }
+ new_directory_files =
+ directory_files_internal (dir, Qnil, Qnil, Qnil, 1, Qnil);
+ new_dl = kqueue_directory_listing (new_directory_files);
+
+ /* Parse through the old list. */
+ dl = old_dl;
+ while (1) {
+ Lisp_Object old_entry, new_entry, dl1;
+ if (NILP (dl))
+ break;
+
+ /* Search for an entry with the same inode. */
+ old_entry = XCAR (dl);
+ new_entry = assq_no_quit (XCAR (old_entry), new_dl);
+ if (! NILP (Fequal (old_entry, new_entry))) {
+ /* Both entries are identical. Nothing to do. */
+ new_dl = Fdelq (new_entry, new_dl);
+ goto the_end;
+ }
+
+ /* Both entries have the same inode. */
+ if (! NILP (new_entry)) {
+ /* Both entries have the same file name. */
+ if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
+ SSDATA (XCAR (XCDR (new_entry)))) == 0) {
+ /* Modification time has been changed, the file has been written. */
+ if (NILP (Fequal (Fnth (make_number (2), old_entry),
+ Fnth (make_number (2), new_entry))))
+ kqueue_generate_event
+ (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (old_entry)), Qnil);
+ /* Status change time has been changed, the file attributes
+ have changed. */
+ if (NILP (Fequal (Fnth (make_number (3), old_entry),
+ Fnth (make_number (3), new_entry))))
+ kqueue_generate_event
+ (watch_object, Fcons (Qattrib, Qnil),
+ XCAR (XCDR (old_entry)), Qnil);
+
+ } else {
+ /* The file has been renamed. */
+ kqueue_generate_event
+ (watch_object, Fcons (Qrename, Qnil),
+ XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
+ deleted_dl = Fcons (new_entry, deleted_dl);
+ }
+ new_dl = Fdelq (new_entry, new_dl);
+ goto the_end;
+ }
+
+ /* Search, whether there is a file with the same name but another
+ inode. */
+ for (dl1 = new_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
+ new_entry = XCAR (dl1);
+ if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
+ SSDATA (XCAR (XCDR (new_entry)))) == 0) {
+ pending_dl = Fcons (new_entry, pending_dl);
+ new_dl = Fdelq (new_entry, new_dl);
+ goto the_end;
+ }
+ }
+
+ /* Check, whether this a pending file. */
+ new_entry = assq_no_quit (XCAR (old_entry), pending_dl);
+
+ if (NILP (new_entry)) {
+ /* Check, whether this is an already deleted file (by rename). */
+ for (dl1 = deleted_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
+ new_entry = XCAR (dl1);
+ if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
+ SSDATA (XCAR (XCDR (new_entry)))) == 0) {
+ deleted_dl = Fdelq (new_entry, deleted_dl);
+ goto the_end;
+ }
+ }
+ /* The file has been deleted. */
+ kqueue_generate_event
+ (watch_object, Fcons (Qdelete, Qnil), XCAR (XCDR (old_entry)), Qnil);
+
+ } else {
+ /* The file has been renamed. */
+ kqueue_generate_event
+ (watch_object, Fcons (Qrename, Qnil),
+ XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
+ pending_dl = Fdelq (new_entry, pending_dl);
+ }
+
+ the_end:
+ dl = XCDR (dl);
+ old_dl = Fdelq (old_entry, old_dl);
+ }
+
+ /* Parse through the resulting new list. */
+ dl = new_dl;
+ while (1) {
+ Lisp_Object entry;
+ if (NILP (dl))
+ break;
+
+ /* A new file has appeared. */
+ entry = XCAR (dl);
+ kqueue_generate_event
+ (watch_object, Fcons (Qcreate, Qnil), XCAR (XCDR (entry)), Qnil);
+
+ /* Check size of that file. */
+ Lisp_Object size = Fnth (make_number (4), entry);
+ if (FLOATP (size) || (XINT (size) > 0))
+ kqueue_generate_event
+ (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
+
+ dl = XCDR (dl);
+ new_dl = Fdelq (entry, new_dl);
+ }
+
+ /* Parse through the resulting pending_dl list. */
+ dl = pending_dl;
+ while (1) {
+ Lisp_Object entry;
+ if (NILP (dl))
+ break;
+
+ /* A file is still pending. Assume it was a write. */
+ entry = XCAR (dl);
+ kqueue_generate_event
+ (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
+
+ dl = XCDR (dl);
+ pending_dl = Fdelq (entry, pending_dl);
+ }
+
+ /* At this point, old_dl, new_dl and pending_dl shall be empty.
+ deleted_dl might not be empty when there was a rename to a
+ nonexistent file. Let's make a check for this (might be removed
+ once the code is stable). */
+ if (! NILP (old_dl))
+ report_file_error ("Old list not empty", old_dl);
+ if (! NILP (new_dl))
+ report_file_error ("New list not empty", new_dl);
+ if (! NILP (pending_dl))
+ report_file_error ("Pending events list not empty", pending_dl);
+ // if (! NILP (deleted_dl))
+ // report_file_error ("Deleted events list not empty", deleted_dl);
+
+ /* Replace old directory listing with the new one. */
+ XSETCDR (Fnthcdr (make_number (3), watch_object),
+ Fcons (new_directory_files, Qnil));
+ return;
+}
+
+/* This is the callback function for arriving input on kqueuefd. It
+ shall create a Lisp event, and put it into the Emacs input queue. */
+static void
+kqueue_callback (int fd, void *data)
+{
+ for (;;) {
+ struct kevent kev;
+ static const struct timespec nullts = { 0, 0 };
+ Lisp_Object descriptor, watch_object, file, actions;
+
+ /* Read one event. */
+ int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts);
+ if (ret < 1) {
+ /* All events read. */
+ return;
+ }
+
+ /* Determine descriptor and file name. */
+ descriptor = make_number (kev.ident);
+ watch_object = assq_no_quit (descriptor, watch_list);
+ if (CONSP (watch_object))
+ file = XCAR (XCDR (watch_object));
+ else
+ continue;
+
+ /* Determine event actions. */
+ actions = Qnil;
+ if (kev.fflags & NOTE_DELETE)
+ actions = Fcons (Qdelete, actions);
+ if (kev.fflags & NOTE_WRITE) {
+ /* Check, whether this is a directory event. */
+ if (NILP (Fnth (make_number (4), watch_object)))
+ actions = Fcons (Qwrite, actions);
+ else
+ kqueue_compare_dir_list (watch_object);
+ }
+ if (kev.fflags & NOTE_EXTEND)
+ actions = Fcons (Qextend, actions);
+ if (kev.fflags & NOTE_ATTRIB)
+ actions = Fcons (Qattrib, actions);
+ if (kev.fflags & NOTE_LINK)
+ actions = Fcons (Qlink, actions);
+ /* It would be useful to know the target of the rename operation.
+ At this point, it is not possible. Happens only when the upper
+ directory is monitored. */
+ if (kev.fflags & NOTE_RENAME)
+ actions = Fcons (Qrename, actions);
+
+ /* Create the event. */
+ if (! NILP (actions))
+ kqueue_generate_event (watch_object, actions, file, Qnil);
+
+ /* Cancel monitor if file or directory is deleted or renamed. */
+ if (kev.fflags & (NOTE_DELETE | NOTE_RENAME))
+ Fkqueue_rm_watch (descriptor);
+ }
+ return;
+}
+
+DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0,
+ doc: /* Add a watch for filesystem events pertaining to FILE.
+
+This arranges for filesystem events pertaining to FILE to be reported
+to Emacs. Use `kqueue-rm-watch' to cancel the watch.
+
+Returned value is a descriptor for the added watch. If the file cannot be
+watched for some reason, this function signals a `file-notify-error' error.
+
+FLAGS is a list of events to be watched for. It can include the
+following symbols:
+
+ `create' -- FILE was created
+ `delete' -- FILE was deleted
+ `write' -- FILE has changed
+ `extend' -- FILE was extended
+ `attrib' -- a FILE attribute was changed
+ `link' -- a FILE's link count was changed
+ `rename' -- FILE was moved to FILE1
+
+When any event happens, Emacs will call the CALLBACK function passing
+it a single argument EVENT, which is of the form
+
+ (DESCRIPTOR ACTIONS FILE [FILE1])
+
+DESCRIPTOR is the same object as the one returned by this function.
+ACTIONS is a list of events.
+
+FILE is the name of the file whose event is being reported. FILE1
+will be reported only in case of the `rename' event. This is possible
+only when the upper directory of the renamed file is watched. */)
+ (Lisp_Object file, Lisp_Object flags, Lisp_Object callback)
+{
+ Lisp_Object watch_object, dir_list;
+ int fd, oflags;
+ u_short fflags = 0;
+ struct kevent kev;
+
+ /* Check parameters. */
+ CHECK_STRING (file);
+ file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
+ if (NILP (Ffile_exists_p (file)))
+ report_file_error ("File does not exist", file);
+
+ CHECK_LIST (flags);
+
+ if (! FUNCTIONP (callback))
+ wrong_type_argument (Qinvalid_function, callback);
+
+ if (kqueuefd < 0)
+ {
+ /* Create kqueue descriptor. */
+ kqueuefd = kqueue ();
+ if (kqueuefd < 0)
+ report_file_notify_error ("File watching is not available", Qnil);
+
+ /* Start monitoring for possible I/O. */
+ add_read_fd (kqueuefd, kqueue_callback, NULL);
+
+ watch_list = Qnil;
+ }
+
+ /* Open file. */
+ file = ENCODE_FILE (file);
+ oflags = O_NONBLOCK;
+#if O_EVTONLY
+ oflags |= O_EVTONLY;
+#else
+ oflags |= O_RDONLY;
+#endif
+#if O_SYMLINK
+ oflags |= O_SYMLINK;
+#else
+ oflags |= O_NOFOLLOW;
+#endif
+ fd = emacs_open (SSDATA (file), oflags, 0);
+ if (fd == -1)
+ report_file_error ("File cannot be opened", file);
+
+ /* Assemble filter flags */
+ if (! NILP (Fmember (Qdelete, flags))) fflags |= NOTE_DELETE;
+ if (! NILP (Fmember (Qwrite, flags))) fflags |= NOTE_WRITE;
+ if (! NILP (Fmember (Qextend, flags))) fflags |= NOTE_EXTEND;
+ if (! NILP (Fmember (Qattrib, flags))) fflags |= NOTE_ATTRIB;
+ if (! NILP (Fmember (Qlink, flags))) fflags |= NOTE_LINK;
+ if (! NILP (Fmember (Qrename, flags))) fflags |= NOTE_RENAME;
+
+ /* Register event. */
+ EV_SET (&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
+ fflags, 0, NULL);
+
+ if (kevent (kqueuefd, &kev, 1, NULL, 0, NULL) < 0) {
+ emacs_close (fd);
+ report_file_error ("Cannot watch file", file);
+ }
+
+ /* Store watch object in watch list. */
+ Lisp_Object watch_descriptor = make_number (fd);
+ if (NILP (Ffile_directory_p (file)))
+ watch_object = list4 (watch_descriptor, file, flags, callback);
+ else {
+ dir_list = directory_files_internal (file, Qnil, Qnil, Qnil, 1, Qnil);
+ watch_object = list5 (watch_descriptor, file, flags, callback, dir_list);
+ }
+ watch_list = Fcons (watch_object, watch_list);
+
+ return watch_descriptor;
+}
+
+DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch, Skqueue_rm_watch, 1, 1, 0,
+ doc: /* Remove an existing WATCH-DESCRIPTOR.
+
+WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
+ (Lisp_Object watch_descriptor)
+{
+ Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list);
+
+ if (! CONSP (watch_object))
+ xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"),
+ watch_descriptor);
+
+ eassert (INTEGERP (watch_descriptor));
+ int fd = XINT (watch_descriptor);
+ if ( fd >= 0)
+ emacs_close (fd);
+
+ /* Remove watch descriptor from watch list. */
+ watch_list = Fdelq (watch_object, watch_list);
+
+ if (NILP (watch_list) && (kqueuefd >= 0)) {
+ delete_read_fd (kqueuefd);
+ emacs_close (kqueuefd);
+ kqueuefd = -1;
+ }
+
+ return Qt;
+}
+
+DEFUN ("kqueue-valid-p", Fkqueue_valid_p, Skqueue_valid_p, 1, 1, 0,
+ doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
+
+WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
+
+A watch can become invalid if the file or directory it watches is
+deleted, or if the watcher thread exits abnormally for any other
+reason. Removing the watch by calling `kqueue-rm-watch' also makes it
+invalid. */)
+ (Lisp_Object watch_descriptor)
+{
+ return NILP (assq_no_quit (watch_descriptor, watch_list)) ? Qnil : Qt;
+}
+
+\f
+void
+globals_of_kqueue (void)
+{
+ watch_list = Qnil;
+}
+
+void
+syms_of_kqueue (void)
+{
+ defsubr (&Skqueue_add_watch);
+ defsubr (&Skqueue_rm_watch);
+ defsubr (&Skqueue_valid_p);
+
+ /* Event types. */
+ DEFSYM (Qcreate, "create");
+ DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */
+ DEFSYM (Qwrite, "write"); /* NOTE_WRITE */
+ DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */
+ DEFSYM (Qattrib, "attrib"); /* NOTE_ATTRIB */
+ DEFSYM (Qlink, "link"); /* NOTE_LINK */
+ DEFSYM (Qrename, "rename"); /* NOTE_RENAME */
+
+ staticpro (&watch_list);
+
+ Fprovide (intern_c_string ("kqueue"), Qnil);
+}
+
+#endif /* HAVE_KQUEUE */
+
+/* PROBLEMS
+ * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
+ prevents tests on Ubuntu. */
extern void syms_of_fontset (void);
#endif
+/* Defined in inotify.c */
+#ifdef HAVE_INOTIFY
+extern void syms_of_inotify (void);
+#endif
+
+/* Defined in kqueue.c */
+#ifdef HAVE_KQUEUE
+extern void globals_of_kqueue (void);
+extern void syms_of_kqueue (void);
+#endif
+
/* Defined in gfilenotify.c */
#ifdef HAVE_GFILENOTIFY
extern void globals_of_gfilenotify (void);
extern void syms_of_gfilenotify (void);
#endif
-/* Defined in inotify.c */
-#ifdef HAVE_INOTIFY
-extern void syms_of_inotify (void);
-#endif
-
#ifdef HAVE_W32NOTIFY
/* Defined on w32notify.c. */
extern void syms_of_w32notify (void);
(defvar file-notify--test-results nil)
(defvar file-notify--test-event nil)
(defvar file-notify--test-events nil)
-(defvar file-notify--test-expected-events nil)
(defun file-notify--test-timeout ()
"Timeout to wait for arriving events, in seconds."
- (if (file-remote-p temporary-file-directory) 6 3))
+ (cond
+ ((file-remote-p temporary-file-directory) 6)
+ ((string-equal (file-notify--test-library) "w32notify") 20)
+ ((eq system-type 'cygwin) 10)
+ (t 3)))
(defun file-notify--test-cleanup ()
"Cleanup after a test."
file-notify--test-tmpfile1 nil
file-notify--test-desc nil
file-notify--test-results nil
- file-notify--test-events nil
- file-notify--test-expected-events nil)
+ file-notify--test-events nil)
(when file-notify--test-event
(error "file-notify--test-event should not be set but bound dynamically")))
;; Return result.
(cdr file-notify--test-remote-enabled-checked))
+(defun file-notify--test-library ()
+ "The used library for the test, as a string.
+In the remote case, it is the process name which runs on the
+remote host, or nil."
+ (if (null (file-remote-p temporary-file-directory))
+ (symbol-name file-notify--library)
+ (and (consp file-notify--test-remote-enabled-checked)
+ (processp (cdr file-notify--test-remote-enabled-checked))
+ (replace-regexp-in-string
+ "<[[:digit:]]+>\\'" ""
+ (process-name (cdr file-notify--test-remote-enabled-checked))))))
+
(defmacro file-notify--deftest-remote (test docstring)
"Define ert `TEST-remote' for remote files."
(declare (indent 1))
"Test availability of `file-notify'."
(skip-unless (file-notify--test-local-enabled))
;; Report the native library which has been used.
- (if (null (file-remote-p temporary-file-directory))
- (message "Local library: `%s'" file-notify--library)
- (message "Remote command: `%s'"
- (replace-regexp-in-string
- "<[[:digit:]]+>\\'" ""
- (process-name (cdr file-notify--test-remote-enabled-checked)))))
+ (message "Library: `%s'" (file-notify--test-library))
(should
(setq file-notify--test-desc
(file-notify-add-watch temporary-file-directory '(change) 'ignore)))
(file-notify-add-watch
temporary-file-directory '(change attribute-change) 'ignore)))
(file-notify-rm-watch file-notify--test-desc)
- ;; The file does not need to exist, just the upper directory.
+ (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
(should
(setq file-notify--test-desc
(file-notify-add-watch
file-notify--test-tmpfile '(change attribute-change) 'ignore)))
(file-notify-rm-watch file-notify--test-desc)
+ (delete-file file-notify--test-tmpfile)
;; Check error handling.
(should-error (file-notify-add-watch 1 2 3 4)
(should
(or (string-equal (file-notify--event-file-name file-notify--test-event)
file-notify--test-tmpfile)
- (string-equal (directory-file-name
- (file-name-directory
- (file-notify--event-file-name file-notify--test-event)))
- file-notify--test-tmpfile)))
+ (string-equal (file-notify--event-file-name file-notify--test-event)
+ file-notify--test-tmpfile1)
+ (string-equal (file-notify--event-file-name file-notify--test-event)
+ temporary-file-directory)))
;; Check the second file name if exists.
(when (eq (nth 1 file-notify--test-event) 'renamed)
(should
- (string-equal
- (file-notify--event-file1-name file-notify--test-event)
- file-notify--test-tmpfile1))))
+ (or (string-equal (file-notify--event-file1-name file-notify--test-event)
+ file-notify--test-tmpfile1)
+ (string-equal (file-notify--event-file1-name file-notify--test-event)
+ temporary-file-directory)))))
(defun file-notify--test-event-handler (event)
"Run a test over FILE-NOTIFY--TEST-EVENT.
(let* ((file-notify--test-event event)
(result
(ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
- ;; Do not add temporary files, this would confuse the checks.
+ ;; Do not add lock files, this would confuse the checks.
(unless (string-match
(regexp-quote ".#")
(file-notify--event-file-name file-notify--test-event))
(defmacro file-notify--test-with-events (events &rest body)
"Run BODY collecting events and then compare with EVENTS.
-Don't wait longer than timeout seconds for the events to be delivered."
+EVENTS is either a simple list of events, or a list of lists of
+events, which represent different possible results. Don't wait
+longer than timeout seconds for the events to be delivered."
(declare (indent 1))
(let ((outer (make-symbol "outer")))
- `(let ((,outer file-notify--test-events))
- (setq file-notify--test-expected-events
- (append file-notify--test-expected-events ,events))
+ `(let* ((,outer file-notify--test-events)
+ (events (if (consp (car ,events)) ,events (list ,events)))
+ (max-length (apply 'max (mapcar 'length events)))
+ create-lockfiles result)
+ ;; Flush pending events.
+ (file-notify--wait-for-events
+ (file-notify--test-timeout)
+ (input-pending-p))
(let (file-notify--test-events)
,@body
(file-notify--wait-for-events
- (file-notify--test-timeout)
- (= (length ,events) (length file-notify--test-events)))
- (should (equal ,events (mapcar #'cadr file-notify--test-events)))
+ ;; More events need more time. Use some fudge factor.
+ (* (ceiling max-length 100) (file-notify--test-timeout))
+ (= max-length (length file-notify--test-events)))
+ ;; One of the possible results shall match.
+ (should
+ (dolist (elt events result)
+ (setq result
+ (or result
+ (equal elt (mapcar #'cadr file-notify--test-events))))))
(setq ,outer (append ,outer file-notify--test-events)))
(setq file-notify--test-events ,outer))))
(ert-deftest file-notify-test02-events ()
"Check file creation/change/removal notifications."
(skip-unless (file-notify--test-local-enabled))
- ;; Under cygwin there are so bad timings that it doesn't make sense to test.
- (skip-unless (not (eq system-type 'cygwin)))
-
- (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
- file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
(unwind-protect
(progn
- ;; Check creation, change and deletion.
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) 'file-notify--test-event-handler))
- (file-notify--test-with-events '(created changed deleted)
+ ;; Check file creation, change and deletion. It doesn't work
+ ;; for cygwin and kqueue, because we don't use an implicit
+ ;; directory monitor (kqueue), or the timings are too bad (cygwin).
+ (unless (or (eq system-type 'cygwin)
+ (string-equal (file-notify--test-library) "kqueue"))
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) 'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ (t '(created changed deleted stopped)))
+ (write-region
+ "another text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
+ (delete-file file-notify--test-tmpfile))
+ ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
+ (let (file-notify--test-events)
+ (file-notify-rm-watch file-notify--test-desc)))
+
+ ;; Check file change and deletion.
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+ (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) 'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ ;; inotify and kqueue raise just one `changed' event.
+ ((or (string-equal "inotify" (file-notify--test-library))
+ (string-equal "kqueue" (file-notify--test-library)))
+ '(changed deleted stopped))
+ ;; gfilenotify raises one or two `changed' events
+ ;; randomly, no chance to test. So we accept both cases.
+ ((string-equal "gfilenotify" (file-notify--test-library))
+ '((changed deleted stopped)
+ (changed changed deleted stopped)))
+ (t '(changed changed deleted stopped)))
+ (read-event nil nil 0.1)
(write-region
- "any text" nil file-notify--test-tmpfile nil 'no-message)
+ "another text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc))
- ;; Check creation, change and deletion. There must be a
- ;; `stopped' event when deleting the directory. It doesn't
- ;; work for w32notify.
- (unless (eq file-notify--library 'w32notify)
- (make-directory file-notify--test-tmpfile)
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) 'file-notify--test-event-handler))
+ ;; Check file creation, change and deletion when watching a
+ ;; directory. There must be a `stopped' event when deleting
+ ;; the directory.
+ (let ((temporary-file-directory
+ (make-temp-file "file-notify-test-parent" t)))
+ (should
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-desc
+ (file-notify-add-watch
+ temporary-file-directory
+ '(change) 'file-notify--test-event-handler)))
(file-notify--test-with-events
- ;; There are two `deleted' events, for the file and for
- ;; the directory.
- '(created changed deleted deleted stopped)
+ (cond
+ ;; w32notify does raise a `stopped' event when a
+ ;; watched directory is deleted.
+ ((string-equal (file-notify--test-library) "w32notify")
+ '(created changed deleted))
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ ;; There are two `deleted' events, for the file and for
+ ;; the directory. Except for kqueue.
+ ((string-equal (file-notify--test-library) "kqueue")
+ '(created changed deleted stopped))
+ (t '(created changed deleted deleted stopped)))
+ (read-event nil nil 0.1)
(write-region
- "any text" nil (expand-file-name "foo" file-notify--test-tmpfile)
- nil 'no-message)
- (delete-directory file-notify--test-tmpfile 'recursive))
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
+ (delete-directory temporary-file-directory 'recursive))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc)))
- ;; Check copy.
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) 'file-notify--test-event-handler))
- (should file-notify--test-desc)
- (file-notify--test-with-events
- ;; w32notify does not distinguish between `changed' and
- ;; `attribute-changed'.
- (if (eq file-notify--library 'w32notify)
- '(created changed changed deleted)
- '(created changed deleted))
- (write-region
- "any text" nil file-notify--test-tmpfile nil 'no-message)
- (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
- ;; The next two events shall not be visible.
- (set-file-modes file-notify--test-tmpfile 000)
- (read-event nil nil 0.1) ; In order to distinguish the events.
- (set-file-times file-notify--test-tmpfile '(0 0))
- (delete-file file-notify--test-tmpfile)
- (delete-file file-notify--test-tmpfile1))
- ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
- (let (file-notify--test-events)
- (file-notify-rm-watch file-notify--test-desc))
+ ;; Check copy of files inside a directory.
+ (let ((temporary-file-directory
+ (make-temp-file "file-notify-test-parent" t)))
+ (should
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
+ file-notify--test-desc
+ (file-notify-add-watch
+ temporary-file-directory
+ '(change) 'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; w32notify does not distinguish between `changed' and
+ ;; `attribute-changed'.
+ ((string-equal (file-notify--test-library) "w32notify")
+ '(created changed created changed changed changed changed
+ deleted deleted))
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ ;; There are three `deleted' events, for two files and
+ ;; for the directory. Except for kqueue.
+ ((string-equal (file-notify--test-library) "kqueue")
+ '(created changed created changed deleted stopped))
+ (t '(created changed created changed
+ deleted deleted deleted stopped)))
+ (read-event nil nil 0.1)
+ (write-region
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
+ (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
+ ;; The next two events shall not be visible.
+ (read-event nil nil 0.1)
+ (set-file-modes file-notify--test-tmpfile 000)
+ (read-event nil nil 0.1)
+ (set-file-times file-notify--test-tmpfile '(0 0))
+ (read-event nil nil 0.1)
+ (delete-directory temporary-file-directory 'recursive))
+ ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
+ (let (file-notify--test-events)
+ (file-notify-rm-watch file-notify--test-desc)))
- ;; Check rename.
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) 'file-notify--test-event-handler))
- (should file-notify--test-desc)
- (file-notify--test-with-events '(created changed renamed)
- (write-region
- "any text" nil file-notify--test-tmpfile nil 'no-message)
- (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
- ;; After the rename, we won't get events anymore.
- (delete-file file-notify--test-tmpfile1))
- ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
- (let (file-notify--test-events)
- (file-notify-rm-watch file-notify--test-desc))
+ ;; Check rename of files inside a directory.
+ (let ((temporary-file-directory
+ (make-temp-file "file-notify-test-parent" t)))
+ (should
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
+ file-notify--test-desc
+ (file-notify-add-watch
+ temporary-file-directory
+ '(change) 'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; w32notify does not distinguish between `changed' and
+ ;; `attribute-changed'.
+ ((string-equal (file-notify--test-library) "w32notify")
+ '(created changed renamed deleted))
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ ;; There are two `deleted' events, for the file and for
+ ;; the directory. Except for kqueue.
+ ((string-equal (file-notify--test-library) "kqueue")
+ '(created changed renamed deleted stopped))
+ (t '(created changed renamed deleted deleted stopped)))
+ (read-event nil nil 0.1)
+ (write-region
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
+ (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
+ ;; After the rename, we won't get events anymore.
+ (read-event nil nil 0.1)
+ (delete-directory temporary-file-directory 'recursive))
+ ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
+ (let (file-notify--test-events)
+ (file-notify-rm-watch file-notify--test-desc)))
- ;; Check attribute change. It doesn't work for w32notify.
- (unless (eq file-notify--library 'w32notify)
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(attribute-change) 'file-notify--test-event-handler))
- (file-notify--test-with-events
- (if (file-remote-p temporary-file-directory)
- ;; In the remote case, `write-region' raises also an
- ;; `attribute-changed' event.
- '(attribute-changed attribute-changed attribute-changed)
- '(attribute-changed attribute-changed))
- ;; We must use short delays between the operations.
- ;; Otherwise, not all events arrive us in the remote case.
- (write-region
- "any text" nil file-notify--test-tmpfile nil 'no-message)
- (read-event nil nil 0.1)
- (set-file-modes file-notify--test-tmpfile 000)
- (read-event nil nil 0.1)
- (set-file-times file-notify--test-tmpfile '(0 0))
- (read-event nil nil 0.1)
- (delete-file file-notify--test-tmpfile))
+ ;; Check attribute change. Does not work for cygwin.
+ (unless (eq system-type 'cygwin)
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+ (write-region
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(attribute-change) 'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; w32notify does not distinguish between `changed' and
+ ;; `attribute-changed'.
+ ((string-equal (file-notify--test-library) "w32notify")
+ '(changed changed changed changed))
+ ;; For kqueue and in the remote case, `write-region'
+ ;; raises also an `attribute-changed' event.
+ ((or (string-equal (file-notify--test-library) "kqueue")
+ (file-remote-p temporary-file-directory))
+ '(attribute-changed attribute-changed attribute-changed))
+ (t '(attribute-changed attribute-changed)))
+ (read-event nil nil 0.1)
+ (write-region
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
+ (set-file-modes file-notify--test-tmpfile 000)
+ (read-event nil nil 0.1)
+ (set-file-times file-notify--test-tmpfile '(0 0))
+ (read-event nil nil 0.1)
+ (delete-file file-notify--test-tmpfile))
;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
(let (file-notify--test-events)
(file-notify-rm-watch file-notify--test-desc)))
;; Check the global sequence again just to make sure that
;; `file-notify--test-events' has been set correctly.
- (should (equal (mapcar #'cadr file-notify--test-events)
- file-notify--test-expected-events))
(should file-notify--test-results)
(dolist (result file-notify--test-results)
(when (ert-test-failed-p result)
(should (string-match "another text" (buffer-string)))
;; Stop file notification. Autorevert shall still work via polling.
- (file-notify-rm-watch auto-revert-notify-watch-descriptor)
- (file-notify--wait-for-events
- timeout (null auto-revert-use-notify))
- (should-not auto-revert-use-notify)
- (should-not auto-revert-notify-watch-descriptor)
-
- ;; Modify file. We wait for two seconds, in order to have
- ;; another timestamp. One second seems to be too short.
- (with-current-buffer (get-buffer-create "*Messages*")
- (narrow-to-region (point-max) (point-max)))
- (sleep-for 2)
- (write-region
- "foo bla" nil file-notify--test-tmpfile nil 'no-message)
-
- ;; Check, that the buffer has been reverted.
- (with-current-buffer (get-buffer-create "*Messages*")
+ ;; It doesn't work for `w32notify'.
+ (unless (string-equal (file-notify--test-library) "w32notify")
+ (file-notify-rm-watch auto-revert-notify-watch-descriptor)
(file-notify--wait-for-events
- timeout
- (string-match
- (format-message "Reverting buffer `%s'." (buffer-name buf))
- (buffer-string))))
- (should (string-match "foo bla" (buffer-string)))))
+ timeout (null auto-revert-use-notify))
+ (should-not auto-revert-use-notify)
+ (should-not auto-revert-notify-watch-descriptor)
+
+ ;; Modify file. We wait for two seconds, in order to
+ ;; have another timestamp. One second seems to be too
+ ;; short.
+ (with-current-buffer (get-buffer-create "*Messages*")
+ (narrow-to-region (point-max) (point-max)))
+ (sleep-for 2)
+ (write-region
+ "foo bla" nil file-notify--test-tmpfile nil 'no-message)
+
+ ;; Check, that the buffer has been reverted.
+ (with-current-buffer (get-buffer-create "*Messages*")
+ (file-notify--wait-for-events
+ timeout
+ (string-match
+ (format-message "Reverting buffer `%s'." (buffer-name buf))
+ (buffer-string))))
+ (should (string-match "foo bla" (buffer-string))))))
;; Cleanup.
(with-current-buffer "*Messages*" (widen))
(ert-deftest file-notify-test04-file-validity ()
"Check `file-notify-valid-p' for files."
(skip-unless (file-notify--test-local-enabled))
- ;; Under cygwin there are so bad timings that it doesn't make sense to test.
- (skip-unless (not (eq system-type 'cygwin)))
(unwind-protect
(progn
- (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
- file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) #'file-notify--test-event-handler))
- (file-notify--test-with-events '(created changed deleted)
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+ (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) #'file-notify--test-event-handler)))
+ (should (file-notify-valid-p file-notify--test-desc))
+ ;; After calling `file-notify-rm-watch', the descriptor is not
+ ;; valid anymore.
+ (file-notify-rm-watch file-notify--test-desc)
+ (should-not (file-notify-valid-p file-notify--test-desc))
+ (delete-file file-notify--test-tmpfile))
+
+ ;; Cleanup.
+ (file-notify--test-cleanup))
+
+ (unwind-protect
+ (progn
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+ (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) #'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ ;; inotify and kqueue raise just one `changed' event.
+ ((or (string-equal "inotify" (file-notify--test-library))
+ (string-equal "kqueue" (file-notify--test-library)))
+ '(changed deleted stopped))
+ ;; gfilenotify raises one or two `changed' events
+ ;; randomly, no chance to test. So we accept both cases.
+ ((string-equal "gfilenotify" (file-notify--test-library))
+ '((changed deleted stopped)
+ (changed changed deleted stopped)))
+ (t '(changed changed deleted stopped)))
(should (file-notify-valid-p file-notify--test-desc))
+ (read-event nil nil 0.1)
(write-region
- "any text" nil file-notify--test-tmpfile nil 'no-message)
+ "another text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
(delete-file file-notify--test-tmpfile))
- ;; After deleting the file, the descriptor is still valid.
- (should (file-notify-valid-p file-notify--test-desc))
- ;; After removing the watch, the descriptor must not be valid
- ;; anymore.
- (file-notify-rm-watch file-notify--test-desc)
- (should-not (file-notify-valid-p file-notify--test-desc)))
+ ;; After deleting the file, the descriptor is not valid anymore.
+ (should-not (file-notify-valid-p file-notify--test-desc))
+ (file-notify-rm-watch file-notify--test-desc))
;; Cleanup.
(file-notify--test-cleanup))
(unwind-protect
- ;; The batch-mode operation of w32notify is fragile (there's no
- ;; input threads to send the message to).
- ;(unless (and noninteractive (eq file-notify--library 'w32notify))
- (unless (eq file-notify--library 'w32notify)
- (let ((temporary-file-directory
+ ;; w32notify does not send a `stopped' event when deleting a
+ ;; directory. The test does not work, therefore.
+ (unless (string-equal (file-notify--test-library) "w32notify")
+ (let ((temporary-file-directory
(make-temp-file "file-notify-test-parent" t)))
- (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
- file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) #'file-notify--test-event-handler))
- (file-notify--test-with-events '(created changed deleted stopped)
- (should (file-notify-valid-p file-notify--test-desc))
- (write-region
- "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (should
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+ file-notify--test-desc
+ (file-notify-add-watch
+ temporary-file-directory
+ '(change) #'file-notify--test-event-handler)))
+ (file-notify--test-with-events
+ (cond
+ ;; cygwin recognizes only `deleted' and `stopped' events.
+ ((eq system-type 'cygwin)
+ '(deleted stopped))
+ ;; There are two `deleted' events, for the file and for
+ ;; the directory. Except for kqueue.
+ ((string-equal (file-notify--test-library) "kqueue")
+ '(created changed deleted stopped))
+ (t '(created changed deleted deleted stopped)))
+ (should (file-notify-valid-p file-notify--test-desc))
+ (read-event nil nil 0.1)
+ (write-region
+ "any text" nil file-notify--test-tmpfile nil 'no-message)
+ (read-event nil nil 0.1)
(delete-directory temporary-file-directory t))
- ;; After deleting the parent directory, the descriptor must
- ;; not be valid anymore.
- (should-not (file-notify-valid-p file-notify--test-desc))))
+ ;; After deleting the parent directory, the descriptor must
+ ;; not be valid anymore.
+ (should-not (file-notify-valid-p file-notify--test-desc))))
;; Cleanup.
(file-notify--test-cleanup)))
(setq file-notify--test-tmpfile
(file-name-as-directory (file-notify--test-make-temp-name)))
(make-directory file-notify--test-tmpfile)
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) #'file-notify--test-event-handler))
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) #'file-notify--test-event-handler)))
(should (file-notify-valid-p file-notify--test-desc))
;; After removing the watch, the descriptor must not be valid
;; anymore.
(unwind-protect
;; The batch-mode operation of w32notify is fragile (there's no
;; input threads to send the message to).
- (unless (and noninteractive (eq file-notify--library 'w32notify))
+ (unless (and noninteractive
+ (string-equal (file-notify--test-library) "w32notify"))
(setq file-notify--test-tmpfile
(file-name-as-directory (file-notify--test-make-temp-name)))
(make-directory file-notify--test-tmpfile)
- (setq file-notify--test-desc
- (file-notify-add-watch
- file-notify--test-tmpfile
- '(change) #'file-notify--test-event-handler))
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) #'file-notify--test-event-handler)))
(should (file-notify-valid-p file-notify--test-desc))
;; After deleting the directory, the descriptor must not be
;; valid anymore.
(delete-directory file-notify--test-tmpfile t)
(file-notify--wait-for-events
- (file-notify--test-timeout)
+ (file-notify--test-timeout)
(not (file-notify-valid-p file-notify--test-desc)))
(should-not (file-notify-valid-p file-notify--test-desc)))
(file-notify--deftest-remote file-notify-test05-dir-validity
"Check `file-notify-valid-p' via file notification for remote directories.")
+(ert-deftest file-notify-test06-many-events ()
+ "Check that events are not dropped."
+ :tags '(:expensive-test)
+ (skip-unless (file-notify--test-local-enabled))
+ ;; Under cygwin events arrive in random order. Impossible to define a test.
+ (skip-unless (not (eq system-type 'cygwin)))
+
+ (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+ (make-directory file-notify--test-tmpfile)
+ (should
+ (setq file-notify--test-desc
+ (file-notify-add-watch
+ file-notify--test-tmpfile
+ '(change) 'file-notify--test-event-handler)))
+ (unwind-protect
+ (let ((n 1000)
+ source-file-list target-file-list
+ (default-directory file-notify--test-tmpfile))
+ (dotimes (i n)
+ ;; It matters which direction we rename, at least for
+ ;; kqueue. This backend parses directories in alphabetic
+ ;; order (x%d before y%d). So we rename both directions.
+ (if (zerop (mod i 2))
+ (progn
+ (push (expand-file-name (format "x%d" i)) source-file-list)
+ (push (expand-file-name (format "y%d" i)) target-file-list))
+ (push (expand-file-name (format "y%d" i)) source-file-list)
+ (push (expand-file-name (format "x%d" i)) target-file-list)))
+ (file-notify--test-with-events (make-list (+ n n) 'created)
+ (let ((source-file-list source-file-list)
+ (target-file-list target-file-list))
+ (while (and source-file-list target-file-list)
+ (read-event nil nil 0.1)
+ (write-region "" nil (pop source-file-list) nil 'no-message)
+ (read-event nil nil 0.1)
+ (write-region "" nil (pop target-file-list) nil 'no-message))))
+ (file-notify--test-with-events
+ (cond
+ ;; w32notify fires both `deleted' and `renamed' events.
+ ((string-equal (file-notify--test-library) "w32notify")
+ (let (r)
+ (dotimes (_i n r)
+ (setq r (append '(deleted renamed) r)))))
+ (t (make-list n 'renamed)))
+ (let ((source-file-list source-file-list)
+ (target-file-list target-file-list))
+ (while (and source-file-list target-file-list)
+ (rename-file (pop source-file-list) (pop target-file-list) t))))
+ (file-notify--test-with-events (make-list n 'deleted)
+ (dolist (file target-file-list)
+ (delete-file file))))
+ (file-notify--test-cleanup)))
+
+(file-notify--deftest-remote file-notify-test06-many-events
+ "Check that events are not dropped for remote directories.")
+
(defun file-notify-test-all (&optional interactive)
"Run all tests for \\[file-notify]."
(interactive "p")
;; TODO:
;; * For w32notify, no stopped events arrive when a directory is removed.
-;; * Try to handle arriving events under cygwin reliably.
+;; * Check, why cygwin recognizes only `deleted' and `stopped' events.
(provide 'file-notify-tests)
;;; file-notify-tests.el ends here