master 07925d70700: Improve handling of pending `renamed' event in filenotify.el

Michael Albinus via Mailing list for Emacs changes <[email protected]>
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 07925d707000288e933ea8239e8c3c7189efb899
Author: Michael Albinus <[email protected]>
Commit: Michael Albinus <[email protected]>

    Improve handling of pending `renamed' event in filenotify.el
    
    * lisp/filenotify.el (file-notify--rm-descriptor): Remove.
    (file-notify--expand-file-name): Use `file-local-name'.
    (file-notify--callback-inotify, file-notify--callback-kqueue)
    (file-notify--callback-w32notify)
    (file-notify--callback-gfilenotify, file-notify-callback): Map
    also `stopped' event.
    (file-notify--call-handler): Handle also `stopped' event.
    (file-notify--handle-event): Improve handling of pending `renamed'
    event.  (Bug#81531)
    (file-notify-rm-watch): Integrate code of removed
    `file-notify--rm-descriptor'.
    
    * test/lisp/filenotify-tests.el (file-notify--test-tmpdir1): Declare.
    (file-notify--test-event-test): Better check for `stopped' events.
    (file-notify-test03-events, file-notify-test05-file-validity)
    (file-notify-test06-dir-validity)
    (file-notify-test07-many-events, file-notify-test08-backup)
    (file-notify-test09-watched-file-in-watched-dir): Adapt tests.
    (file-notify-test10-move-file-to-another-watched-dir): New test.
    (file-notify-test11-sufficient-resources)
    (file-notify-test12-symlinks): Rename tests.
---
 lisp/filenotify.el            | 182 ++++++++++++++++++++++--------------------
 test/lisp/filenotify-tests.el | 174 +++++++++++++++++++++++++++++-----------
 2 files changed, 224 insertions(+), 132 deletions(-)

diff --git a/lisp/filenotify.el b/lisp/filenotify.el
index d69fa91aff5..c65f8bc83ec 100644
--- a/lisp/filenotify.el
+++ b/lisp/filenotify.el
@@ -72,22 +72,6 @@ A key in this hash table is the descriptor as returned from
 handler.  The value in the hash table is a `file-notify--watch'
 struct.")
 
-(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."
-  (when-let* ((watch (gethash descriptor file-notify-descriptors)))
-    (unwind-protect
-        ;; Insert `stopped' event.
-        (insert-special-event
-         (make-file-notify
-          :-event `(,descriptor stopped
-                    ,(file-notify--watch-absolute-filename watch))
-          :-callback (file-notify--watch-callback watch)))
-      ;; Make sure this is the last time the callback was invoked.
-      (setf (file-notify--watch-callback watch) nil)
-      (remhash descriptor file-notify-descriptors))))
-
 (cl-defstruct (file-notify (:type list) :named)
   "A file system monitoring event, coming from the backends."
   -event -callback)
@@ -124,7 +108,8 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
   "Full file name of FILE reported for WATCH."
   (directory-file-name
    (if (file-name-absolute-p file)
-       (concat (file-remote-p (file-notify--watch-directory watch)) file)
+       (concat (file-remote-p (file-notify--watch-directory watch))
+               (file-local-name file))
      (expand-file-name file (file-notify--watch-directory watch)))))
 
 (cl-defun file-notify--callback-inotify ((desc actions file
@@ -140,7 +125,7 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
                         ((memq action '(delete delete-self move-self)) 'deleted)
                         ((eq action 'moved-from) 'renamed-from)
                         ((eq action 'moved-to) 'renamed-to)
-                        ((memq action '(ignored unmount)) 'stopped)))
+                        ((memq action '(stopped ignored unmount)) 'stopped)))
                      actions))
    file file1-or-cookie))
 
@@ -156,7 +141,7 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
                         ((memq action '(attrib link)) 'attribute-changed)
                         ((eq action 'delete) 'deleted)
                         ((eq action 'rename) 'renamed)
-                        ((eq action 'revoke) 'stopped)))
+                        ((memq action '(stopped revoke)) 'stopped)))
                      actions))
    file file1-or-cookie))
 
@@ -168,7 +153,8 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
                  ('modified 'changed)
                  ('removed 'deleted)
                  ('renamed-from 'renamed-from)
-                 ('renamed-to 'renamed-to))))
+                 ('renamed-to 'renamed-to)
+                 ('stopped 'stopped))))
     (when action
       (file-notify--handle-event desc (list action) file file1-or-cookie))))
 
@@ -183,7 +169,7 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
                                '(created changed attribute-changed deleted))
                          action)
                         ((eq action 'moved) 'renamed)
-                        ((eq action 'unmounted) 'stopped)))
+                        ((memq action '(stopped unmounted)) 'stopped)))
                      (if (consp actions) actions (list actions))))
    file file1-or-cookie))
 
@@ -195,6 +181,8 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
    (delq nil (mapcar
               (lambda (action)
                 (cond
+                 ;; Synthetic event.
+                 ((eq action 'stopped) 'stopped)
                  ;; gfilenotify actions:
                  ((memq action '(created changed attribute-changed deleted))
                   action)
@@ -219,6 +207,8 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
 (defun file-notify--call-handler (watch desc action file file1)
   "Call the handler of WATCH with the arguments DESC, ACTION, FILE and FILE1."
   (when (or
+         ;; A stop action shall always be handled.
+         (eq action 'stopped)
          ;; If there is no relative file name for that
          ;; watch, we watch the whole directory.
          (null (file-notify--watch-filename watch))
@@ -240,7 +230,7 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
          (and (stringp file1)
               (string-equal (file-notify--watch-filename watch)
                             (file-name-nondirectory file1))))
-    ;; The callback could have removed in `file-notify--rm-descriptor'.
+    ;; The callback could have been removed in `file-notify-rm-watch'.
     (when (file-notify--watch-callback watch)
       (when file-notify-debug
         (message
@@ -264,11 +254,12 @@ DESC is the back-end descriptor.  ACTIONS is a list of:
  `renamed-from'      -- FILE is old name, FILE1-OR-COOKIE is cookie or nil
  `renamed-to'        -- FILE is new name, FILE1-OR-COOKIE is cookie or nil
  `stopped'           -- no more events after this should be sent"
-  (let* ((watch (gethash desc file-notify-descriptors))
-         (file (and watch (file-notify--expand-file-name watch file))))
-    (when watch
-      (while actions
-        (let ((action (pop actions)))
+  (when-let* ((watch (gethash desc file-notify-descriptors))
+              (file (file-notify--expand-file-name watch file)))
+    (while actions
+      (let ((action (pop actions))
+            (file1 nil))
+        (when watch
           ;; We only handle {renamed,moved}-{from,to} pairs when these
           ;; arrive in order without anything else in-between.
           ;; If there is a pending rename that does not match this event,
@@ -279,68 +270,72 @@ DESC is the back-end descriptor.  ACTIONS is a list of:
                                  file-notify--pending-rename)
                                 file1-or-cookie)
                          (eq action 'renamed-to))
-              (let ((callback (file-notify--watch-callback
-                               (file-notify--rename-watch
-                                file-notify--pending-rename))))
-                (when callback
-                  (funcall callback (list (file-notify--rename-desc
-                                           file-notify--pending-rename)
-                                          'deleted
-                                          (file-notify--rename-from-file
-                                           file-notify--pending-rename))))
-                (setq file-notify--pending-rename nil))))
-
-          (let ((file1 nil))
-            (cond
-             ((eq action 'renamed)
-              ;; A `renamed' event may not have a destination name;
-              ;; if none, treat it as a deletion.
-              (if file1-or-cookie
-                  (setq file1
-                        (file-notify--expand-file-name watch file1-or-cookie))
-                (setq action 'deleted)))
-             ((eq action 'stopped)
-              (file-notify-rm-watch desc)
-              (setq actions nil
-                    action nil))
-             ;; Make the event pending.
-             ((eq action 'renamed-from)
-              (setq file-notify--pending-rename
-                    (file-notify--rename-make watch desc file file1-or-cookie)
-                    action nil))
-             ;; Look for pending event.
-             ((eq action 'renamed-to)
-              (if file-notify--pending-rename
-                  (let ((callback (file-notify--watch-callback
-                                   (file-notify--rename-watch
-                                    file-notify--pending-rename)))
-                        (pending-desc (file-notify--rename-desc
-                                       file-notify--pending-rename))
-                        (from-file (file-notify--rename-from-file
-                                    file-notify--pending-rename)))
-                    (setq file1 file
-                          file from-file)
-                    ;; If the source is handled by another watch, we
-                    ;; must fire the rename event there as well.
-                    (when (and (not (equal desc pending-desc))
-                               callback)
-                      (funcall callback
-                               (list pending-desc 'renamed file file1)))
-                    (setq file-notify--pending-rename nil
-                          action 'renamed))
-                (setq action 'created))))
-
-            (when action
-              (file-notify--call-handler watch desc action file file1))
-
-            ;; Send `stopped' event.
-            (when (and (memq action '(deleted renamed))
+              (file-notify--call-handler
+               (file-notify--rename-watch file-notify--pending-rename)
+               (file-notify--rename-desc file-notify--pending-rename)
+               'deleted
+               (file-notify--rename-from-file file-notify--pending-rename)
+               nil)
+              (setq file-notify--pending-rename nil)))
+
+          (cond
+           ((eq action 'renamed)
+            ;; A `renamed' event may not have a destination name;
+            ;; if none, treat it as a deletion.
+            (if file1-or-cookie
+                (setq file1
+                      (file-notify--expand-file-name watch file1-or-cookie))
+              (setq action 'deleted)))
+           ;; Make the event pending.
+           ((eq action 'renamed-from)
+            (setq file-notify--pending-rename
+                  (file-notify--rename-make watch desc file file1-or-cookie)
+                  action nil))
+           ;; Look for pending event.
+           ((eq action 'renamed-to)
+            (if file-notify--pending-rename
+                (let ((pending-watch (file-notify--rename-watch
+                                      file-notify--pending-rename))
+                      (pending-desc (file-notify--rename-desc
+                                     file-notify--pending-rename))
+                      (from-file (file-notify--rename-from-file
+                                  file-notify--pending-rename)))
+                  (setq file1 file
+                        file from-file)
+                  ;; If the source is handled by another watch, we
+                  ;; must fire the rename event there as well.
+                  (unless (equal desc pending-desc)
+                    (file-notify--call-handler
+                     pending-watch pending-desc 'renamed file file1))
+                  (setq file-notify--pending-rename nil
+                        action 'renamed))
+              (setq action 'created))))
+
+          (when action
+            (file-notify--call-handler watch desc action file file1)))
+
+        ;; Stop handling.
+        (when (or (eq action 'stopped)
+                  (and watch (memq action '(deleted renamed))
                        ;; Not when a file is backed up.
                        (not (and (stringp file1) (backup-file-name-p file1)))
                        ;; Watched file or directory is concerned.
                        (string-equal
-                        file (file-notify--watch-absolute-filename watch)))
-              (file-notify-rm-watch desc))))))))
+                        file (file-notify--watch-absolute-filename watch))))
+          ;; Fire pending `renamed-from' event.
+          (when file-notify--pending-rename
+            (file-notify--call-handler
+             (file-notify--rename-watch file-notify--pending-rename)
+             (file-notify--rename-desc file-notify--pending-rename)
+             'deleted
+             (file-notify--rename-from-file file-notify--pending-rename)
+             nil)
+            (setq file-notify--pending-rename nil))
+          (setq actions nil)
+          ;; Make sure this is the last time the callback was invoked.
+          (when (eq action 'stopped)
+            (setf (file-notify--watch-callback watch) nil))
+          (file-notify-rm-watch desc))))))
 
 (declare-function inotify-add-watch "inotify.c" (file flags callback))
 (declare-function kqueue-add-watch "kqueue.c" (file flags callback))
@@ -491,8 +486,21 @@ DESCRIPTOR should be an object returned by `file-notify-add-watch'."
                 ((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
                descriptor))
           (file-notify-error nil)))
-      ;; Modify `file-notify-descriptors' and send a `stopped' event.
-      (file-notify--rm-descriptor descriptor))))
+
+      ;; Send a `stopped' event.
+      (unwind-protect
+          ;; Insert `stopped' event.
+          (insert-special-event
+           (make-file-notify
+            :-event `(,descriptor stopped
+                    ,(file-notify--watch-absolute-filename watch))
+            :-callback 'file-notify-callback))
+        (read-event nil nil 0.01)
+        ;; Make sure this is the last time the callback was invoked.
+        (setf (file-notify--watch-callback watch) nil)))
+
+    ;; Remove descriptor.
+    (remhash descriptor file-notify-descriptors)))
 
 (defun file-notify-rm-all-watches ()
   "Remove all existing file notification watches from Emacs."
diff --git a/test/lisp/filenotify-tests.el b/test/lisp/filenotify-tests.el
index 798253aeb0c..1a3258c1642 100644
--- a/test/lisp/filenotify-tests.el
+++ b/test/lisp/filenotify-tests.el
@@ -66,6 +66,7 @@
 
 (defvar file-notify--test-rootdir temporary-file-directory)
 (defvar file-notify--test-tmpdir nil)
+(defvar file-notify--test-tmpdir1 nil)
 (defvar file-notify--test-tmpfile nil)
 (defvar file-notify--test-tmpfile1 nil)
 (defvar file-notify--test-desc nil)
@@ -168,6 +169,7 @@ Return nil when any other file notification watch is still active."
 
   (setq auto-revert-buffer-list nil
         file-notify--test-tmpdir nil
+        file-notify--test-tmpdir1 nil
         file-notify--test-tmpfile nil
         file-notify--test-tmpfile1 nil
         file-notify--test-desc nil
@@ -192,6 +194,10 @@ Return nil when any other file notification watch is still active."
       tramp-allow-unsafe-temporary-files
       (or tramp-allow-unsafe-temporary-files noninteractive))
 
+;; (setq file-notify-debug t)
+;; (dolist (elt (apropos-internal (rx bos "file-notify-") #'functionp))
+;;   (trace-function-background elt))
+
 (defun file-notify--test-add-watch (file flags callback)
   "Like `file-notify-add-watch', but also passing FILE to CALLBACK."
   (file-notify-add-watch
@@ -243,9 +249,9 @@ remote host, or nil."
 
 (defun file-notify--test-monitor ()
   "The used monitor for the test, as a symbol.
-This returns only for (local) gfilenotify, (remote) gio or (remote)
-smb-notify libraries; otherwise it is nil.  `file-notify--test-desc'
-must be a valid watch descriptor."
+This returns only for (local) gfilenotify, (remote) gio, (remote)
+smb-notify or (remote) tramp-rpc libraries; otherwise it is nil.
+`file-notify--test-desc' must be a valid watch descriptor."
   ;; We cache the result, because after `file-notify-rm-watch',
   ;; `gfile-monitor-name' does not return a proper result anymore.
   ;; But we still need this information.  So far, we know the monitors
@@ -530,10 +536,11 @@ and `file-notify--test-file' are bound somewhere."
     (should (equal (file-notify--test-event-desc file-notify--test-event)
                    file-notify--test-desc)))
   ;; Check the file name.
-  (should
-   (string-prefix-p
-    file-notify--test-file
-    (file-notify--test-event-file file-notify--test-event)))
+  (unless (eq (file-notify--test-event-action file-notify--test-event) 'stopped)
+    (should
+     (string-prefix-p
+      file-notify--test-file
+      (file-notify--test-event-file file-notify--test-event))))
   ;; Check the second file name if exists.
   (when (eq (file-notify--test-event-action file-notify--test-event) 'renamed)
     (should
@@ -733,14 +740,12 @@ delivered."
 	((eq (file-notify--test-monitor) 'SMBSamba)
          '(created changed changed changed deleted deleted stopped))
 	;; There are two `deleted' events, for the file and for the
-	;; directory.  Except for GFam{File,Directory}Monitor,
-	;; GPollFileMonitor and kqueue.  And GFam{File,Directory}Monitor
-	;; and GPollFileMonitor do not raise a `changed' event.
+	;; directory.  Except for GFam{File,Directory}Monitor and
+	;; GPollFileMonitor.  And GFam{File,Directory}Monitor and
+	;; GPollFileMonitor do not raise a `changed' event.
 	((memq (file-notify--test-monitor)
                '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
 	 '(created deleted stopped))
-	((string-equal (file-notify--test-library) "kqueue")
-	 '(created changed deleted stopped))
 	;; GKqueueFileMonitor does not report the `changed' event.
 	((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
 	 '(created deleted deleted stopped))
@@ -783,13 +788,14 @@ delivered."
            '(created changed changed changed created changed changed changed
              changed changed deleted deleted deleted stopped))
 	  ;; There are three `deleted' events, for two files and for the
-	  ;; directory.  Except for GFam{File,Directory}Monitor,
-	  ;; GPollFileMonitor and kqueue.
+	  ;; directory.  Except for GFam{File,Directory}Monitor and
+	  ;; GPollFileMonitor.
 	  ((memq (file-notify--test-monitor)
                  '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
 	   '(created created changed changed deleted stopped))
+	  ;; kqueue reports two `deleted' events.
 	  ((string-equal (file-notify--test-library) "kqueue")
-	   '(created changed created changed deleted stopped))
+	   '(created changed created changed deleted deleted stopped))
 	  ;; GKqueueFileMonitor does not report the `changed' event.
 	  ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
 	   '(created created deleted deleted deleted stopped))
@@ -834,16 +840,13 @@ delivered."
            '(created changed changed changed
              renamed changed changed deleted deleted stopped))
 	  ;; There are two `deleted' events, for the file and for the
-	  ;; directory.  Except for GFam{File,Directory}Monitor,
-	  ;; GPollfileMonitor and kqueue.  And
-	  ;; GFam{File,Directory}Monitor and GPollFileMonitor raise
-	  ;; `created' and `deleted' events instead of a `renamed'
-	  ;; event.
+	  ;; directory.  Except for GFam{File,Directory}Monitor and
+	  ;; GPollfileMonitor.  And GFam{File,Directory}Monitor and
+	  ;; GPollFileMonitor raise `created' and `deleted' events
+	  ;; instead of a `renamed' event.
 	  ((memq (file-notify--test-monitor)
                  '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
 	   '(created created deleted deleted stopped))
-	  ((string-equal (file-notify--test-library) "kqueue")
-	   '(created changed renamed deleted stopped))
 	  ;; GKqueueFileMonitor does not report the `changed' event.
 	  ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
 	   '(created renamed deleted deleted stopped))
@@ -1093,14 +1096,12 @@ delivered."
 	((eq (file-notify--test-monitor) 'SMBSamba)
          '(created changed changed changed deleted deleted stopped))
 	;; There are two `deleted' events, for the file and for the
-	;; directory.  Except for GFam{File,Directory}Monitor,
-	;; GPollFileMonitor and kqueue.  And GFam{File,Directory}Monitor
-	;; and GPollfileMonitor do not raise a `changed' event.
+	;; directory.  Except for GFam{File,Directory}Monitor and
+	;; GPollFileMonitor.  And GFam{File,Directory}Monitor and
+	;; GPollfileMonitor do not raise a `changed' event.
 	((memq (file-notify--test-monitor)
                '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
 	 '(created deleted stopped))
-	((string-equal (file-notify--test-library) "kqueue")
-	 '(created changed deleted stopped))
 	;; GKqueueFileMonitor does not report the `changed' event.
 	((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
 	 '(created deleted deleted stopped))
@@ -1116,7 +1117,7 @@ delivered."
    ;; filenotify.el to remove the descriptor from the internal hash
    ;; table it maintains.  So we must remove the descriptor manually.
    (if (string-equal (file-notify--test-library) "w32notify")
-       (file-notify--rm-descriptor file-notify--test-desc))
+       (file-notify-rm-watch file-notify--test-desc))
 
    ;; The environment shall be cleaned up.
    (file-notify--test-cleanup-p)))
@@ -1159,7 +1160,7 @@ delivered."
     (not (file-notify-valid-p file-notify--test-desc)))
    (should-not (file-notify-valid-p file-notify--test-desc))
    (if (string-equal (file-notify--test-library) "w32notify")
-       (file-notify--rm-descriptor file-notify--test-desc))
+       (file-notify-rm-watch file-notify--test-desc))
 
    ;; The environment shall be cleaned up.
    (file-notify--test-cleanup-p)))
@@ -1245,7 +1246,7 @@ delivered."
        (dolist (file target-file-list)
          (file-notify--test-wait-event)
          (delete-file file)))
-     (file-notify--rm-descriptor file-notify--test-desc)
+     (file-notify-rm-watch file-notify--test-desc)
 
      ;; The environment shall be cleaned up.
      (file-notify--test-cleanup-p))))
@@ -1291,7 +1292,7 @@ delivered."
          (save-buffer))))
    ;; After saving the buffer, the descriptor is still valid.
    (should (file-notify-valid-p file-notify--test-desc))
-   (file-notify--rm-descriptor file-notify--test-desc)
+   (file-notify-rm-watch file-notify--test-desc)
 
    ;; The environment shall be cleaned up.
    (file-notify--test-cleanup-p))
@@ -1337,7 +1338,7 @@ delivered."
            (save-buffer))))
      ;; After saving the buffer, the descriptor is still valid.
      (should (file-notify-valid-p file-notify--test-desc))
-     (file-notify--rm-descriptor file-notify--test-desc)
+     (file-notify-rm-watch file-notify--test-desc)
 
      ;; The environment shall be cleaned up.
      (file-notify--test-cleanup-p)))))
@@ -1385,7 +1386,7 @@ the file watch."
        (file-notify--test-with-actions
            ;; There could be one or two `changed' events.
            (list
-            ;; SMBSamba.  Sometimes, tha last `changed' event is
+            ;; SMBSamba.  Sometimes, the last `changed' event is
             ;; missing, so we add two alternatives.
             (append
              '(:random)
@@ -1450,7 +1451,8 @@ the file watch."
      ;; file monitor are triggered.
      (file-notify--test-with-actions
          '((:random deleted deleted stopped)
-           (:random deleted deleted deleted stopped))
+           (:random deleted deleted deleted stopped)
+           (:random deleted deleted deleted deleted stopped))
        (delete-file file-notify--test-tmpfile))
      (should (file-notify-valid-p file-notify--test-desc1))
      (unless (string-equal (file-notify--test-library) "w32notify")
@@ -1459,13 +1461,14 @@ the file watch."
      ;; Now we delete the directory.
      (file-notify--test-with-actions
          (cond
-          ;; GFam{File,Directory}Monitor, GPollFileMonitor and kqueue
-          ;; raise just one `deleted' event for the directory.
+          ;; GFam{File,Directory}Monitor and GPollFileMonitor raise
+          ;; just one `deleted' event for the directory.
 	  ((memq (file-notify--test-monitor)
                  '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
            '(deleted stopped))
+	  ;; kqueue raises two `deleted' events.
 	  ((string-equal (file-notify--test-library) "kqueue")
-           '(deleted stopped))
+           '(deleted deleted stopped))
           (t (append
               ;; The directory monitor raises a `deleted' event for
               ;; every file contained in the directory, we must count
@@ -1488,8 +1491,8 @@ the file watch."
        (should-not (file-notify-valid-p file-notify--test-desc1))
        (should-not (file-notify-valid-p file-notify--test-desc2)))
      (when (string-equal (file-notify--test-library) "w32notify")
-       (file-notify--rm-descriptor file-notify--test-desc1)
-       (file-notify--rm-descriptor file-notify--test-desc2))
+       (file-notify-rm-watch file-notify--test-desc1)
+       (file-notify-rm-watch file-notify--test-desc2))
 
      ;; The environment shall be cleaned up.
      (file-notify--test-cleanup-p))))
@@ -1497,7 +1500,88 @@ the file watch."
 (file-notify--deftest-remote file-notify-test09-watched-file-in-watched-dir
   "Check `file-notify-test09-watched-file-in-watched-dir' for remote files.")
 
-(ert-deftest file-notify-test10-sufficient-resources ()
+(ert-deftest file-notify-test10-move-file-to-another-watched-dir ()
+  "Watches two directories, and move a file from one directory to the other one."
+  :tags '(:expensive-test)
+  (skip-unless (file-notify--test-local-enabled))
+  ;; This works only for inotify-based backends.
+  (skip-unless
+   (or (member (file-notify--test-library) '("inotify" "inotifywait"))
+       (ignore-errors
+         ;; `file-notify--test-desc' is needed for `file-notify--test-monitor'.
+         (when-let* ((file-notify--test-desc
+                      (file-notify-add-watch
+                       file-notify--test-rootdir '(change) #'ignore)))
+           (prog1 (memq (file-notify--test-monitor)
+                        '(GInotifyFileMonitor TrampRPCinotify))
+             (file-notify-rm-watch file-notify--test-desc)
+             (file-notify--test-cleanup-p))))))
+
+  (with-file-notify-test
+   (setq file-notify--test-tmpdir1 file-notify--test-tmpdir
+         file-notify--test-tmpfile1 file-notify--test-tmpfile)
+  (with-file-notify-test
+   (setq file-notify--test-tmpdir file-notify--test-tmpdir
+         file-notify--test-tmpfile file-notify--test-tmpfile)
+
+   (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
+   (should
+    (setq file-notify--test-desc1
+          (file-notify--test-add-watch
+           file-notify--test-tmpdir1
+           '(change) #'file-notify--test-event-handler)))
+   (should
+    (setq file-notify--test-desc
+          (file-notify--test-add-watch
+           file-notify--test-tmpdir
+           '(change) #'file-notify--test-event-handler)))
+   (should (file-notify-valid-p file-notify--test-desc1))
+   (should (file-notify-valid-p file-notify--test-desc))
+   (should-not (equal file-notify--test-desc1 file-notify--test-desc))
+
+   ;; `file-notify--test-event-test' would signal a false alarm.
+   (cl-letf* (((symbol-function #'file-notify--test-event-test) #'ignore))
+     (file-notify--test-with-actions '(renamed renamed)
+       ;; Both file notification watches receive the `renamed' action.
+       (rename-file file-notify--test-tmpfile1 file-notify--test-tmpdir)))
+
+   ;; If one file notification watch has been removed, the synthesis of
+   ;; `move-to' and `move-from' doesn't work anymore due to different
+   ;; cookies.  With one exception.
+   (file-notify-rm-watch file-notify--test-desc1)
+   (file-notify--test-with-actions
+       (cond
+        ;; GInotifyFileMonitor uses `moved' instead of `moved-from' and
+        ;; `moved-to'.  So it reports `renamed' twice.
+        ((eq (file-notify--test-monitor) 'GInotifyFileMonitor)
+	 '(renamed renamed))
+        (t '(deleted created)))
+     (rename-file file-notify--test-tmpfile file-notify--test-tmpdir1)
+     (rename-file
+      (expand-file-name
+       (file-name-nondirectory file-notify--test-tmpfile)
+       file-notify--test-tmpdir1)
+      file-notify--test-tmpdir))
+
+   ;; Fire a pending `moved-from' event when the file notification watch
+   ;; has been removed.
+   (file-notify--test-with-actions
+       (cond
+        ;; GInotifyFileMonitor still reports `renamed'.
+        ((eq (file-notify--test-monitor) 'GInotifyFileMonitor)
+	 '(renamed stopped))
+        (t '(deleted stopped)))
+     (rename-file file-notify--test-tmpfile file-notify--test-tmpdir1)
+     (file-notify--test-wait-event)
+     (file-notify-rm-watch file-notify--test-desc))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))))
+
+(file-notify--deftest-remote file-notify-test10-move-file-to-another-watched-dir
+  "Check `file-notify-test10-move-file-to-another-watched-dir' for remote files.")
+
+(ert-deftest file-notify-test11-sufficient-resources ()
   "Check that file notification does not use too many resources."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
@@ -1531,10 +1615,10 @@ the file watch."
      ;; The environment shall be cleaned up.
      (file-notify--test-cleanup-p))))
 
-(file-notify--deftest-remote file-notify-test10-sufficient-resources
-  "Check `file-notify-test10-sufficient-resources' for remote files.")
+(file-notify--deftest-remote file-notify-test11-sufficient-resources
+  "Check `file-notify-test11-sufficient-resources' for remote files.")
 
-(ert-deftest file-notify-test11-symlinks ()
+(ert-deftest file-notify-test12-symlinks ()
   "Check that file notification do not follow symbolic links."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
@@ -1653,8 +1737,8 @@ the file watch."
        (file-notify-rm-watch file-notify--test-desc)
        (file-notify--test-cleanup-p)))))
 
-(file-notify--deftest-remote file-notify-test11-symlinks
-  "Check `file-notify-test11-symlinks' for remote files.")
+(file-notify--deftest-remote file-notify-test12-symlinks
+  "Check `file-notify-test12-symlinks' for remote files.")
 
 (ert-deftest file-notify-test12-unmount ()
   "Check that file notification stop after unmounting the filesystem."
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.