aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Albinus2016-01-22 19:56:09 +0100
committerMichael Albinus2016-01-22 19:56:09 +0100
commit7bf54d01159eb09bae3c9cd86f2af0812d9afdf6 (patch)
treef00e00ba1ed5a492ec94faf8d07a6ca2f857a9c5
parentf7dc6d8b5bb318e02a4016d93f8b34de0716f4dc (diff)
downloademacs-7bf54d01159eb09bae3c9cd86f2af0812d9afdf6.tar.gz
emacs-7bf54d01159eb09bae3c9cd86f2af0812d9afdf6.zip
Backport kqueue integration from master
* 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.
-rw-r--r--configure.ac61
-rw-r--r--doc/lispref/os.texi41
-rw-r--r--etc/NEWS7
-rw-r--r--lisp/filenotify.el101
-rw-r--r--src/Makefile.in11
-rw-r--r--src/emacs.c16
-rw-r--r--src/inotify.c9
-rw-r--r--src/keyboard.c4
-rw-r--r--src/kqueue.c521
-rw-r--r--src/lisp.h16
-rw-r--r--test/automated/file-notify-tests.el585
11 files changed, 1092 insertions, 280 deletions
diff --git a/configure.ac b/configure.ac
index b5e6b77c713..76193fae6dd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -356,17 +356,18 @@ OPTION_DEFAULT_ON([zlib],[don't compile with zlib decompression support])
356OPTION_DEFAULT_OFF([modules],[compile with dynamic modules support]) 356OPTION_DEFAULT_OFF([modules],[compile with dynamic modules support])
357 357
358AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB], 358AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
359 [use a file notification library (LIB one of: yes, gfile, inotify, w32, no)])], 359 [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])],
360 [ case "${withval}" in 360 [ case "${withval}" in
361 y | ye | yes ) val=yes ;; 361 y | ye | yes ) val=yes ;;
362 n | no ) val=no ;; 362 n | no ) val=no ;;
363 g | gf | gfi | gfil | gfile ) val=gfile ;;
364 i | in | ino | inot | inoti | inotif | inotify ) val=inotify ;; 363 i | in | ino | inot | inoti | inotif | inotify ) val=inotify ;;
364 k | kq | kqu | kque | kqueu | kqueue ) val=kqueue ;;
365 g | gf | gfi | gfil | gfile ) val=gfile ;;
365 w | w3 | w32 ) val=w32 ;; 366 w | w3 | w32 ) val=w32 ;;
366 * ) AC_MSG_ERROR(['--with-file-notification=$withval' is invalid; 367 * ) AC_MSG_ERROR(['--with-file-notification=$withval' is invalid;
367this option's value should be 'yes', 'no', 'gfile', 'inotify' or 'w32'. 368this option's value should be 'yes', 'no', 'inotify', 'kqueue', 'gfile' or 'w32'.
368'yes' is a synonym for 'w32' on MS-Windows, for 'no' on Nextstep, 369'yes' is a synonym for 'w32' on MS-Windows, for 'no' on Nextstep,
369otherwise for the first of 'inotify' or 'gfile' that is usable.]) 370otherwise for the first of 'inotify', 'kqueue' or 'gfile' that is usable.])
370 ;; 371 ;;
371 esac 372 esac
372 with_file_notification=$val 373 with_file_notification=$val
@@ -2712,12 +2713,6 @@ AC_SUBST(LIBGNUTLS_CFLAGS)
2712NOTIFY_OBJ= 2713NOTIFY_OBJ=
2713NOTIFY_SUMMARY=no 2714NOTIFY_SUMMARY=no
2714 2715
2715dnl FIXME? Don't auto-detect on NS, but do allow someone to specify
2716dnl a particular library. This doesn't make much sense?
2717if test "${HAVE_NS}" = yes && test ${with_file_notification} = yes; then
2718 with_file_notification=no
2719fi
2720
2721dnl MS Windows native file monitor is available for mingw32 only. 2716dnl MS Windows native file monitor is available for mingw32 only.
2722case $with_file_notification,$opsys in 2717case $with_file_notification,$opsys in
2723 w32,cygwin) 2718 w32,cygwin)
@@ -2748,16 +2743,44 @@ case $with_file_notification,$NOTIFY_OBJ in
2748 fi ;; 2743 fi ;;
2749esac 2744esac
2750 2745
2746dnl kqueue is available on BSD-like systems.
2747case $with_file_notification,$NOTIFY_OBJ in
2748 kqueue,* | yes,)
2749 EMACS_CHECK_MODULES([KQUEUE], [libkqueue])
2750 if test "$HAVE_KQUEUE" = "yes"; then
2751 AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.])
2752 CPPFLAGS="$CPPFLAGS -I/usr/include/kqueue"
2753 NOTIFY_CFLAGS=$KQUEUE_CFLAGS
2754 NOTIFY_LIBS=$KQUEUE_LIBS
2755 NOTIFY_OBJ=kqueue.o
2756 NOTIFY_SUMMARY="yes -lkqueue"
2757 else
2758 AC_SEARCH_LIBS(kqueue, [])
2759 if test "$ac_cv_search_kqueue" != no; then
2760 AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.])
2761 NOTIFY_OBJ=kqueue.o
2762 NOTIFY_SUMMARY="yes (kqueue)"
2763 fi
2764 fi ;;
2765esac
2766
2751dnl g_file_monitor exists since glib 2.18. G_FILE_MONITOR_EVENT_MOVED 2767dnl g_file_monitor exists since glib 2.18. G_FILE_MONITOR_EVENT_MOVED
2752dnl has been added in glib 2.24. It has been tested under 2768dnl has been added in glib 2.24. It has been tested under
2753dnl GNU/Linux only. 2769dnl GNU/Linux only.
2754case $with_file_notification,$NOTIFY_OBJ in 2770case $with_file_notification,$NOTIFY_OBJ in
2755 gfile,* | yes,) 2771 gfile,* | yes,)
2756 EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24]) 2772 if test "${HAVE_NS}" = yes; then
2757 if test "$HAVE_GFILENOTIFY" = "yes"; then 2773 AC_MSG_ERROR(['--with-file-notification=gfile' is not supported in NextStep builds.
2758 AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.]) 2774Consider kqueue instead.])
2759 NOTIFY_OBJ=gfilenotify.o 2775 else
2760 NOTIFY_SUMMARY="yes -lgio (gfile)" 2776 EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24])
2777 if test "$HAVE_GFILENOTIFY" = "yes"; then
2778 AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.])
2779 NOTIFY_CFLAGS=$GFILENOTIFY_CFLAGS
2780 NOTIFY_LIBS=$GFILENOTIFY_LIBS
2781 NOTIFY_OBJ=gfilenotify.o
2782 NOTIFY_SUMMARY="yes -lgio (gfile)"
2783 fi
2761 fi ;; 2784 fi ;;
2762esac 2785esac
2763 2786
@@ -2769,9 +2792,9 @@ esac
2769if test -n "$NOTIFY_OBJ"; then 2792if test -n "$NOTIFY_OBJ"; then
2770 AC_DEFINE(USE_FILE_NOTIFY, 1, [Define to 1 if using file notifications.]) 2793 AC_DEFINE(USE_FILE_NOTIFY, 1, [Define to 1 if using file notifications.])
2771fi 2794fi
2795AC_SUBST(NOTIFY_CFLAGS)
2796AC_SUBST(NOTIFY_LIBS)
2772AC_SUBST(NOTIFY_OBJ) 2797AC_SUBST(NOTIFY_OBJ)
2773AC_SUBST(GFILENOTIFY_CFLAGS)
2774AC_SUBST(GFILENOTIFY_LIBS)
2775 2798
2776dnl Do not put whitespace before the #include statements below. 2799dnl Do not put whitespace before the #include statements below.
2777dnl Older compilers (eg sunos4 cc) choke on it. 2800dnl Older compilers (eg sunos4 cc) choke on it.
@@ -4141,8 +4164,8 @@ OLDCFLAGS="$CFLAGS"
4141OLDLIBS="$LIBS" 4164OLDLIBS="$LIBS"
4142CFLAGS="$CFLAGS $GTK_CFLAGS $RSVG_CFLAGS $DBUS_CFLAGS $SETTINGS_CFLAGS" 4165CFLAGS="$CFLAGS $GTK_CFLAGS $RSVG_CFLAGS $DBUS_CFLAGS $SETTINGS_CFLAGS"
4143LIBS="$LIBS $GTK_LIBS $RSVG_LIBS $DBUS_LIBS $SETTINGS_LIBS" 4166LIBS="$LIBS $GTK_LIBS $RSVG_LIBS $DBUS_LIBS $SETTINGS_LIBS"
4144CFLAGS="$CFLAGS $GFILENOTIFY_CFLAGS $CAIRO_CFLAGS" 4167CFLAGS="$CFLAGS $NOTIFY_CFLAGS $CAIRO_CFLAGS"
4145LIBS="$LIBS $GFILENOTIFY_LIBS $CAIRO_LIBS" 4168LIBS="$LIBS $NOTIFY_LIBS $CAIRO_LIBS"
4146AC_MSG_CHECKING([whether GLib is linked in]) 4169AC_MSG_CHECKING([whether GLib is linked in])
4147AC_LINK_IFELSE([AC_LANG_PROGRAM( 4170AC_LINK_IFELSE([AC_LANG_PROGRAM(
4148 [[#include <glib.h> 4171 [[#include <glib.h>
diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 7206cd4ef86..8e3720eb947 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -2653,9 +2653,9 @@ This function removes the tray notification given by its unique
2653 2653
2654Several operating systems support watching of filesystems for changes 2654Several operating systems support watching of filesystems for changes
2655of files. If configured properly, Emacs links a respective library 2655of files. If configured properly, Emacs links a respective library
2656like @file{gfilenotify}, @file{inotify}, or @file{w32notify} 2656like @file{inotify}, @file{kqueue}, @file{gfilenotify}, or
2657statically. These libraries enable watching of filesystems on the 2657@file{w32notify} statically. These libraries enable watching of
2658local machine. 2658filesystems on the local machine.
2659 2659
2660It is also possible to watch filesystems on remote machines, 2660It is also possible to watch filesystems on remote machines,
2661@pxref{Remote Files,, Remote Files, emacs, The GNU Emacs Manual} 2661@pxref{Remote Files,, Remote Files, emacs, The GNU Emacs Manual}
@@ -2726,7 +2726,8 @@ watching @var{file} has been stopped
2726Note that the @file{w32notify} library does not report 2726Note that the @file{w32notify} library does not report
2727@code{attribute-changed} events. When some file's attribute, like 2727@code{attribute-changed} events. When some file's attribute, like
2728permissions or modification time, has changed, this library reports a 2728permissions or modification time, has changed, this library reports a
2729@code{changed} event. 2729@code{changed} event. Likewise, the @file{kqueue} library does not
2730report reliably file attribute changes when watching a directory.
2730 2731
2731The @code{stopped} event reports, that watching the file has been 2732The @code{stopped} event reports, that watching the file has been
2732stopped. This could be because @code{file-notify-rm-watch} was called 2733stopped. This could be because @code{file-notify-rm-watch} was called
@@ -2765,7 +2766,7 @@ being reported. For example:
2765@group 2766@group
2766(write-region "bla" nil "/tmp/foo") 2767(write-region "bla" nil "/tmp/foo")
2767 @result{} Event (35025468 created "/tmp/.#foo") 2768 @result{} Event (35025468 created "/tmp/.#foo")
2768 Event (35025468 changed "/tmp/foo") [2 times] 2769 Event (35025468 changed "/tmp/foo")
2769 Event (35025468 deleted "/tmp/.#foo") 2770 Event (35025468 deleted "/tmp/.#foo")
2770@end group 2771@end group
2771 2772
@@ -2811,14 +2812,14 @@ also makes it invalid.
2811@example 2812@example
2812@group 2813@group
2813(make-directory "/tmp/foo") 2814(make-directory "/tmp/foo")
2814 @result{} nil 2815 @result{} Event (35025468 created "/tmp/foo")
2815@end group 2816@end group
2816 2817
2817@group 2818@group
2818(setq desc 2819(setq desc
2819 (file-notify-add-watch 2820 (file-notify-add-watch
2820 "/tmp/foo" '(change) 'my-notify-callback)) 2821 "/tmp/foo" '(change) 'my-notify-callback))
2821 @result{} 35025468 2822 @result{} 11359632
2822@end group 2823@end group
2823 2824
2824@group 2825@group
@@ -2828,32 +2829,34 @@ also makes it invalid.
2828 2829
2829@group 2830@group
2830(write-region "bla" nil "/tmp/foo/bla") 2831(write-region "bla" nil "/tmp/foo/bla")
2831 @result{} Event (35025468 created "/tmp/foo/.#bla") 2832 @result{} Event (11359632 created "/tmp/foo/.#bla")
2832 Event (35025468 created "/tmp/foo/bla") 2833 Event (11359632 created "/tmp/foo/bla")
2833 Event (35025468 changed "/tmp/foo/bla") 2834 Event (11359632 changed "/tmp/foo/bla")
2834 Event (35025468 changed "/tmp/foo/.#bla") 2835 Event (11359632 deleted "/tmp/foo/.#bla")
2835@end group 2836@end group
2836 2837
2837@group 2838@group
2838;; Deleting a file in the directory doesn't invalidate the watch. 2839;; Deleting a file in the directory doesn't invalidate the watch.
2839(delete-file "/tmp/foo/bla") 2840(delete-file "/tmp/foo/bla")
2840 @result{} Event (35025468 deleted "/tmp/foo/bla") 2841 @result{} Event (11359632 deleted "/tmp/foo/bla")
2841@end group 2842@end group
2842 2843
2843@group 2844@group
2844(write-region "bla" nil "/tmp/foo/bla") 2845(write-region "bla" nil "/tmp/foo/bla")
2845 @result{} Event (35025468 created "/tmp/foo/.#bla") 2846 @result{} Event (11359632 created "/tmp/foo/.#bla")
2846 Event (35025468 created "/tmp/foo/bla") 2847 Event (11359632 created "/tmp/foo/bla")
2847 Event (35025468 changed "/tmp/foo/bla") 2848 Event (11359632 changed "/tmp/foo/bla")
2848 Event (35025468 changed "/tmp/foo/.#bla") 2849 Event (11359632 deleted "/tmp/foo/.#bla")
2849@end group 2850@end group
2850 2851
2851@group 2852@group
2852;; Deleting the directory invalidates the watch. 2853;; Deleting the directory invalidates the watch.
2854;; Events arrive for different watch descriptors.
2853(delete-directory "/tmp/foo" 'recursive) 2855(delete-directory "/tmp/foo" 'recursive)
2854 @result{} Event (35025468 deleted "/tmp/foo/bla") 2856 @result{} Event (35025468 deleted "/tmp/foo")
2855 Event (35025468 deleted "/tmp/foo") 2857 Event (11359632 deleted "/tmp/foo/bla")
2856 Event (35025468 stopped "/tmp/foo") 2858 Event (11359632 deleted "/tmp/foo")
2859 Event (11359632 stopped "/tmp/foo")
2857@end group 2860@end group
2858 2861
2859@group 2862@group
diff --git a/etc/NEWS b/etc/NEWS
index 06b32ce9ccc..4e47c5882f9 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -64,6 +64,10 @@ If gnustep-config is not available, the old heuristics are used.
64unless gfile is explicitly requested via --with-file-notification='gfile'. 64unless gfile is explicitly requested via --with-file-notification='gfile'.
65 65
66--- 66---
67** 'configure' detects the kqueue file notification library on *BSD
68and Mac OS X machines.
69
70---
67** The configure option '--with-pkg-config-prog' has been removed. 71** The configure option '--with-pkg-config-prog' has been removed.
68Use './configure PKG_CONFIG=/full/name/of/pkg-config' if you need to. 72Use './configure PKG_CONFIG=/full/name/of/pkg-config' if you need to.
69 73
@@ -1121,6 +1125,9 @@ notifications, if Emacs is compiled with file notification support.
1121** File Notifications 1125** File Notifications
1122 1126
1123+++ 1127+++
1128*** The kqueue library is integrated for *BSD and Mac OS X machines.
1129
1130+++
1124*** The new event `stopped' signals, that a file notification watch is 1131*** The new event `stopped' signals, that a file notification watch is
1125not active any longer. 1132not active any longer.
1126 1133
diff --git a/lisp/filenotify.el b/lisp/filenotify.el
index ebf4dd277c8..faa801ee6e7 100644
--- a/lisp/filenotify.el
+++ b/lisp/filenotify.el
@@ -22,15 +22,16 @@
22;;; Commentary 22;;; Commentary
23 23
24;; This package is an abstraction layer from the different low-level 24;; This package is an abstraction layer from the different low-level
25;; file notification packages `gfilenotify', `inotify' and 25;; file notification packages `inotify', `kqueue', `gfilenotify' and
26;; `w32notify'. 26;; `w32notify'.
27 27
28;;; Code: 28;;; Code:
29 29
30(defconst file-notify--library 30(defconst file-notify--library
31 (cond 31 (cond
32 ((featurep 'gfilenotify) 'gfilenotify)
33 ((featurep 'inotify) 'inotify) 32 ((featurep 'inotify) 'inotify)
33 ((featurep 'kqueue) 'kqueue)
34 ((featurep 'gfilenotify) 'gfilenotify)
34 ((featurep 'w32notify) 'w32notify)) 35 ((featurep 'w32notify) 'w32notify))
35 "Non-nil when Emacs has been compiled with file notification support. 36 "Non-nil when Emacs has been compiled with file notification support.
36The value is the name of the low-level file notification package 37The value is the name of the low-level file notification package
@@ -40,25 +41,24 @@ could use another implementation.")
40(defvar file-notify-descriptors (make-hash-table :test 'equal) 41(defvar file-notify-descriptors (make-hash-table :test 'equal)
41 "Hash table for registered file notification descriptors. 42 "Hash table for registered file notification descriptors.
42A key in this hash table is the descriptor as returned from 43A key in this hash table is the descriptor as returned from
43`gfilenotify', `inotify', `w32notify' or a file name handler. 44`inotify', `kqueue', `gfilenotify', `w32notify' or a file name
44The value in the hash table is a list 45handler. The value in the hash table is a list
45 46
46 (DIR (FILE . CALLBACK) (FILE . CALLBACK) ...) 47 (DIR (FILE . CALLBACK) (FILE . CALLBACK) ...)
47 48
48Several values for a given DIR happen only for `inotify', when 49Several values for a given DIR happen only for `inotify', when
49different files from the same directory are watched.") 50different files from the same directory are watched.")
50 51
51(defun file-notify--rm-descriptor (descriptor &optional what) 52(defun file-notify--rm-descriptor (descriptor)
52 "Remove DESCRIPTOR from `file-notify-descriptors'. 53 "Remove DESCRIPTOR from `file-notify-descriptors'.
53DESCRIPTOR should be an object returned by `file-notify-add-watch'. 54DESCRIPTOR should be an object returned by `file-notify-add-watch'.
54If it is registered in `file-notify-descriptors', a stopped event is sent. 55If it is registered in `file-notify-descriptors', a stopped event is sent."
55WHAT is a file or directory name to be removed, needed just for `inotify'."
56 (let* ((desc (if (consp descriptor) (car descriptor) descriptor)) 56 (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
57 (file (if (consp descriptor) (cdr descriptor))) 57 (file (if (consp descriptor) (cdr descriptor)))
58 (registered (gethash desc file-notify-descriptors)) 58 (registered (gethash desc file-notify-descriptors))
59 (dir (car registered))) 59 (dir (car registered)))
60 60
61 (when (and (consp registered) (or (null what) (string-equal dir what))) 61 (when (consp registered)
62 ;; Send `stopped' event. 62 ;; Send `stopped' event.
63 (dolist (entry (cdr registered)) 63 (dolist (entry (cdr registered))
64 (funcall (cdr entry) 64 (funcall (cdr entry)
@@ -76,7 +76,8 @@ WHAT is a file or directory name to be removed, needed just for `inotify'."
76 (remhash desc file-notify-descriptors) 76 (remhash desc file-notify-descriptors)
77 (puthash desc registered file-notify-descriptors)))))) 77 (puthash desc registered file-notify-descriptors))))))
78 78
79;; This function is used by `gfilenotify', `inotify' and `w32notify' events. 79;; This function is used by `inotify', `kqueue', `gfilenotify' and
80;; `w32notify' events.
80;;;###autoload 81;;;###autoload
81(defun file-notify-handle-event (event) 82(defun file-notify-handle-event (event)
82 "Handle file system monitoring event. 83 "Handle file system monitoring event.
@@ -159,7 +160,7 @@ EVENT is the cadr of the event in `file-notify-handle-event'
159 (setq actions nil)) 160 (setq actions nil))
160 161
161 ;; Loop over actions. In fact, more than one action happens only 162 ;; Loop over actions. In fact, more than one action happens only
162 ;; for `inotify'. 163 ;; for `inotify' and `kqueue'.
163 (dolist (action actions) 164 (dolist (action actions)
164 165
165 ;; Send pending event, if it doesn't match. 166 ;; Send pending event, if it doesn't match.
@@ -184,19 +185,17 @@ EVENT is the cadr of the event in `file-notify-handle-event'
184 ;; Map action. We ignore all events which cannot be mapped. 185 ;; Map action. We ignore all events which cannot be mapped.
185 (setq action 186 (setq action
186 (cond 187 (cond
187 ;; gfilenotify. 188 ((memq action
188 ((memq action '(attribute-changed changed created deleted)) 189 '(attribute-changed changed created deleted renamed))
189 action) 190 action)
190 ((eq action 'moved) 191 ((memq action '(moved rename))
191 (setq file1 (file-notify--event-file1-name event)) 192 (setq file1 (file-notify--event-file1-name event))
192 'renamed) 193 'renamed)
193
194 ;; inotify, w32notify.
195 ((eq action 'ignored) 194 ((eq action 'ignored)
196 (setq stopped t actions nil)) 195 (setq stopped t actions nil))
197 ((eq action 'attrib) 'attribute-changed) 196 ((memq action '(attrib link)) 'attribute-changed)
198 ((memq action '(create added)) 'created) 197 ((memq action '(create added)) 'created)
199 ((memq action '(modify modified)) 'changed) 198 ((memq action '(modify modified write)) 'changed)
200 ((memq action '(delete delete-self move-self removed)) 'deleted) 199 ((memq action '(delete delete-self move-self removed)) 'deleted)
201 ;; Make the event pending. 200 ;; Make the event pending.
202 ((memq action '(moved-from renamed-from)) 201 ((memq action '(moved-from renamed-from))
@@ -236,7 +235,6 @@ EVENT is the cadr of the event in `file-notify-handle-event'
236 (setq pending-event nil)) 235 (setq pending-event nil))
237 236
238 ;; Check for stopped. 237 ;; Check for stopped.
239 ;;(message "file-notify-callback %S %S" file registered)
240 (setq 238 (setq
241 stopped 239 stopped
242 (or 240 (or
@@ -244,10 +242,13 @@ EVENT is the cadr of the event in `file-notify-handle-event'
244 (and 242 (and
245 (memq action '(deleted renamed)) 243 (memq action '(deleted renamed))
246 (= (length (cdr registered)) 1) 244 (= (length (cdr registered)) 1)
247 (string-equal 245 (or
248 (file-name-nondirectory file) 246 (string-equal
249 (or (file-name-nondirectory (car registered)) 247 (file-name-nondirectory file)
250 (car (cadr registered))))))) 248 (file-name-nondirectory (car registered)))
249 (string-equal
250 (file-name-nondirectory file)
251 (car (cadr registered)))))))
251 252
252 ;; Apply callback. 253 ;; Apply callback.
253 (when (and action 254 (when (and action
@@ -258,10 +259,17 @@ EVENT is the cadr of the event in `file-notify-handle-event'
258 ;; File matches. 259 ;; File matches.
259 (string-equal 260 (string-equal
260 (nth 0 entry) (file-name-nondirectory file)) 261 (nth 0 entry) (file-name-nondirectory file))
262 ;; Directory matches.
263 (string-equal
264 (file-name-nondirectory file)
265 (file-name-nondirectory (car registered)))
261 ;; File1 matches. 266 ;; File1 matches.
262 (and (stringp file1) 267 (and (stringp file1)
263 (string-equal 268 (string-equal
264 (nth 0 entry) (file-name-nondirectory file1))))) 269 (nth 0 entry) (file-name-nondirectory file1)))))
270 ;;(message
271 ;;"file-notify-callback %S %S %S %S %S"
272 ;;(file-notify--descriptor desc file) action file file1 registered)
265 (if file1 273 (if file1
266 (funcall 274 (funcall
267 callback 275 callback
@@ -272,11 +280,10 @@ EVENT is the cadr of the event in `file-notify-handle-event'
272 280
273 ;; Modify `file-notify-descriptors'. 281 ;; Modify `file-notify-descriptors'.
274 (when stopped 282 (when stopped
275 (file-notify--rm-descriptor 283 (file-notify-rm-watch (file-notify--descriptor desc file))))))
276 (file-notify--descriptor desc file) file)))))
277 284
278;; `gfilenotify' and `w32notify' return a unique descriptor for every 285;; `kqueue', `gfilenotify' and `w32notify' return a unique descriptor
279;; `file-notify-add-watch', while `inotify' returns a unique 286;; for every `file-notify-add-watch', while `inotify' returns a unique
280;; descriptor per inode only. 287;; descriptor per inode only.
281(defun file-notify-add-watch (file flags callback) 288(defun file-notify-add-watch (file flags callback)
282 "Add a watch for filesystem events pertaining to FILE. 289 "Add a watch for filesystem events pertaining to FILE.
@@ -329,7 +336,7 @@ FILE is the name of the file whose event is being reported."
329 (if (file-directory-p file) 336 (if (file-directory-p file)
330 file 337 file
331 (file-name-directory file)))) 338 (file-name-directory file))))
332 desc func l-flags registered) 339 desc func l-flags registered entry)
333 340
334 (unless (file-directory-p dir) 341 (unless (file-directory-p dir)
335 (signal 'file-notify-error `("Directory does not exist" ,dir))) 342 (signal 'file-notify-error `("Directory does not exist" ,dir)))
@@ -338,7 +345,12 @@ FILE is the name of the file whose event is being reported."
338 ;; A file name handler could exist even if there is no local 345 ;; A file name handler could exist even if there is no local
339 ;; file notification support. 346 ;; file notification support.
340 (setq desc (funcall 347 (setq desc (funcall
341 handler 'file-notify-add-watch dir flags callback)) 348 handler 'file-notify-add-watch
349 ;; kqueue does not report file changes in
350 ;; directory monitor. So we must watch the file
351 ;; itself.
352 (if (eq file-notify--library 'kqueue) file dir)
353 flags callback))
342 354
343 ;; Check, whether Emacs has been compiled with file notification 355 ;; Check, whether Emacs has been compiled with file notification
344 ;; support. 356 ;; support.
@@ -349,8 +361,9 @@ FILE is the name of the file whose event is being reported."
349 ;; Determine low-level function to be called. 361 ;; Determine low-level function to be called.
350 (setq func 362 (setq func
351 (cond 363 (cond
352 ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
353 ((eq file-notify--library 'inotify) 'inotify-add-watch) 364 ((eq file-notify--library 'inotify) 'inotify-add-watch)
365 ((eq file-notify--library 'kqueue) 'kqueue-add-watch)
366 ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
354 ((eq file-notify--library 'w32notify) 'w32notify-add-watch))) 367 ((eq file-notify--library 'w32notify) 'w32notify-add-watch)))
355 368
356 ;; Determine respective flags. 369 ;; Determine respective flags.
@@ -362,30 +375,32 @@ FILE is the name of the file whose event is being reported."
362 (cond 375 (cond
363 ((eq file-notify--library 'inotify) 376 ((eq file-notify--library 'inotify)
364 '(create delete delete-self modify move-self move)) 377 '(create delete delete-self modify move-self move))
378 ((eq file-notify--library 'kqueue)
379 '(create delete write extend rename))
365 ((eq file-notify--library 'w32notify) 380 ((eq file-notify--library 'w32notify)
366 '(file-name directory-name size last-write-time))))) 381 '(file-name directory-name size last-write-time)))))
367 (when (memq 'attribute-change flags) 382 (when (memq 'attribute-change flags)
368 (push (cond 383 (push (cond
369 ((eq file-notify--library 'inotify) 'attrib) 384 ((eq file-notify--library 'inotify) 'attrib)
385 ((eq file-notify--library 'kqueue) 'attrib)
370 ((eq file-notify--library 'w32notify) 'attributes)) 386 ((eq file-notify--library 'w32notify) 'attributes))
371 l-flags))) 387 l-flags)))
372 388
373 ;; Call low-level function. 389 ;; Call low-level function.
374 (setq desc (funcall func dir l-flags 'file-notify-callback))) 390 (setq desc (funcall
391 func (if (eq file-notify--library 'kqueue) file dir)
392 l-flags 'file-notify-callback)))
375 393
376 ;; Modify `file-notify-descriptors'. 394 ;; Modify `file-notify-descriptors'.
377 (setq registered (gethash desc file-notify-descriptors)) 395 (setq file (unless (file-directory-p file) (file-name-nondirectory file))
378 (puthash 396 desc (if (consp desc) (car desc) desc)
379 desc 397 registered (gethash desc file-notify-descriptors)
380 `(,dir 398 entry `(,file . ,callback))
381 (,(unless (file-directory-p file) (file-name-nondirectory file)) 399 (unless (member entry (cdr registered))
382 . ,callback) 400 (puthash desc `(,dir ,entry . ,(cdr registered)) file-notify-descriptors))
383 . ,(cdr registered))
384 file-notify-descriptors)
385 401
386 ;; Return descriptor. 402 ;; Return descriptor.
387 (file-notify--descriptor 403 (file-notify--descriptor desc file)))
388 desc (unless (file-directory-p file) (file-name-nondirectory file)))))
389 404
390(defun file-notify-rm-watch (descriptor) 405(defun file-notify-rm-watch (descriptor)
391 "Remove an existing watch specified by its DESCRIPTOR. 406 "Remove an existing watch specified by its DESCRIPTOR.
@@ -410,8 +425,9 @@ DESCRIPTOR should be an object returned by `file-notify-add-watch'."
410 425
411 (funcall 426 (funcall
412 (cond 427 (cond
413 ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
414 ((eq file-notify--library 'inotify) 'inotify-rm-watch) 428 ((eq file-notify--library 'inotify) 'inotify-rm-watch)
429 ((eq file-notify--library 'kqueue) 'kqueue-rm-watch)
430 ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
415 ((eq file-notify--library 'w32notify) 'w32notify-rm-watch)) 431 ((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
416 desc)) 432 desc))
417 (file-notify-error nil))) 433 (file-notify-error nil)))
@@ -441,8 +457,9 @@ DESCRIPTOR should be an object returned by `file-notify-add-watch'."
441 (funcall handler 'file-notify-valid-p descriptor) 457 (funcall handler 'file-notify-valid-p descriptor)
442 (funcall 458 (funcall
443 (cond 459 (cond
444 ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
445 ((eq file-notify--library 'inotify) 'inotify-valid-p) 460 ((eq file-notify--library 'inotify) 'inotify-valid-p)
461 ((eq file-notify--library 'kqueue) 'kqueue-valid-p)
462 ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
446 ((eq file-notify--library 'w32notify) 'w32notify-valid-p)) 463 ((eq file-notify--library 'w32notify) 'w32notify-valid-p))
447 desc)) 464 desc))
448 t)))) 465 t))))
diff --git a/src/Makefile.in b/src/Makefile.in
index 4a52757fec6..fab10aeed47 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -163,12 +163,13 @@ SETTINGS_LIBS = @SETTINGS_LIBS@
163## gtkutil.o if USE_GTK, else empty. 163## gtkutil.o if USE_GTK, else empty.
164GTK_OBJ=@GTK_OBJ@ 164GTK_OBJ=@GTK_OBJ@
165 165
166## gfilenotify.o if HAVE_GFILENOTIFY.
167## inotify.o if HAVE_INOTIFY. 166## inotify.o if HAVE_INOTIFY.
167## kqueue.o if HAVE_KQUEUE.
168## gfilenotify.o if HAVE_GFILENOTIFY.
168## w32notify.o if HAVE_W32NOTIFY. 169## w32notify.o if HAVE_W32NOTIFY.
169NOTIFY_OBJ = @NOTIFY_OBJ@ 170NOTIFY_OBJ = @NOTIFY_OBJ@
170GFILENOTIFY_CFLAGS = @GFILENOTIFY_CFLAGS@ 171NOTIFY_CFLAGS = @NOTIFY_CFLAGS@
171GFILENOTIFY_LIBS = @GFILENOTIFY_LIBS@ 172NOTIFY_LIBS = @NOTIFY_LIBS@
172 173
173## -ltermcap, or -lncurses, or -lcurses, or "". 174## -ltermcap, or -lncurses, or -lcurses, or "".
174LIBS_TERMCAP=@LIBS_TERMCAP@ 175LIBS_TERMCAP=@LIBS_TERMCAP@
@@ -367,7 +368,7 @@ ALL_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \
367 $(WEBKIT_CFLAGS) \ 368 $(WEBKIT_CFLAGS) \
368 $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ 369 $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
369 $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \ 370 $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
370 $(LIBGNUTLS_CFLAGS) $(GFILENOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ 371 $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \
371 $(WARN_CFLAGS) $(WERROR_CFLAGS) $(CFLAGS) 372 $(WARN_CFLAGS) $(WERROR_CFLAGS) $(CFLAGS)
372ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS) 373ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS)
373 374
@@ -482,7 +483,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \
482 $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \ 483 $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \
483 $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ 484 $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
484 $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) \ 485 $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) \
485 $(GFILENOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) 486 $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES)
486 487
487$(leimdir)/leim-list.el: bootstrap-emacs$(EXEEXT) 488$(leimdir)/leim-list.el: bootstrap-emacs$(EXEEXT)
488 $(MAKE) -C ../leim leim-list.el EMACS="$(bootstrap_exe)" 489 $(MAKE) -C ../leim leim-list.el EMACS="$(bootstrap_exe)"
diff --git a/src/emacs.c b/src/emacs.c
index 6de0fffb904..ddaa82c1f64 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1360,6 +1360,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
1360 tzset (); 1360 tzset ();
1361#endif /* MSDOS */ 1361#endif /* MSDOS */
1362 1362
1363#ifdef HAVE_KQUEUE
1364 globals_of_kqueue ();
1365#endif
1366
1363#ifdef HAVE_GFILENOTIFY 1367#ifdef HAVE_GFILENOTIFY
1364 globals_of_gfilenotify (); 1368 globals_of_gfilenotify ();
1365#endif 1369#endif
@@ -1538,14 +1542,18 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
1538 1542
1539 syms_of_gnutls (); 1543 syms_of_gnutls ();
1540 1544
1541#ifdef HAVE_GFILENOTIFY
1542 syms_of_gfilenotify ();
1543#endif /* HAVE_GFILENOTIFY */
1544
1545#ifdef HAVE_INOTIFY 1545#ifdef HAVE_INOTIFY
1546 syms_of_inotify (); 1546 syms_of_inotify ();
1547#endif /* HAVE_INOTIFY */ 1547#endif /* HAVE_INOTIFY */
1548 1548
1549#ifdef HAVE_KQUEUE
1550 syms_of_kqueue ();
1551#endif /* HAVE_KQUEUE */
1552
1553#ifdef HAVE_GFILENOTIFY
1554 syms_of_gfilenotify ();
1555#endif /* HAVE_GFILENOTIFY */
1556
1549#ifdef HAVE_DBUS 1557#ifdef HAVE_DBUS
1550 syms_of_dbusbind (); 1558 syms_of_dbusbind ();
1551#endif /* HAVE_DBUS */ 1559#endif /* HAVE_DBUS */
diff --git a/src/inotify.c b/src/inotify.c
index 47652ff35bd..e0619e584f7 100644
--- a/src/inotify.c
+++ b/src/inotify.c
@@ -46,8 +46,7 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
46static int inotifyfd = -1; 46static int inotifyfd = -1;
47 47
48/* Assoc list of files being watched. 48/* Assoc list of files being watched.
49 Format: 49 Format: (watch-descriptor name callback)
50 (watch-descriptor . callback)
51 */ 50 */
52static Lisp_Object watch_list; 51static Lisp_Object watch_list;
53 52
@@ -106,12 +105,14 @@ inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev)
106 name = make_unibyte_string (ev->name, min (len, ev->len)); 105 name = make_unibyte_string (ev->name, min (len, ev->len));
107 name = DECODE_FILE (name); 106 name = DECODE_FILE (name);
108 } 107 }
108 else
109 name = XCAR (XCDR (watch_object));
109 110
110 return list2 (list4 (make_watch_descriptor (ev->wd), 111 return list2 (list4 (make_watch_descriptor (ev->wd),
111 mask_to_aspects (ev->mask), 112 mask_to_aspects (ev->mask),
112 name, 113 name,
113 make_number (ev->cookie)), 114 make_number (ev->cookie)),
114 XCDR (watch_object)); 115 Fnth (make_number (2), watch_object));
115} 116}
116 117
117/* This callback is called when the FD is available for read. The inotify 118/* This callback is called when the FD is available for read. The inotify
@@ -325,7 +326,7 @@ is managed internally and there is no corresponding inotify_init. Use
325 watch_list = Fdelete (watch_object, watch_list); 326 watch_list = Fdelete (watch_object, watch_list);
326 327
327 /* Store watch object in watch list. */ 328 /* Store watch object in watch list. */
328 watch_object = Fcons (watch_descriptor, callback); 329 watch_object = list3 (watch_descriptor, encoded_file_name, callback);
329 watch_list = Fcons (watch_object, watch_list); 330 watch_list = Fcons (watch_object, watch_list);
330 331
331 return watch_descriptor; 332 return watch_descriptor;
diff --git a/src/keyboard.c b/src/keyboard.c
index 58831f548ca..c93876effd9 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -5965,12 +5965,12 @@ make_lispy_event (struct input_event *event)
5965#endif 5965#endif
5966 5966
5967 5967
5968#if defined HAVE_GFILENOTIFY || defined HAVE_INOTIFY 5968#if defined HAVE_INOTIFY || defined HAVE_KQUEUE || defined HAVE_GFILENOTIFY
5969 case FILE_NOTIFY_EVENT: 5969 case FILE_NOTIFY_EVENT:
5970 { 5970 {
5971 return Fcons (Qfile_notify, event->arg); 5971 return Fcons (Qfile_notify, event->arg);
5972 } 5972 }
5973#endif /* defined HAVE_GFILENOTIFY || defined HAVE_INOTIFY */ 5973#endif /* HAVE_INOTIFY || HAVE_KQUEUE || HAVE_GFILENOTIFY */
5974 5974
5975 case CONFIG_CHANGED_EVENT: 5975 case CONFIG_CHANGED_EVENT:
5976 return list3 (Qconfig_changed_event, 5976 return list3 (Qconfig_changed_event,
diff --git a/src/kqueue.c b/src/kqueue.c
new file mode 100644
index 00000000000..a69d06da3ae
--- /dev/null
+++ b/src/kqueue.c
@@ -0,0 +1,521 @@
1/* Filesystem notifications support with kqueue API.
2
3Copyright (C) 2015-2016 Free Software Foundation, Inc.
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
19
20#include <config.h>
21
22#ifdef HAVE_KQUEUE
23#include <stdio.h>
24#include <sys/types.h>
25#include <sys/event.h>
26#include <sys/time.h>
27#include <sys/file.h>
28#include "lisp.h"
29#include "keyboard.h"
30#include "process.h"
31
32
33/* File handle for kqueue. */
34static int kqueuefd = -1;
35
36/* This is a list, elements are (DESCRIPTOR FILE FLAGS CALLBACK [DIRLIST]). */
37static Lisp_Object watch_list;
38
39/* Generate a list from the directory_files_internal output.
40 Items are (INODE FILE-NAME LAST-MOD LAST-STATUS-MOD SIZE). */
41Lisp_Object
42kqueue_directory_listing (Lisp_Object directory_files)
43{
44 Lisp_Object dl, result = Qnil;
45
46 for (dl = directory_files; ! NILP (dl); dl = XCDR (dl)) {
47 /* We ignore "." and "..". */
48 if ((strcmp (".", SSDATA (XCAR (XCAR (dl)))) == 0) ||
49 (strcmp ("..", SSDATA (XCAR (XCAR (dl)))) == 0))
50 continue;
51
52 result = Fcons
53 (list5 (/* inode. */
54 Fnth (make_number (11), XCAR (dl)),
55 /* filename. */
56 XCAR (XCAR (dl)),
57 /* last modification time. */
58 Fnth (make_number (6), XCAR (dl)),
59 /* last status change time. */
60 Fnth (make_number (7), XCAR (dl)),
61 /* size. */
62 Fnth (make_number (8), XCAR (dl))),
63 result);
64 }
65 return result;
66}
67
68/* Generate a file notification event. */
69static void
70kqueue_generate_event
71(Lisp_Object watch_object, Lisp_Object actions,
72 Lisp_Object file, Lisp_Object file1)
73{
74 Lisp_Object flags, action, entry;
75 struct input_event event;
76
77 /* Check, whether all actions shall be monitored. */
78 flags = Fnth (make_number (2), watch_object);
79 action = actions;
80 do {
81 if (NILP (action))
82 break;
83 entry = XCAR (action);
84 if (NILP (Fmember (entry, flags))) {
85 action = XCDR (action);
86 actions = Fdelq (entry, actions);
87 } else
88 action = XCDR (action);
89 } while (1);
90
91 /* Store it into the input event queue. */
92 if (! NILP (actions)) {
93 EVENT_INIT (event);
94 event.kind = FILE_NOTIFY_EVENT;
95 event.frame_or_window = Qnil;
96 event.arg = list2 (Fcons (XCAR (watch_object),
97 Fcons (actions,
98 NILP (file1)
99 ? Fcons (file, Qnil)
100 : list2 (file, file1))),
101 Fnth (make_number (3), watch_object));
102 kbd_buffer_store_event (&event);
103 }
104}
105
106/* This compares two directory listings in case of a `write' event for
107 a directory. Generate resulting file notification events. The old
108 directory listing is retrieved from watch_object, it will be
109 replaced by the new directory listing at the end of this
110 function. */
111static void
112kqueue_compare_dir_list
113(Lisp_Object watch_object)
114{
115 Lisp_Object dir, pending_dl, deleted_dl;
116 Lisp_Object old_directory_files, old_dl, new_directory_files, new_dl, dl;
117
118 dir = XCAR (XCDR (watch_object));
119 pending_dl = Qnil;
120 deleted_dl = Qnil;
121
122 old_directory_files = Fnth (make_number (4), watch_object);
123 old_dl = kqueue_directory_listing (old_directory_files);
124
125 /* When the directory is not accessible anymore, it has been deleted. */
126 if (NILP (Ffile_directory_p (dir))) {
127 kqueue_generate_event (watch_object, Fcons (Qdelete, Qnil), dir, Qnil);
128 return;
129 }
130 new_directory_files =
131 directory_files_internal (dir, Qnil, Qnil, Qnil, 1, Qnil);
132 new_dl = kqueue_directory_listing (new_directory_files);
133
134 /* Parse through the old list. */
135 dl = old_dl;
136 while (1) {
137 Lisp_Object old_entry, new_entry, dl1;
138 if (NILP (dl))
139 break;
140
141 /* Search for an entry with the same inode. */
142 old_entry = XCAR (dl);
143 new_entry = assq_no_quit (XCAR (old_entry), new_dl);
144 if (! NILP (Fequal (old_entry, new_entry))) {
145 /* Both entries are identical. Nothing to do. */
146 new_dl = Fdelq (new_entry, new_dl);
147 goto the_end;
148 }
149
150 /* Both entries have the same inode. */
151 if (! NILP (new_entry)) {
152 /* Both entries have the same file name. */
153 if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
154 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
155 /* Modification time has been changed, the file has been written. */
156 if (NILP (Fequal (Fnth (make_number (2), old_entry),
157 Fnth (make_number (2), new_entry))))
158 kqueue_generate_event
159 (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (old_entry)), Qnil);
160 /* Status change time has been changed, the file attributes
161 have changed. */
162 if (NILP (Fequal (Fnth (make_number (3), old_entry),
163 Fnth (make_number (3), new_entry))))
164 kqueue_generate_event
165 (watch_object, Fcons (Qattrib, Qnil),
166 XCAR (XCDR (old_entry)), Qnil);
167
168 } else {
169 /* The file has been renamed. */
170 kqueue_generate_event
171 (watch_object, Fcons (Qrename, Qnil),
172 XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
173 deleted_dl = Fcons (new_entry, deleted_dl);
174 }
175 new_dl = Fdelq (new_entry, new_dl);
176 goto the_end;
177 }
178
179 /* Search, whether there is a file with the same name but another
180 inode. */
181 for (dl1 = new_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
182 new_entry = XCAR (dl1);
183 if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
184 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
185 pending_dl = Fcons (new_entry, pending_dl);
186 new_dl = Fdelq (new_entry, new_dl);
187 goto the_end;
188 }
189 }
190
191 /* Check, whether this a pending file. */
192 new_entry = assq_no_quit (XCAR (old_entry), pending_dl);
193
194 if (NILP (new_entry)) {
195 /* Check, whether this is an already deleted file (by rename). */
196 for (dl1 = deleted_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
197 new_entry = XCAR (dl1);
198 if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
199 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
200 deleted_dl = Fdelq (new_entry, deleted_dl);
201 goto the_end;
202 }
203 }
204 /* The file has been deleted. */
205 kqueue_generate_event
206 (watch_object, Fcons (Qdelete, Qnil), XCAR (XCDR (old_entry)), Qnil);
207
208 } else {
209 /* The file has been renamed. */
210 kqueue_generate_event
211 (watch_object, Fcons (Qrename, Qnil),
212 XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
213 pending_dl = Fdelq (new_entry, pending_dl);
214 }
215
216 the_end:
217 dl = XCDR (dl);
218 old_dl = Fdelq (old_entry, old_dl);
219 }
220
221 /* Parse through the resulting new list. */
222 dl = new_dl;
223 while (1) {
224 Lisp_Object entry;
225 if (NILP (dl))
226 break;
227
228 /* A new file has appeared. */
229 entry = XCAR (dl);
230 kqueue_generate_event
231 (watch_object, Fcons (Qcreate, Qnil), XCAR (XCDR (entry)), Qnil);
232
233 /* Check size of that file. */
234 Lisp_Object size = Fnth (make_number (4), entry);
235 if (FLOATP (size) || (XINT (size) > 0))
236 kqueue_generate_event
237 (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
238
239 dl = XCDR (dl);
240 new_dl = Fdelq (entry, new_dl);
241 }
242
243 /* Parse through the resulting pending_dl list. */
244 dl = pending_dl;
245 while (1) {
246 Lisp_Object entry;
247 if (NILP (dl))
248 break;
249
250 /* A file is still pending. Assume it was a write. */
251 entry = XCAR (dl);
252 kqueue_generate_event
253 (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
254
255 dl = XCDR (dl);
256 pending_dl = Fdelq (entry, pending_dl);
257 }
258
259 /* At this point, old_dl, new_dl and pending_dl shall be empty.
260 deleted_dl might not be empty when there was a rename to a
261 nonexistent file. Let's make a check for this (might be removed
262 once the code is stable). */
263 if (! NILP (old_dl))
264 report_file_error ("Old list not empty", old_dl);
265 if (! NILP (new_dl))
266 report_file_error ("New list not empty", new_dl);
267 if (! NILP (pending_dl))
268 report_file_error ("Pending events list not empty", pending_dl);
269 // if (! NILP (deleted_dl))
270 // report_file_error ("Deleted events list not empty", deleted_dl);
271
272 /* Replace old directory listing with the new one. */
273 XSETCDR (Fnthcdr (make_number (3), watch_object),
274 Fcons (new_directory_files, Qnil));
275 return;
276}
277
278/* This is the callback function for arriving input on kqueuefd. It
279 shall create a Lisp event, and put it into the Emacs input queue. */
280static void
281kqueue_callback (int fd, void *data)
282{
283 for (;;) {
284 struct kevent kev;
285 static const struct timespec nullts = { 0, 0 };
286 Lisp_Object descriptor, watch_object, file, actions;
287
288 /* Read one event. */
289 int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts);
290 if (ret < 1) {
291 /* All events read. */
292 return;
293 }
294
295 /* Determine descriptor and file name. */
296 descriptor = make_number (kev.ident);
297 watch_object = assq_no_quit (descriptor, watch_list);
298 if (CONSP (watch_object))
299 file = XCAR (XCDR (watch_object));
300 else
301 continue;
302
303 /* Determine event actions. */
304 actions = Qnil;
305 if (kev.fflags & NOTE_DELETE)
306 actions = Fcons (Qdelete, actions);
307 if (kev.fflags & NOTE_WRITE) {
308 /* Check, whether this is a directory event. */
309 if (NILP (Fnth (make_number (4), watch_object)))
310 actions = Fcons (Qwrite, actions);
311 else
312 kqueue_compare_dir_list (watch_object);
313 }
314 if (kev.fflags & NOTE_EXTEND)
315 actions = Fcons (Qextend, actions);
316 if (kev.fflags & NOTE_ATTRIB)
317 actions = Fcons (Qattrib, actions);
318 if (kev.fflags & NOTE_LINK)
319 actions = Fcons (Qlink, actions);
320 /* It would be useful to know the target of the rename operation.
321 At this point, it is not possible. Happens only when the upper
322 directory is monitored. */
323 if (kev.fflags & NOTE_RENAME)
324 actions = Fcons (Qrename, actions);
325
326 /* Create the event. */
327 if (! NILP (actions))
328 kqueue_generate_event (watch_object, actions, file, Qnil);
329
330 /* Cancel monitor if file or directory is deleted or renamed. */
331 if (kev.fflags & (NOTE_DELETE | NOTE_RENAME))
332 Fkqueue_rm_watch (descriptor);
333 }
334 return;
335}
336
337DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0,
338 doc: /* Add a watch for filesystem events pertaining to FILE.
339
340This arranges for filesystem events pertaining to FILE to be reported
341to Emacs. Use `kqueue-rm-watch' to cancel the watch.
342
343Returned value is a descriptor for the added watch. If the file cannot be
344watched for some reason, this function signals a `file-notify-error' error.
345
346FLAGS is a list of events to be watched for. It can include the
347following symbols:
348
349 `create' -- FILE was created
350 `delete' -- FILE was deleted
351 `write' -- FILE has changed
352 `extend' -- FILE was extended
353 `attrib' -- a FILE attribute was changed
354 `link' -- a FILE's link count was changed
355 `rename' -- FILE was moved to FILE1
356
357When any event happens, Emacs will call the CALLBACK function passing
358it a single argument EVENT, which is of the form
359
360 (DESCRIPTOR ACTIONS FILE [FILE1])
361
362DESCRIPTOR is the same object as the one returned by this function.
363ACTIONS is a list of events.
364
365FILE is the name of the file whose event is being reported. FILE1
366will be reported only in case of the `rename' event. This is possible
367only when the upper directory of the renamed file is watched. */)
368 (Lisp_Object file, Lisp_Object flags, Lisp_Object callback)
369{
370 Lisp_Object watch_object, dir_list;
371 int fd, oflags;
372 u_short fflags = 0;
373 struct kevent kev;
374
375 /* Check parameters. */
376 CHECK_STRING (file);
377 file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
378 if (NILP (Ffile_exists_p (file)))
379 report_file_error ("File does not exist", file);
380
381 CHECK_LIST (flags);
382
383 if (! FUNCTIONP (callback))
384 wrong_type_argument (Qinvalid_function, callback);
385
386 if (kqueuefd < 0)
387 {
388 /* Create kqueue descriptor. */
389 kqueuefd = kqueue ();
390 if (kqueuefd < 0)
391 report_file_notify_error ("File watching is not available", Qnil);
392
393 /* Start monitoring for possible I/O. */
394 add_read_fd (kqueuefd, kqueue_callback, NULL);
395
396 watch_list = Qnil;
397 }
398
399 /* Open file. */
400 file = ENCODE_FILE (file);
401 oflags = O_NONBLOCK;
402#if O_EVTONLY
403 oflags |= O_EVTONLY;
404#else
405 oflags |= O_RDONLY;
406#endif
407#if O_SYMLINK
408 oflags |= O_SYMLINK;
409#else
410 oflags |= O_NOFOLLOW;
411#endif
412 fd = emacs_open (SSDATA (file), oflags, 0);
413 if (fd == -1)
414 report_file_error ("File cannot be opened", file);
415
416 /* Assemble filter flags */
417 if (! NILP (Fmember (Qdelete, flags))) fflags |= NOTE_DELETE;
418 if (! NILP (Fmember (Qwrite, flags))) fflags |= NOTE_WRITE;
419 if (! NILP (Fmember (Qextend, flags))) fflags |= NOTE_EXTEND;
420 if (! NILP (Fmember (Qattrib, flags))) fflags |= NOTE_ATTRIB;
421 if (! NILP (Fmember (Qlink, flags))) fflags |= NOTE_LINK;
422 if (! NILP (Fmember (Qrename, flags))) fflags |= NOTE_RENAME;
423
424 /* Register event. */
425 EV_SET (&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
426 fflags, 0, NULL);
427
428 if (kevent (kqueuefd, &kev, 1, NULL, 0, NULL) < 0) {
429 emacs_close (fd);
430 report_file_error ("Cannot watch file", file);
431 }
432
433 /* Store watch object in watch list. */
434 Lisp_Object watch_descriptor = make_number (fd);
435 if (NILP (Ffile_directory_p (file)))
436 watch_object = list4 (watch_descriptor, file, flags, callback);
437 else {
438 dir_list = directory_files_internal (file, Qnil, Qnil, Qnil, 1, Qnil);
439 watch_object = list5 (watch_descriptor, file, flags, callback, dir_list);
440 }
441 watch_list = Fcons (watch_object, watch_list);
442
443 return watch_descriptor;
444}
445
446DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch, Skqueue_rm_watch, 1, 1, 0,
447 doc: /* Remove an existing WATCH-DESCRIPTOR.
448
449WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
450 (Lisp_Object watch_descriptor)
451{
452 Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list);
453
454 if (! CONSP (watch_object))
455 xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"),
456 watch_descriptor);
457
458 eassert (INTEGERP (watch_descriptor));
459 int fd = XINT (watch_descriptor);
460 if ( fd >= 0)
461 emacs_close (fd);
462
463 /* Remove watch descriptor from watch list. */
464 watch_list = Fdelq (watch_object, watch_list);
465
466 if (NILP (watch_list) && (kqueuefd >= 0)) {
467 delete_read_fd (kqueuefd);
468 emacs_close (kqueuefd);
469 kqueuefd = -1;
470 }
471
472 return Qt;
473}
474
475DEFUN ("kqueue-valid-p", Fkqueue_valid_p, Skqueue_valid_p, 1, 1, 0,
476 doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
477
478WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
479
480A watch can become invalid if the file or directory it watches is
481deleted, or if the watcher thread exits abnormally for any other
482reason. Removing the watch by calling `kqueue-rm-watch' also makes it
483invalid. */)
484 (Lisp_Object watch_descriptor)
485{
486 return NILP (assq_no_quit (watch_descriptor, watch_list)) ? Qnil : Qt;
487}
488
489
490void
491globals_of_kqueue (void)
492{
493 watch_list = Qnil;
494}
495
496void
497syms_of_kqueue (void)
498{
499 defsubr (&Skqueue_add_watch);
500 defsubr (&Skqueue_rm_watch);
501 defsubr (&Skqueue_valid_p);
502
503 /* Event types. */
504 DEFSYM (Qcreate, "create");
505 DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */
506 DEFSYM (Qwrite, "write"); /* NOTE_WRITE */
507 DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */
508 DEFSYM (Qattrib, "attrib"); /* NOTE_ATTRIB */
509 DEFSYM (Qlink, "link"); /* NOTE_LINK */
510 DEFSYM (Qrename, "rename"); /* NOTE_RENAME */
511
512 staticpro (&watch_list);
513
514 Fprovide (intern_c_string ("kqueue"), Qnil);
515}
516
517#endif /* HAVE_KQUEUE */
518
519/* PROBLEMS
520 * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
521 prevents tests on Ubuntu. */
diff --git a/src/lisp.h b/src/lisp.h
index b0a8d75c439..8aa034e9e57 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4319,17 +4319,23 @@ extern void init_font (void);
4319extern void syms_of_fontset (void); 4319extern void syms_of_fontset (void);
4320#endif 4320#endif
4321 4321
4322/* Defined in inotify.c */
4323#ifdef HAVE_INOTIFY
4324extern void syms_of_inotify (void);
4325#endif
4326
4327/* Defined in kqueue.c */
4328#ifdef HAVE_KQUEUE
4329extern void globals_of_kqueue (void);
4330extern void syms_of_kqueue (void);
4331#endif
4332
4322/* Defined in gfilenotify.c */ 4333/* Defined in gfilenotify.c */
4323#ifdef HAVE_GFILENOTIFY 4334#ifdef HAVE_GFILENOTIFY
4324extern void globals_of_gfilenotify (void); 4335extern void globals_of_gfilenotify (void);
4325extern void syms_of_gfilenotify (void); 4336extern void syms_of_gfilenotify (void);
4326#endif 4337#endif
4327 4338
4328/* Defined in inotify.c */
4329#ifdef HAVE_INOTIFY
4330extern void syms_of_inotify (void);
4331#endif
4332
4333#ifdef HAVE_W32NOTIFY 4339#ifdef HAVE_W32NOTIFY
4334/* Defined on w32notify.c. */ 4340/* Defined on w32notify.c. */
4335extern void syms_of_w32notify (void); 4341extern void syms_of_w32notify (void);
diff --git a/test/automated/file-notify-tests.el b/test/automated/file-notify-tests.el
index 7bf7b0b3d15..de64f5086d2 100644
--- a/test/automated/file-notify-tests.el
+++ b/test/automated/file-notify-tests.el
@@ -61,11 +61,14 @@
61(defvar file-notify--test-results nil) 61(defvar file-notify--test-results nil)
62(defvar file-notify--test-event nil) 62(defvar file-notify--test-event nil)
63(defvar file-notify--test-events nil) 63(defvar file-notify--test-events nil)
64(defvar file-notify--test-expected-events nil)
65 64
66(defun file-notify--test-timeout () 65(defun file-notify--test-timeout ()
67 "Timeout to wait for arriving events, in seconds." 66 "Timeout to wait for arriving events, in seconds."
68 (if (file-remote-p temporary-file-directory) 6 3)) 67 (cond
68 ((file-remote-p temporary-file-directory) 6)
69 ((string-equal (file-notify--test-library) "w32notify") 20)
70 ((eq system-type 'cygwin) 10)
71 (t 3)))
69 72
70(defun file-notify--test-cleanup () 73(defun file-notify--test-cleanup ()
71 "Cleanup after a test." 74 "Cleanup after a test."
@@ -89,8 +92,7 @@
89 file-notify--test-tmpfile1 nil 92 file-notify--test-tmpfile1 nil
90 file-notify--test-desc nil 93 file-notify--test-desc nil
91 file-notify--test-results nil 94 file-notify--test-results nil
92 file-notify--test-events nil 95 file-notify--test-events nil)
93 file-notify--test-expected-events nil)
94 (when file-notify--test-event 96 (when file-notify--test-event
95 (error "file-notify--test-event should not be set but bound dynamically"))) 97 (error "file-notify--test-event should not be set but bound dynamically")))
96 98
@@ -133,6 +135,18 @@ being the result.")
133 ;; Return result. 135 ;; Return result.
134 (cdr file-notify--test-remote-enabled-checked)) 136 (cdr file-notify--test-remote-enabled-checked))
135 137
138(defun file-notify--test-library ()
139 "The used library for the test, as a string.
140In the remote case, it is the process name which runs on the
141remote host, or nil."
142 (if (null (file-remote-p temporary-file-directory))
143 (symbol-name file-notify--library)
144 (and (consp file-notify--test-remote-enabled-checked)
145 (processp (cdr file-notify--test-remote-enabled-checked))
146 (replace-regexp-in-string
147 "<[[:digit:]]+>\\'" ""
148 (process-name (cdr file-notify--test-remote-enabled-checked))))))
149
136(defmacro file-notify--deftest-remote (test docstring) 150(defmacro file-notify--deftest-remote (test docstring)
137 "Define ert `TEST-remote' for remote files." 151 "Define ert `TEST-remote' for remote files."
138 (declare (indent 1)) 152 (declare (indent 1))
@@ -151,12 +165,7 @@ being the result.")
151 "Test availability of `file-notify'." 165 "Test availability of `file-notify'."
152 (skip-unless (file-notify--test-local-enabled)) 166 (skip-unless (file-notify--test-local-enabled))
153 ;; Report the native library which has been used. 167 ;; Report the native library which has been used.
154 (if (null (file-remote-p temporary-file-directory)) 168 (message "Library: `%s'" (file-notify--test-library))
155 (message "Local library: `%s'" file-notify--library)
156 (message "Remote command: `%s'"
157 (replace-regexp-in-string
158 "<[[:digit:]]+>\\'" ""
159 (process-name (cdr file-notify--test-remote-enabled-checked)))))
160 (should 169 (should
161 (setq file-notify--test-desc 170 (setq file-notify--test-desc
162 (file-notify-add-watch temporary-file-directory '(change) 'ignore))) 171 (file-notify-add-watch temporary-file-directory '(change) 'ignore)))
@@ -190,12 +199,13 @@ being the result.")
190 (file-notify-add-watch 199 (file-notify-add-watch
191 temporary-file-directory '(change attribute-change) 'ignore))) 200 temporary-file-directory '(change attribute-change) 'ignore)))
192 (file-notify-rm-watch file-notify--test-desc) 201 (file-notify-rm-watch file-notify--test-desc)
193 ;; The file does not need to exist, just the upper directory. 202 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
194 (should 203 (should
195 (setq file-notify--test-desc 204 (setq file-notify--test-desc
196 (file-notify-add-watch 205 (file-notify-add-watch
197 file-notify--test-tmpfile '(change attribute-change) 'ignore))) 206 file-notify--test-tmpfile '(change attribute-change) 'ignore)))
198 (file-notify-rm-watch file-notify--test-desc) 207 (file-notify-rm-watch file-notify--test-desc)
208 (delete-file file-notify--test-tmpfile)
199 209
200 ;; Check error handling. 210 ;; Check error handling.
201 (should-error (file-notify-add-watch 1 2 3 4) 211 (should-error (file-notify-add-watch 1 2 3 4)
@@ -236,16 +246,17 @@ is bound somewhere."
236 (should 246 (should
237 (or (string-equal (file-notify--event-file-name file-notify--test-event) 247 (or (string-equal (file-notify--event-file-name file-notify--test-event)
238 file-notify--test-tmpfile) 248 file-notify--test-tmpfile)
239 (string-equal (directory-file-name 249 (string-equal (file-notify--event-file-name file-notify--test-event)
240 (file-name-directory 250 file-notify--test-tmpfile1)
241 (file-notify--event-file-name file-notify--test-event))) 251 (string-equal (file-notify--event-file-name file-notify--test-event)
242 file-notify--test-tmpfile))) 252 temporary-file-directory)))
243 ;; Check the second file name if exists. 253 ;; Check the second file name if exists.
244 (when (eq (nth 1 file-notify--test-event) 'renamed) 254 (when (eq (nth 1 file-notify--test-event) 'renamed)
245 (should 255 (should
246 (string-equal 256 (or (string-equal (file-notify--event-file1-name file-notify--test-event)
247 (file-notify--event-file1-name file-notify--test-event) 257 file-notify--test-tmpfile1)
248 file-notify--test-tmpfile1)))) 258 (string-equal (file-notify--event-file1-name file-notify--test-event)
259 temporary-file-directory)))))
249 260
250(defun file-notify--test-event-handler (event) 261(defun file-notify--test-event-handler (event)
251 "Run a test over FILE-NOTIFY--TEST-EVENT. 262 "Run a test over FILE-NOTIFY--TEST-EVENT.
@@ -254,7 +265,7 @@ and the event to `file-notify--test-events'."
254 (let* ((file-notify--test-event event) 265 (let* ((file-notify--test-event event)
255 (result 266 (result
256 (ert-run-test (make-ert-test :body 'file-notify--test-event-test)))) 267 (ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
257 ;; Do not add temporary files, this would confuse the checks. 268 ;; Do not add lock files, this would confuse the checks.
258 (unless (string-match 269 (unless (string-match
259 (regexp-quote ".#") 270 (regexp-quote ".#")
260 (file-notify--event-file-name file-notify--test-event)) 271 (file-notify--event-file-name file-notify--test-event))
@@ -278,137 +289,246 @@ TIMEOUT is the maximum time to wait for, in seconds."
278 289
279(defmacro file-notify--test-with-events (events &rest body) 290(defmacro file-notify--test-with-events (events &rest body)
280 "Run BODY collecting events and then compare with EVENTS. 291 "Run BODY collecting events and then compare with EVENTS.
281Don't wait longer than timeout seconds for the events to be delivered." 292EVENTS is either a simple list of events, or a list of lists of
293events, which represent different possible results. Don't wait
294longer than timeout seconds for the events to be delivered."
282 (declare (indent 1)) 295 (declare (indent 1))
283 (let ((outer (make-symbol "outer"))) 296 (let ((outer (make-symbol "outer")))
284 `(let ((,outer file-notify--test-events)) 297 `(let* ((,outer file-notify--test-events)
285 (setq file-notify--test-expected-events 298 (events (if (consp (car ,events)) ,events (list ,events)))
286 (append file-notify--test-expected-events ,events)) 299 (max-length (apply 'max (mapcar 'length events)))
300 create-lockfiles result)
301 ;; Flush pending events.
302 (file-notify--wait-for-events
303 (file-notify--test-timeout)
304 (input-pending-p))
287 (let (file-notify--test-events) 305 (let (file-notify--test-events)
288 ,@body 306 ,@body
289 (file-notify--wait-for-events 307 (file-notify--wait-for-events
290 (file-notify--test-timeout) 308 ;; More events need more time. Use some fudge factor.
291 (= (length ,events) (length file-notify--test-events))) 309 (* (ceiling max-length 100) (file-notify--test-timeout))
292 (should (equal ,events (mapcar #'cadr file-notify--test-events))) 310 (= max-length (length file-notify--test-events)))
311 ;; One of the possible results shall match.
312 (should
313 (dolist (elt events result)
314 (setq result
315 (or result
316 (equal elt (mapcar #'cadr file-notify--test-events))))))
293 (setq ,outer (append ,outer file-notify--test-events))) 317 (setq ,outer (append ,outer file-notify--test-events)))
294 (setq file-notify--test-events ,outer)))) 318 (setq file-notify--test-events ,outer))))
295 319
296(ert-deftest file-notify-test02-events () 320(ert-deftest file-notify-test02-events ()
297 "Check file creation/change/removal notifications." 321 "Check file creation/change/removal notifications."
298 (skip-unless (file-notify--test-local-enabled)) 322 (skip-unless (file-notify--test-local-enabled))
299 ;; Under cygwin there are so bad timings that it doesn't make sense to test.
300 (skip-unless (not (eq system-type 'cygwin)))
301
302 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
303 file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
304 323
305 (unwind-protect 324 (unwind-protect
306 (progn 325 (progn
307 ;; Check creation, change and deletion. 326 ;; Check file creation, change and deletion. It doesn't work
308 (setq file-notify--test-desc 327 ;; for cygwin and kqueue, because we don't use an implicit
309 (file-notify-add-watch 328 ;; directory monitor (kqueue), or the timings are too bad (cygwin).
310 file-notify--test-tmpfile 329 (unless (or (eq system-type 'cygwin)
311 '(change) 'file-notify--test-event-handler)) 330 (string-equal (file-notify--test-library) "kqueue"))
312 (file-notify--test-with-events '(created changed deleted) 331 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
332 (should
333 (setq file-notify--test-desc
334 (file-notify-add-watch
335 file-notify--test-tmpfile
336 '(change) 'file-notify--test-event-handler)))
337 (file-notify--test-with-events
338 (cond
339 ;; cygwin recognizes only `deleted' and `stopped' events.
340 ((eq system-type 'cygwin)
341 '(deleted stopped))
342 (t '(created changed deleted stopped)))
343 (write-region
344 "another text" nil file-notify--test-tmpfile nil 'no-message)
345 (read-event nil nil 0.1)
346 (delete-file file-notify--test-tmpfile))
347 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
348 (let (file-notify--test-events)
349 (file-notify-rm-watch file-notify--test-desc)))
350
351 ;; Check file change and deletion.
352 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
353 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
354 (should
355 (setq file-notify--test-desc
356 (file-notify-add-watch
357 file-notify--test-tmpfile
358 '(change) 'file-notify--test-event-handler)))
359 (file-notify--test-with-events
360 (cond
361 ;; cygwin recognizes only `deleted' and `stopped' events.
362 ((eq system-type 'cygwin)
363 '(deleted stopped))
364 ;; inotify and kqueue raise just one `changed' event.
365 ((or (string-equal "inotify" (file-notify--test-library))
366 (string-equal "kqueue" (file-notify--test-library)))
367 '(changed deleted stopped))
368 ;; gfilenotify raises one or two `changed' events
369 ;; randomly, no chance to test. So we accept both cases.
370 ((string-equal "gfilenotify" (file-notify--test-library))
371 '((changed deleted stopped)
372 (changed changed deleted stopped)))
373 (t '(changed changed deleted stopped)))
374 (read-event nil nil 0.1)
313 (write-region 375 (write-region
314 "any text" nil file-notify--test-tmpfile nil 'no-message) 376 "another text" nil file-notify--test-tmpfile nil 'no-message)
377 (read-event nil nil 0.1)
315 (delete-file file-notify--test-tmpfile)) 378 (delete-file file-notify--test-tmpfile))
316 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it. 379 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
317 (let (file-notify--test-events) 380 (let (file-notify--test-events)
318 (file-notify-rm-watch file-notify--test-desc)) 381 (file-notify-rm-watch file-notify--test-desc))
319 382
320 ;; Check creation, change and deletion. There must be a 383 ;; Check file creation, change and deletion when watching a
321 ;; `stopped' event when deleting the directory. It doesn't 384 ;; directory. There must be a `stopped' event when deleting
322 ;; work for w32notify. 385 ;; the directory.
323 (unless (eq file-notify--library 'w32notify) 386 (let ((temporary-file-directory
324 (make-directory file-notify--test-tmpfile) 387 (make-temp-file "file-notify-test-parent" t)))
325 (setq file-notify--test-desc 388 (should
326 (file-notify-add-watch 389 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
327 file-notify--test-tmpfile 390 file-notify--test-desc
328 '(change) 'file-notify--test-event-handler)) 391 (file-notify-add-watch
392 temporary-file-directory
393 '(change) 'file-notify--test-event-handler)))
329 (file-notify--test-with-events 394 (file-notify--test-with-events
330 ;; There are two `deleted' events, for the file and for 395 (cond
331 ;; the directory. 396 ;; w32notify does raise a `stopped' event when a
332 '(created changed deleted deleted stopped) 397 ;; watched directory is deleted.
398 ((string-equal (file-notify--test-library) "w32notify")
399 '(created changed deleted))
400 ;; cygwin recognizes only `deleted' and `stopped' events.
401 ((eq system-type 'cygwin)
402 '(deleted stopped))
403 ;; There are two `deleted' events, for the file and for
404 ;; the directory. Except for kqueue.
405 ((string-equal (file-notify--test-library) "kqueue")
406 '(created changed deleted stopped))
407 (t '(created changed deleted deleted stopped)))
408 (read-event nil nil 0.1)
333 (write-region 409 (write-region
334 "any text" nil (expand-file-name "foo" file-notify--test-tmpfile) 410 "any text" nil file-notify--test-tmpfile nil 'no-message)
335 nil 'no-message) 411 (read-event nil nil 0.1)
336 (delete-directory file-notify--test-tmpfile 'recursive)) 412 (delete-directory temporary-file-directory 'recursive))
337 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it. 413 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
338 (let (file-notify--test-events) 414 (let (file-notify--test-events)
339 (file-notify-rm-watch file-notify--test-desc))) 415 (file-notify-rm-watch file-notify--test-desc)))
340 416
341 ;; Check copy. 417 ;; Check copy of files inside a directory.
342 (setq file-notify--test-desc 418 (let ((temporary-file-directory
343 (file-notify-add-watch 419 (make-temp-file "file-notify-test-parent" t)))
344 file-notify--test-tmpfile 420 (should
345 '(change) 'file-notify--test-event-handler)) 421 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
346 (should file-notify--test-desc) 422 file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
347 (file-notify--test-with-events 423 file-notify--test-desc
348 ;; w32notify does not distinguish between `changed' and 424 (file-notify-add-watch
349 ;; `attribute-changed'. 425 temporary-file-directory
350 (if (eq file-notify--library 'w32notify) 426 '(change) 'file-notify--test-event-handler)))
351 '(created changed changed deleted) 427 (file-notify--test-with-events
352 '(created changed deleted)) 428 (cond
353 (write-region 429 ;; w32notify does not distinguish between `changed' and
354 "any text" nil file-notify--test-tmpfile nil 'no-message) 430 ;; `attribute-changed'.
355 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1) 431 ((string-equal (file-notify--test-library) "w32notify")
356 ;; The next two events shall not be visible. 432 '(created changed created changed changed changed changed
357 (set-file-modes file-notify--test-tmpfile 000) 433 deleted deleted))
358 (read-event nil nil 0.1) ; In order to distinguish the events. 434 ;; cygwin recognizes only `deleted' and `stopped' events.
359 (set-file-times file-notify--test-tmpfile '(0 0)) 435 ((eq system-type 'cygwin)
360 (delete-file file-notify--test-tmpfile) 436 '(deleted stopped))
361 (delete-file file-notify--test-tmpfile1)) 437 ;; There are three `deleted' events, for two files and
362 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it. 438 ;; for the directory. Except for kqueue.
363 (let (file-notify--test-events) 439 ((string-equal (file-notify--test-library) "kqueue")
364 (file-notify-rm-watch file-notify--test-desc)) 440 '(created changed created changed deleted stopped))
441 (t '(created changed created changed
442 deleted deleted deleted stopped)))
443 (read-event nil nil 0.1)
444 (write-region
445 "any text" nil file-notify--test-tmpfile nil 'no-message)
446 (read-event nil nil 0.1)
447 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
448 ;; The next two events shall not be visible.
449 (read-event nil nil 0.1)
450 (set-file-modes file-notify--test-tmpfile 000)
451 (read-event nil nil 0.1)
452 (set-file-times file-notify--test-tmpfile '(0 0))
453 (read-event nil nil 0.1)
454 (delete-directory temporary-file-directory 'recursive))
455 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
456 (let (file-notify--test-events)
457 (file-notify-rm-watch file-notify--test-desc)))
365 458
366 ;; Check rename. 459 ;; Check rename of files inside a directory.
367 (setq file-notify--test-desc 460 (let ((temporary-file-directory
368 (file-notify-add-watch 461 (make-temp-file "file-notify-test-parent" t)))
369 file-notify--test-tmpfile 462 (should
370 '(change) 'file-notify--test-event-handler)) 463 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
371 (should file-notify--test-desc) 464 file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
372 (file-notify--test-with-events '(created changed renamed) 465 file-notify--test-desc
373 (write-region 466 (file-notify-add-watch
374 "any text" nil file-notify--test-tmpfile nil 'no-message) 467 temporary-file-directory
375 (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1) 468 '(change) 'file-notify--test-event-handler)))
376 ;; After the rename, we won't get events anymore. 469 (file-notify--test-with-events
377 (delete-file file-notify--test-tmpfile1)) 470 (cond
378 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it. 471 ;; w32notify does not distinguish between `changed' and
379 (let (file-notify--test-events) 472 ;; `attribute-changed'.
380 (file-notify-rm-watch file-notify--test-desc)) 473 ((string-equal (file-notify--test-library) "w32notify")
474 '(created changed renamed deleted))
475 ;; cygwin recognizes only `deleted' and `stopped' events.
476 ((eq system-type 'cygwin)
477 '(deleted stopped))
478 ;; There are two `deleted' events, for the file and for
479 ;; the directory. Except for kqueue.
480 ((string-equal (file-notify--test-library) "kqueue")
481 '(created changed renamed deleted stopped))
482 (t '(created changed renamed deleted deleted stopped)))
483 (read-event nil nil 0.1)
484 (write-region
485 "any text" nil file-notify--test-tmpfile nil 'no-message)
486 (read-event nil nil 0.1)
487 (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
488 ;; After the rename, we won't get events anymore.
489 (read-event nil nil 0.1)
490 (delete-directory temporary-file-directory 'recursive))
491 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
492 (let (file-notify--test-events)
493 (file-notify-rm-watch file-notify--test-desc)))
381 494
382 ;; Check attribute change. It doesn't work for w32notify. 495 ;; Check attribute change. Does not work for cygwin.
383 (unless (eq file-notify--library 'w32notify) 496 (unless (eq system-type 'cygwin)
384 (setq file-notify--test-desc 497 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
385 (file-notify-add-watch 498 (write-region
386 file-notify--test-tmpfile 499 "any text" nil file-notify--test-tmpfile nil 'no-message)
387 '(attribute-change) 'file-notify--test-event-handler)) 500 (should
388 (file-notify--test-with-events 501 (setq file-notify--test-desc
389 (if (file-remote-p temporary-file-directory) 502 (file-notify-add-watch
390 ;; In the remote case, `write-region' raises also an 503 file-notify--test-tmpfile
391 ;; `attribute-changed' event. 504 '(attribute-change) 'file-notify--test-event-handler)))
392 '(attribute-changed attribute-changed attribute-changed) 505 (file-notify--test-with-events
393 '(attribute-changed attribute-changed)) 506 (cond
394 ;; We must use short delays between the operations. 507 ;; w32notify does not distinguish between `changed' and
395 ;; Otherwise, not all events arrive us in the remote case. 508 ;; `attribute-changed'.
396 (write-region 509 ((string-equal (file-notify--test-library) "w32notify")
397 "any text" nil file-notify--test-tmpfile nil 'no-message) 510 '(changed changed changed changed))
398 (read-event nil nil 0.1) 511 ;; For kqueue and in the remote case, `write-region'
399 (set-file-modes file-notify--test-tmpfile 000) 512 ;; raises also an `attribute-changed' event.
400 (read-event nil nil 0.1) 513 ((or (string-equal (file-notify--test-library) "kqueue")
401 (set-file-times file-notify--test-tmpfile '(0 0)) 514 (file-remote-p temporary-file-directory))
402 (read-event nil nil 0.1) 515 '(attribute-changed attribute-changed attribute-changed))
403 (delete-file file-notify--test-tmpfile)) 516 (t '(attribute-changed attribute-changed)))
517 (read-event nil nil 0.1)
518 (write-region
519 "any text" nil file-notify--test-tmpfile nil 'no-message)
520 (read-event nil nil 0.1)
521 (set-file-modes file-notify--test-tmpfile 000)
522 (read-event nil nil 0.1)
523 (set-file-times file-notify--test-tmpfile '(0 0))
524 (read-event nil nil 0.1)
525 (delete-file file-notify--test-tmpfile))
404 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it. 526 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
405 (let (file-notify--test-events) 527 (let (file-notify--test-events)
406 (file-notify-rm-watch file-notify--test-desc))) 528 (file-notify-rm-watch file-notify--test-desc)))
407 529
408 ;; Check the global sequence again just to make sure that 530 ;; Check the global sequence again just to make sure that
409 ;; `file-notify--test-events' has been set correctly. 531 ;; `file-notify--test-events' has been set correctly.
410 (should (equal (mapcar #'cadr file-notify--test-events)
411 file-notify--test-expected-events))
412 (should file-notify--test-results) 532 (should file-notify--test-results)
413 (dolist (result file-notify--test-results) 533 (dolist (result file-notify--test-results)
414 (when (ert-test-failed-p result) 534 (when (ert-test-failed-p result)
@@ -476,28 +596,31 @@ Don't wait longer than timeout seconds for the events to be delivered."
476 (should (string-match "another text" (buffer-string))) 596 (should (string-match "another text" (buffer-string)))
477 597
478 ;; Stop file notification. Autorevert shall still work via polling. 598 ;; Stop file notification. Autorevert shall still work via polling.
479 (file-notify-rm-watch auto-revert-notify-watch-descriptor) 599 ;; It doesn't work for `w32notify'.
480 (file-notify--wait-for-events 600 (unless (string-equal (file-notify--test-library) "w32notify")
481 timeout (null auto-revert-use-notify)) 601 (file-notify-rm-watch auto-revert-notify-watch-descriptor)
482 (should-not auto-revert-use-notify)
483 (should-not auto-revert-notify-watch-descriptor)
484
485 ;; Modify file. We wait for two seconds, in order to have
486 ;; another timestamp. One second seems to be too short.
487 (with-current-buffer (get-buffer-create "*Messages*")
488 (narrow-to-region (point-max) (point-max)))
489 (sleep-for 2)
490 (write-region
491 "foo bla" nil file-notify--test-tmpfile nil 'no-message)
492
493 ;; Check, that the buffer has been reverted.
494 (with-current-buffer (get-buffer-create "*Messages*")
495 (file-notify--wait-for-events 602 (file-notify--wait-for-events
496 timeout 603 timeout (null auto-revert-use-notify))
497 (string-match 604 (should-not auto-revert-use-notify)
498 (format-message "Reverting buffer `%s'." (buffer-name buf)) 605 (should-not auto-revert-notify-watch-descriptor)
499 (buffer-string)))) 606
500 (should (string-match "foo bla" (buffer-string))))) 607 ;; Modify file. We wait for two seconds, in order to
608 ;; have another timestamp. One second seems to be too
609 ;; short.
610 (with-current-buffer (get-buffer-create "*Messages*")
611 (narrow-to-region (point-max) (point-max)))
612 (sleep-for 2)
613 (write-region
614 "foo bla" nil file-notify--test-tmpfile nil 'no-message)
615
616 ;; Check, that the buffer has been reverted.
617 (with-current-buffer (get-buffer-create "*Messages*")
618 (file-notify--wait-for-events
619 timeout
620 (string-match
621 (format-message "Reverting buffer `%s'." (buffer-name buf))
622 (buffer-string))))
623 (should (string-match "foo bla" (buffer-string))))))
501 624
502 ;; Cleanup. 625 ;; Cleanup.
503 (with-current-buffer "*Messages*" (widen)) 626 (with-current-buffer "*Messages*" (widen))
@@ -510,51 +633,94 @@ Don't wait longer than timeout seconds for the events to be delivered."
510(ert-deftest file-notify-test04-file-validity () 633(ert-deftest file-notify-test04-file-validity ()
511 "Check `file-notify-valid-p' for files." 634 "Check `file-notify-valid-p' for files."
512 (skip-unless (file-notify--test-local-enabled)) 635 (skip-unless (file-notify--test-local-enabled))
513 ;; Under cygwin there are so bad timings that it doesn't make sense to test.
514 (skip-unless (not (eq system-type 'cygwin)))
515 636
516 (unwind-protect 637 (unwind-protect
517 (progn 638 (progn
518 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name) 639 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
519 file-notify--test-desc 640 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
520 (file-notify-add-watch 641 (should
521 file-notify--test-tmpfile 642 (setq file-notify--test-desc
522 '(change) #'file-notify--test-event-handler)) 643 (file-notify-add-watch
523 (file-notify--test-with-events '(created changed deleted) 644 file-notify--test-tmpfile
645 '(change) #'file-notify--test-event-handler)))
646 (should (file-notify-valid-p file-notify--test-desc))
647 ;; After calling `file-notify-rm-watch', the descriptor is not
648 ;; valid anymore.
649 (file-notify-rm-watch file-notify--test-desc)
650 (should-not (file-notify-valid-p file-notify--test-desc))
651 (delete-file file-notify--test-tmpfile))
652
653 ;; Cleanup.
654 (file-notify--test-cleanup))
655
656 (unwind-protect
657 (progn
658 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
659 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
660 (should
661 (setq file-notify--test-desc
662 (file-notify-add-watch
663 file-notify--test-tmpfile
664 '(change) #'file-notify--test-event-handler)))
665 (file-notify--test-with-events
666 (cond
667 ;; cygwin recognizes only `deleted' and `stopped' events.
668 ((eq system-type 'cygwin)
669 '(deleted stopped))
670 ;; inotify and kqueue raise just one `changed' event.
671 ((or (string-equal "inotify" (file-notify--test-library))
672 (string-equal "kqueue" (file-notify--test-library)))
673 '(changed deleted stopped))
674 ;; gfilenotify raises one or two `changed' events
675 ;; randomly, no chance to test. So we accept both cases.
676 ((string-equal "gfilenotify" (file-notify--test-library))
677 '((changed deleted stopped)
678 (changed changed deleted stopped)))
679 (t '(changed changed deleted stopped)))
524 (should (file-notify-valid-p file-notify--test-desc)) 680 (should (file-notify-valid-p file-notify--test-desc))
681 (read-event nil nil 0.1)
525 (write-region 682 (write-region
526 "any text" nil file-notify--test-tmpfile nil 'no-message) 683 "another text" nil file-notify--test-tmpfile nil 'no-message)
684 (read-event nil nil 0.1)
527 (delete-file file-notify--test-tmpfile)) 685 (delete-file file-notify--test-tmpfile))
528 ;; After deleting the file, the descriptor is still valid. 686 ;; After deleting the file, the descriptor is not valid anymore.
529 (should (file-notify-valid-p file-notify--test-desc)) 687 (should-not (file-notify-valid-p file-notify--test-desc))
530 ;; After removing the watch, the descriptor must not be valid 688 (file-notify-rm-watch file-notify--test-desc))
531 ;; anymore.
532 (file-notify-rm-watch file-notify--test-desc)
533 (should-not (file-notify-valid-p file-notify--test-desc)))
534 689
535 ;; Cleanup. 690 ;; Cleanup.
536 (file-notify--test-cleanup)) 691 (file-notify--test-cleanup))
537 692
538 (unwind-protect 693 (unwind-protect
539 ;; The batch-mode operation of w32notify is fragile (there's no 694 ;; w32notify does not send a `stopped' event when deleting a
540 ;; input threads to send the message to). 695 ;; directory. The test does not work, therefore.
541 ;(unless (and noninteractive (eq file-notify--library 'w32notify)) 696 (unless (string-equal (file-notify--test-library) "w32notify")
542 (unless (eq file-notify--library 'w32notify) 697 (let ((temporary-file-directory
543 (let ((temporary-file-directory
544 (make-temp-file "file-notify-test-parent" t))) 698 (make-temp-file "file-notify-test-parent" t)))
545 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name) 699 (should
546 file-notify--test-desc 700 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
547 (file-notify-add-watch 701 file-notify--test-desc
548 file-notify--test-tmpfile 702 (file-notify-add-watch
549 '(change) #'file-notify--test-event-handler)) 703 temporary-file-directory
550 (file-notify--test-with-events '(created changed deleted stopped) 704 '(change) #'file-notify--test-event-handler)))
551 (should (file-notify-valid-p file-notify--test-desc)) 705 (file-notify--test-with-events
552 (write-region 706 (cond
553 "any text" nil file-notify--test-tmpfile nil 'no-message) 707 ;; cygwin recognizes only `deleted' and `stopped' events.
708 ((eq system-type 'cygwin)
709 '(deleted stopped))
710 ;; There are two `deleted' events, for the file and for
711 ;; the directory. Except for kqueue.
712 ((string-equal (file-notify--test-library) "kqueue")
713 '(created changed deleted stopped))
714 (t '(created changed deleted deleted stopped)))
715 (should (file-notify-valid-p file-notify--test-desc))
716 (read-event nil nil 0.1)
717 (write-region
718 "any text" nil file-notify--test-tmpfile nil 'no-message)
719 (read-event nil nil 0.1)
554 (delete-directory temporary-file-directory t)) 720 (delete-directory temporary-file-directory t))
555 ;; After deleting the parent directory, the descriptor must 721 ;; After deleting the parent directory, the descriptor must
556 ;; not be valid anymore. 722 ;; not be valid anymore.
557 (should-not (file-notify-valid-p file-notify--test-desc)))) 723 (should-not (file-notify-valid-p file-notify--test-desc))))
558 724
559 ;; Cleanup. 725 ;; Cleanup.
560 (file-notify--test-cleanup))) 726 (file-notify--test-cleanup)))
@@ -571,10 +737,11 @@ Don't wait longer than timeout seconds for the events to be delivered."
571 (setq file-notify--test-tmpfile 737 (setq file-notify--test-tmpfile
572 (file-name-as-directory (file-notify--test-make-temp-name))) 738 (file-name-as-directory (file-notify--test-make-temp-name)))
573 (make-directory file-notify--test-tmpfile) 739 (make-directory file-notify--test-tmpfile)
574 (setq file-notify--test-desc 740 (should
575 (file-notify-add-watch 741 (setq file-notify--test-desc
576 file-notify--test-tmpfile 742 (file-notify-add-watch
577 '(change) #'file-notify--test-event-handler)) 743 file-notify--test-tmpfile
744 '(change) #'file-notify--test-event-handler)))
578 (should (file-notify-valid-p file-notify--test-desc)) 745 (should (file-notify-valid-p file-notify--test-desc))
579 ;; After removing the watch, the descriptor must not be valid 746 ;; After removing the watch, the descriptor must not be valid
580 ;; anymore. 747 ;; anymore.
@@ -590,20 +757,22 @@ Don't wait longer than timeout seconds for the events to be delivered."
590 (unwind-protect 757 (unwind-protect
591 ;; The batch-mode operation of w32notify is fragile (there's no 758 ;; The batch-mode operation of w32notify is fragile (there's no
592 ;; input threads to send the message to). 759 ;; input threads to send the message to).
593 (unless (and noninteractive (eq file-notify--library 'w32notify)) 760 (unless (and noninteractive
761 (string-equal (file-notify--test-library) "w32notify"))
594 (setq file-notify--test-tmpfile 762 (setq file-notify--test-tmpfile
595 (file-name-as-directory (file-notify--test-make-temp-name))) 763 (file-name-as-directory (file-notify--test-make-temp-name)))
596 (make-directory file-notify--test-tmpfile) 764 (make-directory file-notify--test-tmpfile)
597 (setq file-notify--test-desc 765 (should
598 (file-notify-add-watch 766 (setq file-notify--test-desc
599 file-notify--test-tmpfile 767 (file-notify-add-watch
600 '(change) #'file-notify--test-event-handler)) 768 file-notify--test-tmpfile
769 '(change) #'file-notify--test-event-handler)))
601 (should (file-notify-valid-p file-notify--test-desc)) 770 (should (file-notify-valid-p file-notify--test-desc))
602 ;; After deleting the directory, the descriptor must not be 771 ;; After deleting the directory, the descriptor must not be
603 ;; valid anymore. 772 ;; valid anymore.
604 (delete-directory file-notify--test-tmpfile t) 773 (delete-directory file-notify--test-tmpfile t)
605 (file-notify--wait-for-events 774 (file-notify--wait-for-events
606 (file-notify--test-timeout) 775 (file-notify--test-timeout)
607 (not (file-notify-valid-p file-notify--test-desc))) 776 (not (file-notify-valid-p file-notify--test-desc)))
608 (should-not (file-notify-valid-p file-notify--test-desc))) 777 (should-not (file-notify-valid-p file-notify--test-desc)))
609 778
@@ -613,6 +782,62 @@ Don't wait longer than timeout seconds for the events to be delivered."
613(file-notify--deftest-remote file-notify-test05-dir-validity 782(file-notify--deftest-remote file-notify-test05-dir-validity
614 "Check `file-notify-valid-p' via file notification for remote directories.") 783 "Check `file-notify-valid-p' via file notification for remote directories.")
615 784
785(ert-deftest file-notify-test06-many-events ()
786 "Check that events are not dropped."
787 :tags '(:expensive-test)
788 (skip-unless (file-notify--test-local-enabled))
789 ;; Under cygwin events arrive in random order. Impossible to define a test.
790 (skip-unless (not (eq system-type 'cygwin)))
791
792 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
793 (make-directory file-notify--test-tmpfile)
794 (should
795 (setq file-notify--test-desc
796 (file-notify-add-watch
797 file-notify--test-tmpfile
798 '(change) 'file-notify--test-event-handler)))
799 (unwind-protect
800 (let ((n 1000)
801 source-file-list target-file-list
802 (default-directory file-notify--test-tmpfile))
803 (dotimes (i n)
804 ;; It matters which direction we rename, at least for
805 ;; kqueue. This backend parses directories in alphabetic
806 ;; order (x%d before y%d). So we rename both directions.
807 (if (zerop (mod i 2))
808 (progn
809 (push (expand-file-name (format "x%d" i)) source-file-list)
810 (push (expand-file-name (format "y%d" i)) target-file-list))
811 (push (expand-file-name (format "y%d" i)) source-file-list)
812 (push (expand-file-name (format "x%d" i)) target-file-list)))
813 (file-notify--test-with-events (make-list (+ n n) 'created)
814 (let ((source-file-list source-file-list)
815 (target-file-list target-file-list))
816 (while (and source-file-list target-file-list)
817 (read-event nil nil 0.1)
818 (write-region "" nil (pop source-file-list) nil 'no-message)
819 (read-event nil nil 0.1)
820 (write-region "" nil (pop target-file-list) nil 'no-message))))
821 (file-notify--test-with-events
822 (cond
823 ;; w32notify fires both `deleted' and `renamed' events.
824 ((string-equal (file-notify--test-library) "w32notify")
825 (let (r)
826 (dotimes (_i n r)
827 (setq r (append '(deleted renamed) r)))))
828 (t (make-list n 'renamed)))
829 (let ((source-file-list source-file-list)
830 (target-file-list target-file-list))
831 (while (and source-file-list target-file-list)
832 (rename-file (pop source-file-list) (pop target-file-list) t))))
833 (file-notify--test-with-events (make-list n 'deleted)
834 (dolist (file target-file-list)
835 (delete-file file))))
836 (file-notify--test-cleanup)))
837
838(file-notify--deftest-remote file-notify-test06-many-events
839 "Check that events are not dropped for remote directories.")
840
616(defun file-notify-test-all (&optional interactive) 841(defun file-notify-test-all (&optional interactive)
617 "Run all tests for \\[file-notify]." 842 "Run all tests for \\[file-notify]."
618 (interactive "p") 843 (interactive "p")
@@ -623,7 +848,7 @@ Don't wait longer than timeout seconds for the events to be delivered."
623;; TODO: 848;; TODO:
624 849
625;; * For w32notify, no stopped events arrive when a directory is removed. 850;; * For w32notify, no stopped events arrive when a directory is removed.
626;; * Try to handle arriving events under cygwin reliably. 851;; * Check, why cygwin recognizes only `deleted' and `stopped' events.
627 852
628(provide 'file-notify-tests) 853(provide 'file-notify-tests)
629;;; file-notify-tests.el ends here 854;;; file-notify-tests.el ends here