]> git.eshelyaron.com Git - emacs.git/commitdiff
Rework filenotify-tests.el
authorMichael Albinus <michael.albinus@gmx.de>
Sat, 26 Jul 2025 10:45:51 +0000 (12:45 +0200)
committerEshel Yaron <me@eshelyaron.com>
Sat, 26 Jul 2025 15:25:02 +0000 (17:25 +0200)
* lisp/filenotify.el (file-notify--rm-descriptor):
Use `insert-special-event'.

* lisp/net/tramp-sh.el (tramp-bundle-read-file-names): Fix docstring.
(tramp-sh-gio-monitor-process-filter): Extend error strings.

* test/infra/Dockerfile.emba (emacs-base): Install python3.

* test/lisp/filenotify-tests.el (auto-revert-buffer-list): Declare.
(file-notify--test-rootdir): New defvar.  Replace all uses of
`temporary-file-directory' by it.
(file-notify--test-cleanup): Set `auto-revert-buffer-list' to nil.
(file-notify--test-monitor): Check also for `process-live-p'.
Skip test, if the monitor cannot be determined.
(file-notify--deftest-remote): Do not call
`tramp-cleanup-connection', this is done in
`file-notify--test-cleanup' already.
(with-file-notify-test): New defmacro.  Use it in (almost) all tests.
(file-notify--test-make-temp-name): Do not create
`file-notify--test-tmpdir'.
(file-notify--test-event-handler): Use `directory-file-name '.
(auto-revert-debug): Set it to nil.
(file-notify-test<nn>-*): Rewrite, using `with-file-notify-test'
and `ert-with-temp-file'.

* test/lisp/net/tramp-tests.el (tramp-test46-file-notifications):
Tag it :unstable.

(cherry picked from commit 76c17ac0f018dd860f0829e78f780bcd884466c6)

lisp/filenotify.el
lisp/net/tramp-sh.el
test/infra/Dockerfile.emba
test/lisp/filenotify-tests.el
test/lisp/net/tramp-tests.el

index 300c9babfdbedc848d89312dee4f2a267bb16fb4..54b7f0463579717e7c45f1ac09275b98d76c1454 100644 (file)
@@ -78,8 +78,8 @@ 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
-        ;; Send `stopped' event.
-        (file-notify-handle-event
+        ;; Insert `stopped' event.
+        (insert-special-event
          (make-file-notify
           :-event `(,descriptor stopped
                     ,(file-notify--watch-absolute-filename watch))
index 1d1879fb4d29e90668076b17d39f6f239715ca3f..9dd449023a9ae9eff18e2700ddb38d01bfea5bdf 100644 (file)
@@ -3628,8 +3628,9 @@ will be used."
 
 (defun tramp-bundle-read-file-names (vec files)
   "Read file attributes of FILES and with one command fill the cache.
-FILES must be the local names only.  The cache attributes to be
-filled are described in `tramp-bundle-read-file-names'."
+FILES must be the local names only.  The cache attributes to be filled
+are \"file-exists-p\", \"file-readable-p\", \"file-directory-p\" and
+\"file-executable-p\"."
   (when files
     (tramp-maybe-send-script
      vec tramp-bundle-read-file-names "tramp_bundle_read_file_names")
@@ -3883,7 +3884,10 @@ Fall back to normal file name handler if no Tramp handler exists."
     (catch 'doesnt-work
       ;; https://bugs.launchpad.net/bugs/1742946
       (when (string-match-p
-            (rx (| "Monitoring not supported" "No locations given")) string)
+            (rx (| "Monitoring not supported"
+                    "No locations given"
+                    "Unable to find default local file monitor type"))
+             string)
         (delete-process proc)
         (throw 'doesnt-work nil))
 
index 64cb40fd399685502115abdcc132f0430a2225f5..7301c3ea2e7d175c45311af2e1c68c8947d99b70 100644 (file)
@@ -29,7 +29,7 @@ FROM debian:bookworm as emacs-base
 RUN apt-get update && \
     apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \
       libc-dev gcc g++ make autoconf automake libncurses-dev gnutls-dev \
-      libxml2-dev libdbus-1-dev libacl1-dev acl git man-db texinfo gdb \
+      libxml2-dev libdbus-1-dev libacl1-dev acl git man-db texinfo gdb python3 \
     && rm -rf /var/lib/apt/lists/*
 
 FROM emacs-base as emacs-inotify
index 506d79ecfb3353959bb270549178bb629b3155d1..8b494456014a1479312e6b8c6bf866c7963c626a 100644 (file)
 (require 'ert-x)
 (require 'filenotify)
 
+(defvar auto-revert-buffer-list)
+
 ;; Filter suppressed remote file-notify libraries.
 (when (stringp (getenv "REMOTE_FILE_NOTIFY_LIBRARY"))
   (dolist (lib '("inotifywait" "gio" "smb-notify"))
     (unless (string-equal (getenv "REMOTE_FILE_NOTIFY_LIBRARY") lib)
       (add-to-list 'tramp-connection-properties `(nil ,lib nil)))))
 
+(defvar file-notify--test-rootdir temporary-file-directory)
 (defvar file-notify--test-tmpdir nil)
 (defvar file-notify--test-tmpfile nil)
 (defvar file-notify--test-tmpfile1 nil)
@@ -86,14 +89,14 @@ There are different timeouts for local and remote file notification libraries."
     ;; GFam{File,Directory}Monitor case.  So we use a large timeout
     ;; for any monitor.
     ((file-notify--test-monitor) 7)
-    ((file-remote-p temporary-file-directory) 0.1)
+    ((file-remote-p file-notify--test-rootdir) 0.1)
     (t 0.01)))
   nil)
 
 (defun file-notify--test-timeout ()
   "Timeout to wait for arriving a bunch of events, in seconds."
   (cond
-   ((file-remote-p temporary-file-directory) 20)
+   ((file-remote-p file-notify--test-rootdir) 20)
    ((eq system-type 'cygwin) 10)
    ((getenv "EMACS_EMBA_CI") 10)
    ((string-equal (file-notify--test-library) "w32notify") 4)
@@ -156,14 +159,15 @@ Return nil when any other file notification watch is still active."
   (ignore-errors
     (delete-directory file-notify--test-tmpdir 'recursive))
   (ignore-errors
-    (when (file-remote-p temporary-file-directory)
+    (when (file-remote-p file-notify--test-rootdir)
       (tramp-cleanup-connection
-       (tramp-dissect-file-name temporary-file-directory) t 'keep-password)))
+       (tramp-dissect-file-name file-notify--test-rootdir) t 'keep-password)))
 
   (when (hash-table-p file-notify-descriptors)
     (clrhash file-notify-descriptors))
 
-  (setq file-notify--test-tmpdir nil
+  (setq auto-revert-buffer-list nil
+        file-notify--test-tmpdir nil
         file-notify--test-tmpfile nil
         file-notify--test-tmpfile1 nil
         file-notify--test-desc nil
@@ -195,10 +199,10 @@ Return nil when any other file notification watch is still active."
 ;; We do not want to try and fail `file-notify-add-watch'.
 (defun file-notify--test-local-enabled ()
   "Whether local file notification is enabled.
-This is needed for local `temporary-file-directory' only, in the
+This is needed for local `file-notify--test-rootdir' only, in the
 remote case we return always t."
   (or file-notify--library
-      (file-remote-p temporary-file-directory)))
+      (file-remote-p file-notify--test-rootdir)))
 
 (defvar file-notify--test-remote-enabled-checked
   (if (getenv "EMACS_HYDRA_CI") '(t . nil))
@@ -228,7 +232,7 @@ being the result.")
   "The used library for the test, as a string.
 In the remote case, it is the process name which runs on the
 remote host, or nil."
-  (if (null (file-remote-p temporary-file-directory))
+  (if (null (file-remote-p file-notify--test-rootdir))
       (symbol-name file-notify--library)
     (and (consp file-notify--test-remote-enabled-checked)
         (processp (cdr file-notify--test-remote-enabled-checked))
@@ -258,20 +262,21 @@ must be a valid watch descriptor."
          (add-to-list
           'file-notify--test-monitors
           (cons file-notify--test-desc
-                (if (file-remote-p temporary-file-directory)
+                (if (file-remote-p file-notify--test-rootdir)
                      ;; `file-notify--test-desc' is the connection process.
                      (progn
-                       (while (not (tramp-connection-property-p
-                                   file-notify--test-desc "file-monitor"))
+                       (while (and (process-live-p file-notify--test-desc)
+                                   (not (tramp-connection-property-p
+                                        file-notify--test-desc "file-monitor")))
                          (accept-process-output file-notify--test-desc 0))
                       (tramp-get-connection-property
-                       file-notify--test-desc "file-monitor" nil))
+                       file-notify--test-desc "file-monitor"))
                   (and (functionp 'gfile-monitor-name)
                        (gfile-monitor-name file-notify--test-desc)))))
           ;; If we don't know the monitor, there are good chances the
-          ;; test will fail.  We let it fail already here, in order to
-          ;; know the real reason.
-          (should (alist-get file-notify--test-desc file-notify--test-monitors)))
+          ;; test will fail.  We skip it.
+          (unless (alist-get file-notify--test-desc file-notify--test-monitors)
+            (ert-skip "Cannot determine test monitor")))
        (alist-get file-notify--test-desc file-notify--test-monitors))))
 
 (defmacro file-notify--deftest-remote (test docstring &optional unstable)
@@ -281,122 +286,118 @@ If UNSTABLE is non-nil, the test is tagged as `:unstable'."
   `(ert-deftest ,(intern (concat (symbol-name test) "-remote")) ()
      ,docstring
      :tags (if ,unstable '(:expensive-test :unstable) '(:expensive-test))
-     (let* ((temporary-file-directory ert-remote-temporary-file-directory)
+     (let* ((file-notify--test-rootdir ert-remote-temporary-file-directory)
            (ert-test (ert-get-test ',test))
             vc-handled-backends)
        (skip-unless (file-notify--test-remote-enabled))
-       (tramp-cleanup-connection
-       (tramp-dissect-file-name temporary-file-directory) t 'keep-password)
        (file-notify--test-cleanup)
        (funcall (ert-test-body ert-test)))))
 
+(defmacro with-file-notify-test (&rest body)
+  "Setup test environment.
+It creates `file-notify--test-tmpdir' and `file-notify--test-tmpfile'.
+When returning, they are deleted."
+  (declare (debug t))
+  `(ert-with-temp-directory file-notify--test-tmpdir
+     :prefix
+     (expand-file-name "file-notify-test-parent" file-notify--test-rootdir)
+     :suffix ""
+     (let ((ert-temp-file-prefix
+            (expand-file-name "file-notify-test" file-notify--test-tmpdir))
+           (ert-temp-file-suffix ""))
+       (ert-with-temp-file file-notify--test-tmpfile
+         :prefix ert-temp-file-prefix :suffix ert-temp-file-suffix
+         (unwind-protect
+             (progn ,@body)
+           (file-notify--test-cleanup))))))
+
 (ert-deftest file-notify-test00-availability ()
   "Test availability of `file-notify'."
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      (progn
-        ;; Report the native library which has been used.
-        (message "Library: `%s'" (file-notify--test-library))
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                temporary-file-directory '(change) #'ignore)))
-        (when (file-notify--test-monitor)
-          (message "Monitor: `%s'" (file-notify--test-monitor)))
-        (file-notify-rm-watch file-notify--test-desc)
+  (with-file-notify-test
+   ;; Report the native library which has been used.
+   (message "Library: `%s'" (file-notify--test-library))
+   (should
+    (setq file-notify--test-desc
+          (file-notify-add-watch file-notify--test-tmpdir '(change) #'ignore)))
+   (when (file-notify--test-monitor)
+     (message "Monitor: `%s'" (file-notify--test-monitor)))
+   (file-notify-rm-watch file-notify--test-desc)
 
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p)))
 
 (file-notify--deftest-remote file-notify-test00-availability
   "Test availability of `file-notify' for remote files.")
 
 (defun file-notify--test-make-temp-name ()
   "Create a temporary file name for test."
-  (unless (stringp file-notify--test-tmpdir)
-    (setq file-notify--test-tmpdir
-          (expand-file-name
-           (make-temp-name "file-notify-test-parent") temporary-file-directory)))
-  (unless (file-directory-p file-notify--test-tmpdir)
-    (make-directory file-notify--test-tmpdir))
-  (expand-file-name
-   (make-temp-name "file-notify-test") file-notify--test-tmpdir))
+  (make-temp-name
+   (expand-file-name "file-notify-test" file-notify--test-tmpdir)))
 
 (ert-deftest file-notify-test01-add-watch ()
   "Check `file-notify-add-watch'."
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      (progn
-        (setq file-notify--test-tmpfile  (file-notify--test-make-temp-name)
-              file-notify--test-tmpfile1
-              (format
-               "%s/%s" file-notify--test-tmpfile (md5 (current-time-string))))
-
-        ;; Check, that different valid parameters are accepted.
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpdir '(change) #'ignore)))
-        (file-notify-rm-watch file-notify--test-desc)
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpdir '(attribute-change) #'ignore)))
-        (file-notify-rm-watch file-notify--test-desc)
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpdir '(change attribute-change) #'ignore)))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; File monitors like kqueue insist, that the watched file
-        ;; exists.  Directory monitors are not bound to this
-        ;; restriction.
-        (when (string-equal (file-notify--test-library) "kqueue")
-          (write-region
-           "any text" nil file-notify--test-tmpfile nil 'no-message))
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpfile '(change attribute-change) #'ignore)))
-        (file-notify-rm-watch file-notify--test-desc)
-        (when (string-equal (file-notify--test-library) "kqueue")
-          (delete-file file-notify--test-tmpfile))
-
-        ;; Check error handling.
-        (should-error (file-notify-add-watch 1 2 3 4)
-                      :type 'wrong-number-of-arguments)
-        (should
-         (equal (should-error
-                 (file-notify-add-watch 1 2 3))
-                '(wrong-type-argument 1)))
-        (should
-         (equal (should-error
-                 (file-notify-add-watch file-notify--test-tmpdir 2 3))
-                '(wrong-type-argument 2)))
-        (should
-         (equal (should-error
-                 (file-notify-add-watch file-notify--test-tmpdir '(change) 3))
-                '(wrong-type-argument 3)))
-        ;; The upper directory of a file must exist.
-        (should
-         (equal (should-error
-                 (file-notify-add-watch
-                  file-notify--test-tmpfile1
-                  '(change attribute-change) #'ignore))
-                `(file-notify-error
-                  "Directory does not exist" ,file-notify--test-tmpfile)))
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (setq file-notify--test-tmpfile1
+         (format "%s/%s" file-notify--test-tmpfile (md5 (current-time-string))))
+   (delete-file file-notify--test-tmpfile)
+
+   ;; Check, that different valid parameters are accepted.
+   (should
+    (setq file-notify--test-desc
+          (file-notify-add-watch
+           file-notify--test-tmpdir '(change) #'ignore)))
+   (file-notify-rm-watch file-notify--test-desc)
+   (should
+    (setq file-notify--test-desc
+          (file-notify-add-watch
+           file-notify--test-tmpdir '(attribute-change) #'ignore)))
+   (file-notify-rm-watch file-notify--test-desc)
+   (should
+    (setq file-notify--test-desc
+          (file-notify-add-watch
+           file-notify--test-tmpdir '(change attribute-change) #'ignore)))
+   (file-notify-rm-watch file-notify--test-desc)
+
+   ;; File monitors like kqueue insist, that the watched file exists.
+   ;; Directory monitors are not bound to this restriction.
+   (when (string-equal (file-notify--test-library) "kqueue")
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message))
+   (should
+    (setq file-notify--test-desc
+          (file-notify-add-watch
+           file-notify--test-tmpfile '(change attribute-change) #'ignore)))
+   (file-notify-rm-watch file-notify--test-desc)
+
+   ;; Check error handling.
+   (should-error (file-notify-add-watch 1 2 3 4)
+                 :type 'wrong-number-of-arguments)
+   (should
+    (equal (should-error
+            (file-notify-add-watch 1 2 3))
+           '(wrong-type-argument 1)))
+   (should
+    (equal (should-error
+            (file-notify-add-watch file-notify--test-tmpdir 2 3))
+           '(wrong-type-argument 2)))
+   (should
+    (equal (should-error
+            (file-notify-add-watch file-notify--test-tmpdir '(change) 3))
+           '(wrong-type-argument 3)))
+   ;; The upper directory of a file must exist.
+   (should
+    (equal (should-error
+            (file-notify-add-watch
+             file-notify--test-tmpfile1
+             '(change attribute-change) #'ignore))
+           `(file-notify-error
+             "Directory does not exist" ,file-notify--test-tmpfile)))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p)))
 
 (file-notify--deftest-remote file-notify-test01-add-watch
   "Check `file-notify-add-watch' for remote files.")
@@ -406,125 +407,104 @@ If UNSTABLE is non-nil, the test is tagged as `:unstable'."
   "Check `file-notify-rm-watch' and `file-notify-rm-all-watches'."
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      ;; Check, that `file-notify-rm-watch' works.
-      (progn
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                temporary-file-directory '(change) #'ignore)))
-        (file-notify-rm-watch file-notify--test-desc)
-        ;; Check, that any parameter is accepted.
-        (condition-case err
-            (progn
-              (file-notify-rm-watch nil)
-              (file-notify-rm-watch 0)
-              (file-notify-rm-watch "foo")
-              (file-notify-rm-watch 'foo))
-          (error (ert-fail err)))
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check, that no error is returned removing a watch descriptor twice.
-      (progn
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
-        (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
-        (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpfile '(change) #'ignore)))
-        (should
-         (setq file-notify--test-desc1
-               (file-notify-add-watch
-                file-notify--test-tmpfile1 '(change) #'ignore)))
-        ;; Remove `file-notify--test-desc' twice.
-        (file-notify-rm-watch file-notify--test-desc)
-        (file-notify-rm-watch file-notify--test-desc)
-        (file-notify-rm-watch file-notify--test-desc1)
-        (delete-file file-notify--test-tmpfile)
-        (delete-file file-notify--test-tmpfile1)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check, that removing watch descriptors out of order do not
-      ;; harm.  This fails on cygwin because of timing issues unless a
-      ;; long `sit-for' is added before the call to
-      ;; `file-notify--test-wait-event'.
-      (unless (eq system-type 'cygwin)
-        (let (results)
-          (cl-flet ((first-callback (event)
-                     (when (eq (file-notify--test-event-action event) 'deleted)
-                       (push 1 results)))
-                    (second-callback (event)
-                     (when (eq (file-notify--test-event-action event) 'deleted)
-                       (push 2 results))))
-            (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-            (write-region
-             "any text" nil file-notify--test-tmpfile nil 'no-message)
-            (should
-             (setq file-notify--test-desc
-                   (file-notify-add-watch
-                    file-notify--test-tmpfile
-                    '(change) #'first-callback)))
-            (should
-             (setq file-notify--test-desc1
-                   (file-notify-add-watch
-                    file-notify--test-tmpfile
-                    '(change) #'second-callback)))
-            ;; `file-notify-rm-watch' confuses `file-notify--test-monitor'.
-            ;; Initialize it in time.
-            (file-notify--test-monitor)
-            ;; Remove first watch.
-            (file-notify-rm-watch file-notify--test-desc)
-            ;; Only the second callback shall run.
-           (file-notify--test-wait-event)
-            (delete-file file-notify--test-tmpfile)
-            (file-notify--test-wait-for-events
-             (file-notify--test-timeout) results)
-            (should (equal results (list 2)))
-
-            ;; The environment shall be cleaned up.
-            (file-notify--test-cleanup-p))))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check `file-notify-rm-all-watches'.
-      (progn
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
-        (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
-        (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
-        (should
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpfile '(change) #'ignore)))
-        (should
-         (setq file-notify--test-desc1
-               (file-notify-add-watch
-                file-notify--test-tmpfile1 '(change) #'ignore)))
-        (file-notify-rm-all-watches)
-        (delete-file file-notify--test-tmpfile)
-        (delete-file file-notify--test-tmpfile1)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   ;; Check, that `file-notify-rm-watch' works.
+   (should
+    (setq file-notify--test-desc
+          (file-notify-add-watch
+           file-notify--test-tmpdir '(change) #'ignore)))
+   (file-notify-rm-watch file-notify--test-desc)
+   ;; Check, that any parameter is accepted.
+   (condition-case err
+       (progn
+         (file-notify-rm-watch nil)
+         (file-notify-rm-watch 0)
+         (file-notify-rm-watch "foo")
+         (file-notify-rm-watch 'foo))
+     (error (ert-fail err)))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   (ert-with-temp-file file-notify--test-tmpfile1
+     :prefix ert-temp-file-prefix :suffix ert-temp-file-suffix
+     ;; Check, that no error is returned removing a watch descriptor twice.
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
+     (should
+      (setq file-notify--test-desc
+            (file-notify-add-watch
+             file-notify--test-tmpfile '(change) #'ignore)))
+     (should
+      (setq file-notify--test-desc1
+            (file-notify-add-watch
+             file-notify--test-tmpfile1 '(change) #'ignore)))
+     ;; Remove `file-notify--test-desc' twice.
+     (file-notify-rm-watch file-notify--test-desc)
+     (file-notify-rm-watch file-notify--test-desc)
+     (file-notify-rm-watch file-notify--test-desc1)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p)))
+
+  (with-file-notify-test
+   ;; Check, that removing watch descriptors out of order do not harm.
+   ;; This fails on cygwin because of timing issues unless a long
+   ;; `sit-for' is added before the call to
+   ;; `file-notify--test-wait-event'.
+   (unless (eq system-type 'cygwin)
+     (let (results)
+       (cl-flet ((first-callback (event)
+                   (when (eq (file-notify--test-event-action event) 'deleted)
+                     (push 1 results)))
+                 (second-callback (event)
+                   (when (eq (file-notify--test-event-action event) 'deleted)
+                     (push 2 results))))
+         (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+         (should
+          (setq file-notify--test-desc
+                (file-notify-add-watch
+                 file-notify--test-tmpfile
+                 '(change) #'first-callback)))
+         (should
+          (setq file-notify--test-desc1
+                (file-notify-add-watch
+                 file-notify--test-tmpfile
+                 '(change) #'second-callback)))
+         ;; `file-notify-rm-watch' confuses `file-notify--test-monitor'.
+         ;; Initialize it in time.
+         (file-notify--test-monitor)
+         ;; Remove first watch.
+         (file-notify-rm-watch file-notify--test-desc)
+         ;; Only the second callback shall run.
+        (file-notify--test-wait-event)
+         (delete-file file-notify--test-tmpfile)
+         (file-notify--test-wait-for-events
+          (file-notify--test-timeout) results)
+         (should (equal results (list 2)))
+
+         ;; The environment shall be cleaned up.
+         (file-notify--test-cleanup-p)))))
+
+  (with-file-notify-test
+   (ert-with-temp-file file-notify--test-tmpfile1
+     :prefix ert-temp-file-prefix :suffix ert-temp-file-suffix
+     ;; Check `file-notify-rm-all-watches'.
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
+     (should
+      (setq file-notify--test-desc
+            (file-notify-add-watch
+             file-notify--test-tmpfile '(change) #'ignore)))
+     (should
+      (setq file-notify--test-desc1
+            (file-notify-add-watch
+             file-notify--test-tmpfile1 '(change) #'ignore)))
+     (file-notify-rm-all-watches)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p))))
 
 (file-notify--deftest-remote file-notify-test02-rm-watch
   "Check `file-notify-rm-watch' for remote files.")
@@ -559,7 +539,7 @@ and `file-notify--test-file' are bound somewhere."
 For later analysis, append the test result to `file-notify--test-results'
 and the event to `file-notify--test-events'."
   (let* ((file-notify--test-event event)
-         (file-notify--test-file file)
+         (file-notify--test-file (directory-file-name file))
          (result
           (ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
     ;; Do not add lock files, this would confuse the checks.
@@ -661,305 +641,274 @@ delivered."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      ;; Check file creation, change and deletion.  It doesn't work
-      ;; for kqueue, because we don't use an implicit directory
-      ;; monitor.
-      (unless (string-equal (file-notify--test-library) "kqueue")
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-        (should
-         (setq file-notify--test-desc
-               (file-notify--test-add-watch
-                file-notify--test-tmpfile
-                '(change) #'file-notify--test-event-handler)))
-        (file-notify--test-with-actions
-            (cond
-             ;; SMBSamba reports three `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(created changed changed changed deleted stopped))
-            ;; GFam{File,Directory}Monitor, GKqueueFileMonitor and
-            ;; GPollFileMonitor do not report the `changed' event.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor
-                      GKqueueFileMonitor GPollFileMonitor))
-             '(created deleted stopped))
-             (t '(created changed deleted stopped)))
-          (write-region
-           "another text" nil file-notify--test-tmpfile nil 'no-message)
-          (file-notify--test-wait-event)
-          (delete-file file-notify--test-tmpfile))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      (progn
-        ;; Check file change and deletion.
-       (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-        (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpfile
-               '(change) #'file-notify--test-event-handler)))
-        (file-notify--test-with-actions
-           (cond
-             ;; SMBSamba reports four `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(changed changed changed changed deleted stopped))
-            ;; GFam{File,Directory}Monitor and GPollFileMonitor do
-             ;; not detect the `changed' event reliably.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
-             '((deleted stopped)
-               (changed deleted stopped)))
-            ;; GKqueueFileMonitor does not report the `changed' event.
-            ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
-             '(deleted stopped))
-            ;; There could be one or two `changed' events.
-            (t '((changed deleted stopped)
-                 (changed changed deleted stopped))))
-          (write-region
-           "another text" nil file-notify--test-tmpfile nil 'no-message)
-          (file-notify--test-wait-event)
-          (delete-file file-notify--test-tmpfile))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check file creation, change and deletion when watching a
-      ;; directory.  There must be a `stopped' event when deleting the
-      ;; directory.
-      (let ((file-notify--test-tmpdir
-             (make-temp-file "file-notify-test-parent" t)))
-       (should
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpdir
-               '(change) #'file-notify--test-event-handler)))
-       (file-notify--test-with-actions
-           (cond
-            ;; w32notify does not raise `deleted' and `stopped'
-            ;; events for the watched directory.
-            ((string-equal (file-notify--test-library) "w32notify")
-             '(created changed deleted))
-             ;; SMBSamba reports three `changed' events.
-            ((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.
-            ((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))
-            (t '(created changed deleted deleted stopped)))
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message)
-         (file-notify--test-wait-event)
-          (delete-directory file-notify--test-tmpdir 'recursive))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check copy of files inside a directory.
-      (let ((file-notify--test-tmpdir
-            (make-temp-file "file-notify-test-parent" t)))
-       (should
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
-              file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpdir
-               '(change) #'file-notify--test-event-handler)))
-       (file-notify--test-with-actions
-           (cond
-            ;; w32notify does not distinguish between `changed' and
-            ;; `attribute-changed'.  It does not raise `deleted' and
-            ;; `stopped' events for the watched directory.
-            ((string-equal (file-notify--test-library) "w32notify")
-             '(created changed created changed
-               changed changed changed
-               deleted deleted))
-             ;; SMBSamba reports three `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(created changed changed changed created 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.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
-             '(created created changed changed deleted stopped))
-            ((string-equal (file-notify--test-library) "kqueue")
-             '(created changed created changed deleted stopped))
-            ;; GKqueueFileMonitor does not report the `changed' event.
-            ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
-             '(created created deleted deleted deleted stopped))
-            (t '(created changed created changed
-                 deleted deleted deleted stopped)))
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message)
-         (file-notify--test-wait-event)
-         (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
-         ;; The next two events shall not be visible.
-         (file-notify--test-wait-event)
-         (set-file-modes file-notify--test-tmpfile 000 'nofollow)
-         (file-notify--test-wait-event)
-         (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
-         (file-notify--test-wait-event)
-          (delete-directory file-notify--test-tmpdir 'recursive))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check rename of files inside a directory.
-      (let ((file-notify--test-tmpdir
-            (make-temp-file "file-notify-test-parent" t)))
-       (should
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
-              file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpdir
-               '(change) #'file-notify--test-event-handler)))
-       (file-notify--test-with-actions
-           (cond
-            ;; w32notify does not raise `deleted' and `stopped'
-            ;; events for the watched directory.
-            ((string-equal (file-notify--test-library) "w32notify")
-             '(created changed renamed deleted))
-             ;; SMBSamba reports three `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(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.
-            ((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))
-            (t '(created changed renamed deleted deleted stopped)))
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message)
-         (file-notify--test-wait-event)
-         (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
-         ;; After the rename, we won't get events anymore.
-         (file-notify--test-wait-event)
-          (delete-directory file-notify--test-tmpdir 'recursive))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; Check attribute change.
-      (progn
-       (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-       (write-region
-        "any text" nil file-notify--test-tmpfile nil 'no-message)
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpfile
-               '(attribute-change) #'file-notify--test-event-handler)))
-       (file-notify--test-with-actions
-           (cond
-            ;; w32notify does not distinguish between `changed' and
-            ;; `attribute-changed'.  Under MS Windows 7, we get four
-            ;; `changed' events, and under MS Windows 10 just two.
-            ;; Strange.
-            ((string-equal (file-notify--test-library) "w32notify")
-             '((changed changed)
-               (changed changed changed changed)))
-             ;; SMBWindows does not distinguish between `changed' and
-            ;; `attribute-changed'.
-            ((eq (file-notify--test-monitor) 'SMBWindows)
-             '(changed changed))
-             ;; SMBSamba does not distinguish between `changed' and
-            ;; `attribute-changed'.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(changed changed changed changed))
-            ;; GFam{File,Directory}Monitor, GKqueueFileMonitor and
-            ;; GPollFileMonitor do not report the `attribute-changed'
-            ;; event.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor
-                      GKqueueFileMonitor GPollFileMonitor))
-              '())
-            ;; For GInotifyFileMonitor,`write-region' raises
-            ;; also an `attribute-changed' event on gio.
-            ((and (string-equal (file-notify--test-library) "gio")
-                   (eq (file-notify--test-monitor) 'GInotifyFileMonitor))
-             '(attribute-changed attribute-changed attribute-changed))
-            ;; For kqueue, `write-region' raises also an
-            ;; `attribute-changed' event.
-             ((string-equal (file-notify--test-library) "kqueue")
-             '(attribute-changed attribute-changed attribute-changed))
-            ;; For inotifywait, `write-region' raises also an
-            ;; `attribute-changed' event.
-             ((string-equal (file-notify--test-library) "inotifywait")
-             '(attribute-changed attribute-changed attribute-changed))
-            (t '(attribute-changed attribute-changed)))
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message)
-         (file-notify--test-wait-event)
-         (set-file-modes file-notify--test-tmpfile 000 'nofollow)
-         (file-notify--test-wait-event)
-         (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
-         (file-notify--test-wait-event)
-         (delete-file file-notify--test-tmpfile))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  ;; It doesn't work for kqueue, because we don't use an implicit
+  ;; directory monitor.
+  (unless (string-equal (file-notify--test-library) "kqueue")
+    (with-file-notify-test
+     ;; Check file creation, change and deletion.
+     (delete-file file-notify--test-tmpfile)
+     (should
+      (setq file-notify--test-desc
+            (file-notify--test-add-watch
+             file-notify--test-tmpfile
+             '(change) #'file-notify--test-event-handler)))
+     (file-notify--test-with-actions
+         (cond
+          ;; SMBSamba reports three `changed' events.
+         ((eq (file-notify--test-monitor) 'SMBSamba)
+           '(created changed changed changed deleted stopped))
+         ;; GFam{File,Directory}Monitor, GKqueueFileMonitor and
+         ;; GPollFileMonitor do not report the `changed' event.
+         ((memq (file-notify--test-monitor)
+                 '(GFamFileMonitor GFamDirectoryMonitor
+                   GKqueueFileMonitor GPollFileMonitor))
+          '(created deleted stopped))
+          (t '(created changed deleted stopped)))
+       (write-region
+        "another text" nil file-notify--test-tmpfile nil 'no-message)
+       (file-notify--test-wait-event)
+       (delete-file file-notify--test-tmpfile))
+     (file-notify-rm-watch file-notify--test-desc)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p)))
+
+  (with-file-notify-test
+   ;; Check file change and deletion.
+   (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+          file-notify--test-tmpfile
+          '(change) #'file-notify--test-event-handler)))
+   (file-notify--test-with-actions
+       (cond
+        ;; SMBSamba reports four `changed' events.
+       ((eq (file-notify--test-monitor) 'SMBSamba)
+         '(changed changed changed changed deleted stopped))
+       ;; GFam{File,Directory}Monitor and GPollFileMonitor do not
+        ;; detect the `changed' event reliably.
+       ((memq (file-notify--test-monitor)
+               '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
+        '((deleted stopped)
+          (changed deleted stopped)))
+       ;; GKqueueFileMonitor does not report the `changed' event.
+       ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
+        '(deleted stopped))
+       ;; There could be one or two `changed' events.
+       (t '((changed deleted stopped)
+            (changed changed deleted stopped))))
+     (write-region "another text" nil file-notify--test-tmpfile nil 'no-message)
+     (file-notify--test-wait-event)
+     (delete-file file-notify--test-tmpfile))
+   (file-notify-rm-watch file-notify--test-desc)
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   (delete-file file-notify--test-tmpfile)
+   ;; Check file creation, change and deletion when watching a
+   ;; directory.  There must be a `stopped' event when deleting the
+   ;; directory.
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+          file-notify--test-tmpdir
+          '(change) #'file-notify--test-event-handler)))
+   (file-notify--test-with-actions
+       (cond
+       ;; w32notify does not raise `deleted' and `stopped' events for
+       ;; the watched directory.
+       ((string-equal (file-notify--test-library) "w32notify")
+        '(created changed deleted))
+        ;; SMBSamba reports three `changed' events.
+       ((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.
+       ((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))
+       (t '(created changed deleted deleted stopped)))
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (file-notify--test-wait-event)
+     (delete-directory file-notify--test-tmpdir 'recursive))
+   (file-notify-rm-watch file-notify--test-desc)
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   (ert-with-temp-file file-notify--test-tmpfile1
+     :prefix ert-temp-file-prefix :suffix ert-temp-file-suffix
+     ;; Check copy of files inside a directory.
+     (delete-file file-notify--test-tmpfile)
+     (delete-file file-notify--test-tmpfile1)
+     (should
+      (setq file-notify--test-desc
+           (file-notify--test-add-watch
+            file-notify--test-tmpdir
+            '(change) #'file-notify--test-event-handler)))
+     (file-notify--test-with-actions
+        (cond
+         ;; w32notify does not distinguish between `changed' and
+         ;; `attribute-changed'.  It does not raise `deleted' and
+         ;; `stopped' events for the watched directory.
+         ((string-equal (file-notify--test-library) "w32notify")
+          '(created changed created changed
+            changed changed changed
+            deleted deleted))
+          ;; SMBSamba reports three `changed' events.
+         ((eq (file-notify--test-monitor) 'SMBSamba)
+           '(created changed changed changed created 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.
+         ((memq (file-notify--test-monitor)
+                 '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
+          '(created created changed changed deleted stopped))
+         ((string-equal (file-notify--test-library) "kqueue")
+          '(created changed created changed deleted stopped))
+         ;; GKqueueFileMonitor does not report the `changed' event.
+         ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
+          '(created created deleted deleted deleted stopped))
+         (t '(created changed created changed
+              deleted deleted deleted stopped)))
+       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+       (file-notify--test-wait-event)
+       (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
+       ;; The next two events shall not be visible.
+       (file-notify--test-wait-event)
+       (set-file-modes file-notify--test-tmpfile 000 'nofollow)
+       (file-notify--test-wait-event)
+       (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
+       (file-notify--test-wait-event)
+       (delete-directory file-notify--test-tmpdir 'recursive))
+     (file-notify-rm-watch file-notify--test-desc)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p)))
+
+  (with-file-notify-test
+   (ert-with-temp-file file-notify--test-tmpfile1
+     :prefix ert-temp-file-prefix :suffix ert-temp-file-suffix
+     ;; Check rename of files inside a directory.
+     (delete-file file-notify--test-tmpfile)
+     (delete-file file-notify--test-tmpfile1)
+     (should
+      (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+           file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
+           file-notify--test-desc
+           (file-notify--test-add-watch
+            file-notify--test-tmpdir
+            '(change) #'file-notify--test-event-handler)))
+     (file-notify--test-with-actions
+        (cond
+         ;; w32notify does not raise `deleted' and `stopped' events for
+         ;; the watched directory.
+         ((string-equal (file-notify--test-library) "w32notify")
+          '(created changed renamed deleted))
+          ;; SMBSamba reports three `changed' events.
+         ((eq (file-notify--test-monitor) 'SMBSamba)
+           '(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.
+         ((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))
+         (t '(created changed renamed deleted deleted stopped)))
+       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+       (file-notify--test-wait-event)
+       (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
+       ;; After the rename, we won't get events anymore.
+       (file-notify--test-wait-event)
+       (delete-directory file-notify--test-tmpdir 'recursive))
+     (file-notify-rm-watch file-notify--test-desc)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p)))
+
+  (with-file-notify-test
+   ;; Check attribute change.
+   (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+          file-notify--test-tmpfile
+          '(attribute-change) #'file-notify--test-event-handler)))
+   (file-notify--test-with-actions
+       (cond
+       ;; w32notify does not distinguish between `changed' and
+       ;; `attribute-changed'.  Under MS Windows 7, we get four
+       ;; `changed' events, and under MS Windows 10 just two.  Strange.
+       ((string-equal (file-notify--test-library) "w32notify")
+        '((changed changed)
+          (changed changed changed changed)))
+        ;; SMBWindows does not distinguish between `changed' and
+       ;; `attribute-changed'.
+       ((eq (file-notify--test-monitor) 'SMBWindows)
+        '(changed changed))
+        ;; SMBSamba does not distinguish between `changed' and
+       ;; `attribute-changed'.
+       ((eq (file-notify--test-monitor) 'SMBSamba)
+         '(changed changed changed changed))
+       ;; GFam{File,Directory}Monitor, GKqueueFileMonitor and
+       ;; GPollFileMonitor do not report the `attribute-changed' event.
+       ((memq (file-notify--test-monitor)
+               '(GFamFileMonitor GFamDirectoryMonitor
+                 GKqueueFileMonitor GPollFileMonitor))
+         '())
+       ;; For GInotifyFileMonitor,`write-region' raises also an
+       ;; `attribute-changed' event on gio.
+       ((and (string-equal (file-notify--test-library) "gio")
+              (eq (file-notify--test-monitor) 'GInotifyFileMonitor))
+        '(attribute-changed attribute-changed attribute-changed))
+       ;; For kqueue, `write-region' raises also an `attribute-changed'
+       ;; event.
+        ((string-equal (file-notify--test-library) "kqueue")
+        '(attribute-changed attribute-changed attribute-changed))
+       ;; For inotifywait, `write-region' raises also an
+       ;; `attribute-changed' event.
+        ((string-equal (file-notify--test-library) "inotifywait")
+        '(attribute-changed attribute-changed attribute-changed))
+       (t '(attribute-changed attribute-changed)))
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (file-notify--test-wait-event)
+     (set-file-modes file-notify--test-tmpfile 000 'nofollow)
+     (file-notify--test-wait-event)
+     (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
+     (file-notify--test-wait-event)
+     (delete-file file-notify--test-tmpfile))
+   (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-test03-events
   "Check file creation/change/removal notifications for remote files.")
 
 (require 'autorevert)
-(setq auto-revert-notify-exclude-dir-regexp "nothing-to-be-excluded"
+(setq auto-revert-debug nil
+      auto-revert-notify-exclude-dir-regexp "nothing-to-be-excluded"
       auto-revert-remote-files t
       auto-revert-stop-on-user-input nil)
 
@@ -970,99 +919,93 @@ delivered."
 
   ;; Run with shortened `auto-revert-interval' for a faster test.
   (let* ((auto-revert-interval 1)
-         (timeout (if (file-remote-p temporary-file-directory)
+         (timeout (if (file-remote-p file-notify--test-rootdir)
                       60   ; FIXME: can this be shortened?
                     (* auto-revert-interval 2.5)))
          (text-quoting-style 'grave)
          buf)
     (auto-revert-set-timer)
-    (unwind-protect
-       (progn
-          ;; In the remote case, `vc-refresh-state' returns undesired
-          ;; error messages.  Let's suppress them.
-          (advice-add 'vc-refresh-state :around 'ignore)
-         (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message)
-         (setq buf (find-file-noselect file-notify--test-tmpfile))
-         (with-current-buffer buf
-           (should (string-equal (buffer-string) "any text"))
-            ;; `buffer-stale--default-function' checks for
-            ;; `verify-visited-file-modtime'.  We must ensure that it
-            ;; returns nil.
-            (sleep-for 1)
-           (auto-revert-mode 1)
-
-           (with-timeout (timeout (ignore))
-             (while (null auto-revert-notify-watch-descriptor)
-               (sleep-for 0.2)))
-
-            ;; `file-notify--test-monitor' needs to know
-            ;; `file-notify--test-desc' in order to compute proper
-            ;; timeouts.
-            (setq file-notify--test-desc auto-revert-notify-watch-descriptor)
-
-           ;; GKqueueFileMonitor does not report the `changed' event.
-           (skip-when (eq (file-notify--test-monitor) 'GKqueueFileMonitor))
-
-           ;; Check, that file notification has been used.
-           (should auto-revert-mode)
-           (should auto-revert-use-notify)
-           (should auto-revert-notify-watch-descriptor)
-
-           ;; Modify file.  We wait for a second, in order to have
-            ;; another timestamp.
-            (ert-with-message-capture captured-messages
-              (let ((inhibit-message t))
-                (sleep-for 1)
-                (write-region
-                 "another text" nil file-notify--test-tmpfile nil 'no-message)
-
-                ;; Check, that the buffer has been reverted.
-                (file-notify--test-wait-for-events
-                 timeout
-                 (string-match-p
-                  (rx bol "Reverting buffer `"
-                      (literal (buffer-name buf)) "'" eol)
-                  captured-messages))
-                (should (string-match-p "another text" (buffer-string)))))
-
-            ;; Stop file notification.  Autorevert shall still work via polling.
-           (file-notify-rm-watch auto-revert-notify-watch-descriptor)
-           (file-notify--test-wait-for-events
-            timeout (null auto-revert-notify-watch-descriptor))
-           (should auto-revert-use-notify)
-           (should-not
-             (file-notify-valid-p auto-revert-notify-watch-descriptor))
-
-           ;; Modify file.  We wait for two seconds, in order to
-           ;; have another timestamp.  One second seems to be too
-            ;; short.  And cygwin sporadically requires more than two.
-            (ert-with-message-capture captured-messages
-              (let ((inhibit-message t))
-                (sleep-for (if (eq system-type 'cygwin) 3 2))
-                (write-region
-                 "foo bla" nil file-notify--test-tmpfile nil 'no-message)
-
-                ;; Check, that the buffer has been reverted.
-                (file-notify--test-wait-for-events
-                 timeout
-                 (string-match-p
-                  (rx bol "Reverting buffer `"
-                      (literal (buffer-name buf)) "'" eol)
-                  captured-messages))
-                (should (string-match-p "foo bla" (buffer-string)))))
-
-            ;; Stop autorevert, in order to cleanup descriptor.
-            (auto-revert-mode -1))
-
-          ;; The environment shall be cleaned up.
-          (file-notify--test-cleanup-p))
-
-      ;; Cleanup.
-      (advice-remove 'vc-refresh-state 'ignore)
-      (ignore-errors (kill-buffer buf))
-      (file-notify--test-cleanup))))
+
+    (with-file-notify-test
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (setq buf (find-file-noselect file-notify--test-tmpfile))
+     (with-current-buffer buf
+       ;; In the remote case, `vc-refresh-state' returns undesired error
+       ;; messages.  Let's suppress them.
+       (add-function :around (local #'vc-refresh-state) #'ignore)
+       (should (string-equal (buffer-string) "any text"))
+       ;; `buffer-stale--default-function' checks for
+       ;; `verify-visited-file-modtime'.  We must ensure that it returns
+       ;; nil.
+       (sleep-for 1)
+       (auto-revert-mode 1)
+
+       (file-notify--test-wait-for-events
+        timeout auto-revert-notify-watch-descriptor)
+
+       ;; `file-notify--test-monitor' needs to know
+       ;; `file-notify--test-desc' in order to compute proper timeouts.
+       (setq file-notify--test-desc auto-revert-notify-watch-descriptor)
+
+       ;; GKqueueFileMonitor does not report the `changed' event.
+       (skip-when (eq (file-notify--test-monitor) 'GKqueueFileMonitor))
+
+       ;; Check, that file notification has been used.
+       (should auto-revert-mode)
+       (should auto-revert-use-notify)
+       (should auto-revert-notify-watch-descriptor)
+
+       ;; Modify file.  We wait for a second, in order to have another
+       ;; timestamp.
+       (ert-with-message-capture captured-messages
+         (let ((inhibit-message t))
+           (sleep-for 1)
+           (write-region
+            "another text" nil file-notify--test-tmpfile nil 'no-message)
+
+           ;; Check, that the buffer has been reverted.
+           (file-notify--test-wait-for-events
+            timeout
+            (string-match-p
+             (rx bol "Reverting buffer `"
+                 (literal (buffer-name buf)) "'" eol)
+             captured-messages))
+           (should (string-match-p "another text" (buffer-string)))))
+
+       ;; Stop file notification.  Autorevert shall still work via polling.
+       (file-notify-rm-watch auto-revert-notify-watch-descriptor)
+       (file-notify--test-wait-for-events
+       timeout
+        (or (null auto-revert-notify-watch-descriptor)
+            (not (file-notify-valid-p auto-revert-notify-watch-descriptor))))
+       (should auto-revert-use-notify)
+
+       ;; Modify file.  We wait for two seconds, in order to have
+       ;; another timestamp.  One second seems to be too short.  And
+       ;; cygwin sporadically requires more than two.
+       (ert-with-message-capture captured-messages
+         (let ((inhibit-message t))
+           (sleep-for (if (eq system-type 'cygwin) 3 2))
+           (write-region "foo bla" nil file-notify--test-tmpfile nil 'no-message)
+
+           ;; Check, that the buffer has been reverted.
+           (file-notify--test-wait-for-events
+            timeout
+            (string-match-p
+             (rx bol "Reverting buffer `"
+                 (literal (buffer-name buf)) "'" eol)
+             captured-messages))
+           (should (string-match-p "foo bla" (buffer-string)))))
+
+       ;; Stop autorevert, in order to cleanup descriptor.
+       (auto-revert-mode -1)
+       (remove-function (local #'vc-refresh-state) #'ignore))
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p)
+
+     ;; Cleanup.
+     (ignore-errors (kill-buffer buf)))))
 
 (file-notify--deftest-remote file-notify-test04-autorevert
   "Check autorevert via file notification for remote files.")
@@ -1072,124 +1015,102 @@ delivered."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      (progn
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
-       (should
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-                file-notify--test-tmpfile '(change) #'ignore)))
-        (should (file-notify-valid-p file-notify--test-desc))
-       ;; After calling `file-notify-rm-watch', the descriptor is not
-       ;; valid anymore.
-        (file-notify-rm-watch file-notify--test-desc)
-        (should-not (file-notify-valid-p file-notify--test-desc))
-       (delete-file file-notify--test-tmpfile)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      (progn
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpfile
-               '(change) #'file-notify--test-event-handler)))
-       (should (file-notify-valid-p file-notify--test-desc))
-        (file-notify--test-with-actions
-           (cond
-             ;; SMBSamba reports three `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(changed changed changed changed deleted stopped))
-            ;; GFam{File,Directory}Monitor do not
-             ;; detect the `changed' event reliably.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor))
-             '((deleted stopped)
-               (changed deleted stopped)))
-            ;; GKqueueFileMonitor does not report the `changed' event.
-            ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
-             '(deleted stopped))
-            ;; There could be one or two `changed' events.
-            (t '((changed deleted stopped)
-                 (changed changed deleted stopped))))
-          (write-region
-           "another text" nil file-notify--test-tmpfile nil 'no-message)
-         (file-notify--test-wait-event)
-         (delete-file file-notify--test-tmpfile))
-       (file-notify--test-wait-event)
-       ;; After deleting the file, the descriptor is not valid anymore.
-        (should-not (file-notify-valid-p file-notify--test-desc))
-        (file-notify-rm-watch file-notify--test-desc)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; On emba, `deleted' and `stopped' events of the directory are
-      ;; not detected.
-      (let ((file-notify--test-tmpdir
-            (make-temp-file "file-notify-test-parent" t)))
-       (should
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpdir
-               '(change) #'file-notify--test-event-handler)))
-       (should (file-notify-valid-p file-notify--test-desc))
-       (file-notify--test-with-actions
-           (cond
-            ;; w32notify does not raise `deleted' and `stopped' events
-            ;; for the watched directory.
-            ((string-equal (file-notify--test-library) "w32notify")
-             '(created changed deleted))
-             ;; SMBSamba reports three `changed' events.
-            ((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.
-            ((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))
-            (t '(created changed deleted deleted stopped)))
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message)
-         (file-notify--test-wait-event)
-         (delete-directory file-notify--test-tmpdir 'recursive))
-       ;; After deleting the parent directory, the descriptor must not
-       ;; be valid anymore.
-       (should-not (file-notify-valid-p file-notify--test-desc))
-        ;; w32notify doesn't generate `stopped' events when the parent
-        ;; directory is deleted, which doesn't provide a chance for
-        ;; 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))
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+   (should
+    (setq file-notify--test-desc
+         (file-notify-add-watch
+           file-notify--test-tmpfile '(change) #'ignore)))
+   (should (file-notify-valid-p file-notify--test-desc))
+   ;; After calling `file-notify-rm-watch', the descriptor is not valid
+   ;; anymore.
+   (file-notify-rm-watch file-notify--test-desc)
+   (should-not (file-notify-valid-p file-notify--test-desc))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+          file-notify--test-tmpfile
+          '(change) #'file-notify--test-event-handler)))
+   (should (file-notify-valid-p file-notify--test-desc))
+   (file-notify--test-with-actions
+       (cond
+        ;; SMBSamba reports three `changed' events.
+       ((eq (file-notify--test-monitor) 'SMBSamba)
+         '(changed changed changed changed deleted stopped))
+       ;; GFam{File,Directory}Monitor do not detect the `changed' event
+        ;; reliably.
+       ((memq (file-notify--test-monitor)
+               '(GFamFileMonitor GFamDirectoryMonitor))
+        '((deleted stopped)
+          (changed deleted stopped)))
+       ;; GKqueueFileMonitor does not report the `changed' event.
+       ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
+        '(deleted stopped))
+       ;; There could be one or two `changed' events.
+       (t '((changed deleted stopped)
+            (changed changed deleted stopped))))
+     (write-region "another text" nil file-notify--test-tmpfile nil 'no-message)
+     (file-notify--test-wait-event)
+     (delete-file file-notify--test-tmpfile))
+   (file-notify--test-wait-event)
+   ;; After deleting the file, the descriptor is not valid anymore.
+   (should-not (file-notify-valid-p file-notify--test-desc))
+   (file-notify-rm-watch file-notify--test-desc)
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   (delete-file file-notify--test-tmpfile)
+   (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-desc))
+   (file-notify--test-with-actions
+       (cond
+       ;; w32notify does not raise `deleted' and `stopped' events for
+       ;; the watched directory.
+       ((string-equal (file-notify--test-library) "w32notify")
+        '(created changed deleted))
+        ;; SMBSamba reports three `changed' events.
+       ((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.
+       ((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))
+       (t '(created changed deleted deleted stopped)))
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (file-notify--test-wait-event)
+     (delete-directory file-notify--test-tmpdir 'recursive))
+   ;; After deleting the parent directory, the descriptor must not be
+   ;; valid anymore.
+   (should-not (file-notify-valid-p file-notify--test-desc))
+   ;; w32notify doesn't generate `stopped' events when the parent
+   ;; directory is deleted, which doesn't provide a chance for
+   ;; 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))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p)))
 
 (file-notify--deftest-remote file-notify-test05-file-validity
   "Check `file-notify-valid-p' via file notification for remote files.")
@@ -1198,58 +1119,41 @@ delivered."
   "Check `file-notify-valid-p' for directories."
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      (progn
-       (should
-        (setq file-notify--test-tmpfile
-              (make-temp-file "file-notify-test-parent" t)))
-       (should
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-                file-notify--test-tmpfile '(change) #'ignore)))
-        (should (file-notify-valid-p file-notify--test-desc))
-        ;; After removing the watch, the descriptor must not be valid
-        ;; anymore.
-        (file-notify-rm-watch file-notify--test-desc)
-        (file-notify--test-wait-for-events
-         (file-notify--test-timeout)
-        (not (file-notify-valid-p file-notify--test-desc)))
-        (should-not (file-notify-valid-p file-notify--test-desc))
-        (delete-directory file-notify--test-tmpfile 'recursive)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; On emba, `deleted' and `stopped' events of the directory are
-      ;; not detected.
-      (progn
-       (should
-        (setq file-notify--test-tmpfile
-              (make-temp-file "file-notify-test-parent" t)))
-       (should
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile '(change) #'ignore)))
-        (should (file-notify-valid-p file-notify--test-desc))
-        ;; After deleting the directory, the descriptor must not be
-        ;; valid anymore.
-        (delete-directory file-notify--test-tmpfile 'recursive)
-        (file-notify--test-wait-for-events
-        (file-notify--test-timeout)
-        (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))
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (should
+    (setq file-notify--test-desc
+         (file-notify-add-watch
+           file-notify--test-tmpdir '(change) #'ignore)))
+   (should (file-notify-valid-p file-notify--test-desc))
+   ;; After removing the watch, the descriptor must not be valid
+   ;; anymore.
+   (file-notify-rm-watch file-notify--test-desc)
+   (file-notify--test-wait-for-events
+    (file-notify--test-timeout)
+    (not (file-notify-valid-p file-notify--test-desc)))
+   (should-not (file-notify-valid-p file-notify--test-desc))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   (should
+    (setq file-notify--test-desc
+         (file-notify-add-watch
+          file-notify--test-tmpdir '(change) #'ignore)))
+   (should (file-notify-valid-p file-notify--test-desc))
+   ;; After deleting the directory, the descriptor must not be valid
+   ;; anymore.
+   (delete-directory file-notify--test-tmpdir 'recursive)
+   (file-notify--test-wait-for-events
+    (file-notify--test-timeout)
+    (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))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p)))
 
 (file-notify--deftest-remote file-notify-test06-dir-validity
   "Check `file-notify-valid-p' via file notification for remote directories.")
@@ -1259,89 +1163,79 @@ delivered."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
 
-  (should
-   (setq file-notify--test-tmpfile
-        (make-temp-file "file-notify-test-parent" t)))
-  (should
-   (setq file-notify--test-desc
-        (file-notify--test-add-watch
-         file-notify--test-tmpfile
-         '(change) #'file-notify--test-event-handler)))
-  (unwind-protect
-      (let ((n 10);00)
-            source-file-list target-file-list
-            (default-directory file-notify--test-tmpfile))
-        (dotimes (i n)
-         ;; It matters which direction we rename, at least for
-         ;; kqueue.  This backend parses directories in alphabetic
-         ;; order (x%d before y%d).  So we rename into both directions.
-         (if (evenp i)
-             (progn
-               (push (expand-file-name (format "x%d" i)) source-file-list)
-               (push (expand-file-name (format "y%d" i)) target-file-list))
-           (push (expand-file-name (format "y%d" i)) source-file-list)
-           (push (expand-file-name (format "x%d" i)) target-file-list)))
-        (file-notify--test-with-actions
-           (cond
-             ;; SMBSamba fires both `created' and `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-             (let (r)
-               (dotimes (_i (+ n n) r)
-                 (setq r (append '(created changed) r)))))
-            (t (make-list (+ n n) 'created)))
-          (let ((source-file-list source-file-list)
-                (target-file-list target-file-list))
-            (while (and source-file-list target-file-list)
-              (file-notify--test-wait-event)
-              (write-region "" nil (pop source-file-list) nil 'no-message)
-              (file-notify--test-wait-event)
-              (write-region "" nil (pop target-file-list) nil 'no-message))))
-        (file-notify--test-with-actions
-           (cond
-            ;; w32notify fires both `deleted' and `renamed' events.
-            ((string-equal (file-notify--test-library) "w32notify")
-             (let (r)
-               (dotimes (_i n r)
-                 (setq r (append '(deleted renamed) r)))))
-             ;; SMBWindows fires both `changed' and `deleted' events.
-            ((eq (file-notify--test-monitor) 'SMBWindows)
-             (let (r)
-               (dotimes (_i n r)
-                 (setq r (append '(changed deleted) r)))))
-             ;; SMBSamba fires both `changed' and `deleted' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-             (let (r)
-               (dotimes (_i n r)
-                 (setq r (append '(changed changed deleted) r)))))
-             ;; GFam{File,Directory}Monitor and GPollFileMonitor fire
-            ;; `changed' and `deleted' events, sometimes in random
-            ;; order.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
-             (let (r)
-               (dotimes (_i n (cons :random r))
-                 (setq r (append '(changed deleted) r)))))
-            (t (make-list n 'renamed)))
-          (let ((source-file-list source-file-list)
-                (target-file-list target-file-list))
-            (while (and source-file-list target-file-list)
-              (file-notify--test-wait-event)
-              (rename-file (pop source-file-list) (pop target-file-list) t))))
-        (file-notify--test-with-actions (make-list n 'deleted)
-          (dolist (file target-file-list)
-            (file-notify--test-wait-event)
-            (delete-file file)))
-        (delete-directory file-notify--test-tmpfile)
-        (if (string-equal (file-notify--test-library) "w32notify")
-            (file-notify--rm-descriptor file-notify--test-desc))
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
-
-;; Unpredictable failures, eg https://hydra.nixos.org/build/86016286
+  (with-file-notify-test
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+          file-notify--test-tmpdir
+          '(change) #'file-notify--test-event-handler)))
+   (let ((n 10);00)
+         source-file-list target-file-list
+         (default-directory file-notify--test-tmpdir))
+     (dotimes (i n)
+       ;; It matters which direction we rename, at least for kqueue.
+       ;; This backend parses directories in alphabetic order (x%d
+       ;; before y%d).  So we rename into both directions.
+       (if (evenp i)
+          (progn
+            (push (expand-file-name (format "x%d" i)) source-file-list)
+            (push (expand-file-name (format "y%d" i)) target-file-list))
+        (push (expand-file-name (format "y%d" i)) source-file-list)
+        (push (expand-file-name (format "x%d" i)) target-file-list)))
+     (file-notify--test-with-actions
+        (cond
+          ;; SMBSamba fires both `created' and `changed' events.
+         ((eq (file-notify--test-monitor) 'SMBSamba)
+          (let (r)
+            (dotimes (_i (+ n n) r)
+              (setq r (append '(created changed) r)))))
+         (t (make-list (+ n n) 'created)))
+       (let ((source-file-list source-file-list)
+             (target-file-list target-file-list))
+         (while (and source-file-list target-file-list)
+           (file-notify--test-wait-event)
+           (write-region "" nil (pop source-file-list) nil 'no-message)
+           (file-notify--test-wait-event)
+           (write-region "" nil (pop target-file-list) nil 'no-message))))
+     (file-notify--test-with-actions
+        (cond
+         ;; w32notify fires both `deleted' and `renamed' events.
+         ((string-equal (file-notify--test-library) "w32notify")
+          (let (r)
+            (dotimes (_i n r)
+              (setq r (append '(deleted renamed) r)))))
+          ;; SMBWindows fires both `changed' and `deleted' events.
+         ((eq (file-notify--test-monitor) 'SMBWindows)
+          (let (r)
+            (dotimes (_i n r)
+              (setq r (append '(changed deleted) r)))))
+          ;; SMBSamba fires both `changed' and `deleted' events.
+         ((eq (file-notify--test-monitor) 'SMBSamba)
+          (let (r)
+            (dotimes (_i n r)
+              (setq r (append '(changed changed deleted) r)))))
+          ;; GFam{File,Directory}Monitor and GPollFileMonitor fire
+         ;; `changed' and `deleted' events, sometimes in random order.
+         ((memq (file-notify--test-monitor)
+                 '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
+          (let (r)
+            (dotimes (_i n (cons :random r))
+              (setq r (append '(changed deleted) r)))))
+         (t (make-list n 'renamed)))
+       (let ((source-file-list source-file-list)
+             (target-file-list target-file-list))
+         (while (and source-file-list target-file-list)
+           (file-notify--test-wait-event)
+           (rename-file (pop source-file-list) (pop target-file-list) t))))
+     (file-notify--test-with-actions (make-list n 'deleted)
+       (dolist (file target-file-list)
+         (file-notify--test-wait-event)
+         (delete-file file)))
+     (file-notify--rm-descriptor file-notify--test-desc)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p))))
+
 (file-notify--deftest-remote file-notify-test07-many-events
   "Check that events are not dropped for remote directories.")
 
@@ -1350,95 +1244,85 @@ delivered."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
 
-  (unwind-protect
-      (progn
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-               file-notify--test-tmpfile
-               '(change) #'file-notify--test-event-handler)))
-        (should (file-notify-valid-p file-notify--test-desc))
-        (file-notify--test-with-actions
-           (cond
-             ;; SMBSamba reports four `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(changed changed changed changed))
-            ;; GKqueueFileMonitor does not report the `changed' event.
-            ((eq (file-notify--test-monitor) 'GKqueueFileMonitor) '())
-             ;; There could be one or two `changed' events.
-            (t '((changed)
-                 (changed changed))))
-          ;; There shouldn't be any problem, because the file is kept.
-          (with-temp-buffer
-            (let ((buffer-file-name file-notify--test-tmpfile)
-                  (make-backup-files t)
-                  (backup-by-copying t)
-                  (kept-new-versions 1)
-                  (delete-old-versions t))
-              (insert "another text")
-              (save-buffer))))
-        ;; After saving the buffer, the descriptor is still valid.
-        (should (file-notify-valid-p file-notify--test-desc))
-       (delete-file file-notify--test-tmpfile)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (unwind-protect
-      ;; It doesn't work for kqueue, because we don't use an implicit
-      ;; directory monitor.
-      (unless (string-equal (file-notify--test-library) "kqueue")
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-        (write-region
-         "any text" nil file-notify--test-tmpfile nil 'no-message)
-        (should
-         (setq file-notify--test-desc
-               (file-notify--test-add-watch
-                file-notify--test-tmpfile
-                '(change) #'file-notify--test-event-handler)))
-        (should (file-notify-valid-p file-notify--test-desc))
-        (file-notify--test-with-actions
-            (cond
-             ;; SMBWindows reports two `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBWindows)
-              '(changed changed))
-             ;; SMBSamba reports four `changed' events.
-            ((eq (file-notify--test-monitor) 'SMBSamba)
-              '(changed changed changed changed))
-            ;; GFam{File,Directory}Monitor and GPollFileMonitor
-            ;; report only the `changed' event.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
-             '(changed))
-            ;; GKqueueFileMonitor does not report the `changed' event.
-            ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
-             '(renamed created))
-             (t '(renamed created changed)))
-          ;; The file is renamed when creating a backup.  It shall
-          ;; still be watched.
-          (with-temp-buffer
-            (let ((buffer-file-name file-notify--test-tmpfile)
-                  (make-backup-files t)
-                  (backup-by-copying nil)
-                  (backup-by-copying-when-mismatch nil)
-                  (kept-new-versions 1)
-                  (delete-old-versions t))
-              (insert "another text")
-              (save-buffer))))
-        ;; After saving the buffer, the descriptor is still valid.
-        (should (file-notify-valid-p file-notify--test-desc))
-        (delete-file file-notify--test-tmpfile)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+          file-notify--test-tmpfile
+          '(change) #'file-notify--test-event-handler)))
+   (should (file-notify-valid-p file-notify--test-desc))
+   (file-notify--test-with-actions
+       (cond
+        ;; SMBSamba reports four `changed' events.
+       ((eq (file-notify--test-monitor) 'SMBSamba)
+         '(changed changed changed changed))
+       ;; GKqueueFileMonitor does not report the `changed' event.
+       ((eq (file-notify--test-monitor) 'GKqueueFileMonitor) '())
+        ;; There could be one or two `changed' events.
+       (t '((changed)
+            (changed changed))))
+     ;; There shouldn't be any problem, because the file is kept.
+     (with-temp-buffer
+       (let ((buffer-file-name file-notify--test-tmpfile)
+             (make-backup-files t)
+             (backup-by-copying t)
+             (kept-new-versions 1)
+             (delete-old-versions t))
+         (insert "another text")
+         (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)
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p))
+
+  (with-file-notify-test
+   ;; It doesn't work for kqueue, because we don't use an implicit
+   ;; directory monitor.
+   (unless (string-equal (file-notify--test-library) "kqueue")
+     (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+     (should
+      (setq file-notify--test-desc
+            (file-notify--test-add-watch
+             file-notify--test-tmpfile
+             '(change) #'file-notify--test-event-handler)))
+     (should (file-notify-valid-p file-notify--test-desc))
+     (file-notify--test-with-actions
+         (cond
+          ;; SMBWindows reports two `changed' events.
+         ((eq (file-notify--test-monitor) 'SMBWindows)
+           '(changed changed))
+          ;; SMBSamba reports four `changed' events.
+         ((eq (file-notify--test-monitor) 'SMBSamba)
+           '(changed changed changed changed))
+         ;; GFam{File,Directory}Monitor and GPollFileMonitor
+         ;; report only the `changed' event.
+         ((memq (file-notify--test-monitor)
+                 '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
+          '(changed))
+         ;; GKqueueFileMonitor does not report the `changed' event.
+         ((eq (file-notify--test-monitor) 'GKqueueFileMonitor)
+          '(renamed created))
+          (t '(renamed created changed)))
+       ;; The file is renamed when creating a backup.  It shall still be
+       ;; watched.
+       (with-temp-buffer
+         (let ((buffer-file-name file-notify--test-tmpfile)
+               (make-backup-files t)
+               (backup-by-copying nil)
+               (backup-by-copying-when-mismatch nil)
+               (kept-new-versions 1)
+               (delete-old-versions t))
+           (insert "another text")
+           (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)
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p))))
 
 (file-notify--deftest-remote file-notify-test08-backup
   "Check that backup keeps file notification for remote files.")
@@ -1453,157 +1337,143 @@ the file watch."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
 
-  ;; A directory to be watched.
-  (should
-   (setq file-notify--test-tmpfile
-         (make-temp-file "file-notify-test-parent" t)))
-  ;; A file to be watched.
-  (should
-   (setq file-notify--test-tmpfile1
-         (let ((file-notify--test-tmpdir file-notify--test-tmpfile))
-           (file-notify--test-make-temp-name))))
-  (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
-  (unwind-protect
-      (cl-flet (;; Directory monitor.
-                (dir-callback (event file)
-                 (let ((file-notify--test-desc file-notify--test-desc1))
-                   (file-notify--test-event-handler event file)))
-                ;; File monitor.
-                (file-callback (event file)
-                 (let ((file-notify--test-desc file-notify--test-desc2))
-                   (file-notify--test-event-handler event file))))
-        (should
-         (setq file-notify--test-desc1
-               (file-notify--test-add-watch
-                file-notify--test-tmpfile
-                '(change) #'dir-callback)
-               ;; This is needed for `file-notify--test-monitor'.
-               file-notify--test-desc file-notify--test-desc1))
-        (should
-         (setq file-notify--test-desc2
-               (file-notify--test-add-watch
-                file-notify--test-tmpfile1
-                '(change) #'file-callback)))
-        (should (file-notify-valid-p file-notify--test-desc1))
-        (should (file-notify-valid-p file-notify--test-desc2))
-        (should-not (equal file-notify--test-desc1 file-notify--test-desc2))
-        (let ((n 10));0))
-          ;; Run the test.
-          (file-notify--test-with-actions
-              ;; There could be one or two `changed' events.
-              (list
-               ;; SMBSamba.  Sometimes, tha last `changed' event is
-               ;; missing, so we add two alternatives.
-               (append
-                '(:random)
-                ;; Just the file monitor.
-                (make-list (* (/ n 2) 5) 'changed)
-                ;; Just the directory monitor.  Strange, not all
-                ;; `changed' events do arrive.
-                (make-list (1- (* (/ n 2) 10)) 'changed)
-                (make-list (/ n 2) 'created)
-                (make-list (/ n 2) 'created))
-                (append
-                '(:random)
-                ;; Just the file monitor.
-                (make-list (* (/ n 2) 5) 'changed)
-                ;; Just the directory monitor.  This is the alternative
-                ;; with all `changed' events.
-                (make-list (* (/ n 2) 10) 'changed)
-                (make-list (/ n 2) 'created)
-                (make-list (/ n 2) 'created))
-               ;; cygwin.
-               (append
-                '(:random)
-                (make-list (/ n 2) 'changed)
-                (make-list (/ n 2) 'created)
-                (make-list (/ n 2) 'changed))
-               (append
-                '(:random)
-                ;; Directory monitor and file monitor.
-                (make-list (/ n 2) 'changed)
-                (make-list (/ n 2) 'changed)
-                ;; Just the directory monitor.
-                (make-list (/ n 2) 'created)
-                (make-list (/ n 2) 'changed))
-               (append
-                '(:random)
-                ;; Directory monitor and file monitor.
-                (make-list (/ n 2) 'changed)
-                (make-list (/ n 2) 'changed)
-                (make-list (/ n 2) 'changed)
-                (make-list (/ n 2) 'changed)
-                ;; Just the directory monitor.
-                (make-list (/ n 2) 'created)
-                (make-list (/ n 2) 'changed))
-              (append
-                '(:random)
-               ;; Just the directory monitor.  GKqueueFileMonitor
-               ;; does not report the `changed' event.
-                (make-list (/ n 2) 'created)))
-            (dotimes (i n)
-              (file-notify--test-wait-event)
-              (if (evenp i)
-                  (write-region
-                   "any text" nil file-notify--test-tmpfile1 t 'no-message)
-                (let ((file-notify--test-tmpdir file-notify--test-tmpfile))
-                  (write-region
-                   "any text" nil
-                   (file-notify--test-make-temp-name) nil 'no-message))))))
-
-        ;; If we delete the file, the directory monitor shall still be
-        ;; active.  We receive the `deleted' event from both the
-        ;; directory and the file monitor.  The `stopped' event is
-        ;; from the file monitor.  It's undecided in which order the
-        ;; directory and the file monitor are triggered.
-        (file-notify--test-with-actions
-            '((:random deleted deleted stopped)
-              (:random deleted deleted deleted stopped))
-          (delete-file file-notify--test-tmpfile1))
-        (should (file-notify-valid-p file-notify--test-desc1))
-        (should-not (file-notify-valid-p file-notify--test-desc2))
-
-        ;; 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.
-            ((memq (file-notify--test-monitor)
-                    '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
-              '(deleted stopped))
-            ((string-equal (file-notify--test-library) "kqueue")
-              '(deleted stopped))
-             (t (append
-                 ;; The directory monitor raises a `deleted' event for
-                 ;; every file contained in the directory, we must
-                 ;; count them.
-                 (make-list
-                  (length
-                   (directory-files
-                    file-notify--test-tmpfile nil
-                    directory-files-no-dot-files-regexp 'nosort))
-                  'deleted)
-                 ;; The events of the directory itself.
-                 (cond
-                 ;; w32notify does not raise `deleted' and `stopped'
-                 ;; events for the watched directory.
-                  ((string-equal (file-notify--test-library) "w32notify")
-                   '())
-                  (t '(deleted stopped))))))
-          (delete-directory file-notify--test-tmpfile 'recursive))
-        (unless (getenv "EMACS_EMBA_CI")
-          (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))
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+   (cl-flet (;; Directory monitor.
+             (dir-callback (event file)
+               (let ((file-notify--test-desc file-notify--test-desc1))
+                 (file-notify--test-event-handler event file)))
+             ;; File monitor.
+             (file-callback (event file)
+               (let ((file-notify--test-desc file-notify--test-desc2))
+                 (file-notify--test-event-handler event file))))
+     (should
+      (setq file-notify--test-desc1
+            (file-notify--test-add-watch
+             file-notify--test-tmpdir
+             '(change) #'dir-callback)
+            ;; This is needed for `file-notify--test-monitor'.
+            file-notify--test-desc file-notify--test-desc1))
+     (should
+      (setq file-notify--test-desc2
+            (file-notify--test-add-watch
+             file-notify--test-tmpfile
+             '(change) #'file-callback)))
+     (should (file-notify-valid-p file-notify--test-desc1))
+     (should (file-notify-valid-p file-notify--test-desc2))
+     (should-not (equal file-notify--test-desc1 file-notify--test-desc2))
+     (let ((n 10));0))
+       ;; Run the test.
+       (file-notify--test-with-actions
+           ;; There could be one or two `changed' events.
+           (list
+            ;; SMBSamba.  Sometimes, tha last `changed' event is
+            ;; missing, so we add two alternatives.
+            (append
+             '(:random)
+             ;; Just the file monitor.
+             (make-list (* (/ n 2) 5) 'changed)
+             ;; Just the directory monitor.  Strange, not all `changed'
+             ;; events do arrive.
+             (make-list (1- (* (/ n 2) 10)) 'changed)
+             (make-list (/ n 2) 'created)
+             (make-list (/ n 2) 'created))
+            (append
+             '(:random)
+             ;; Just the file monitor.
+             (make-list (* (/ n 2) 5) 'changed)
+             ;; Just the directory monitor.  This is the alternative
+             ;; with all `changed' events.
+             (make-list (* (/ n 2) 10) 'changed)
+             (make-list (/ n 2) 'created)
+             (make-list (/ n 2) 'created))
+            ;; cygwin.
+            (append
+             '(:random)
+             (make-list (/ n 2) 'changed)
+             (make-list (/ n 2) 'created)
+             (make-list (/ n 2) 'changed))
+            (append
+             '(:random)
+             ;; Directory monitor and file monitor.
+             (make-list (/ n 2) 'changed)
+             (make-list (/ n 2) 'changed)
+             ;; Just the directory monitor.
+             (make-list (/ n 2) 'created)
+             (make-list (/ n 2) 'changed))
+            (append
+             '(:random)
+             ;; Directory monitor and file monitor.
+             (make-list (/ n 2) 'changed)
+             (make-list (/ n 2) 'changed)
+             (make-list (/ n 2) 'changed)
+             (make-list (/ n 2) 'changed)
+             ;; Just the directory monitor.
+             (make-list (/ n 2) 'created)
+             (make-list (/ n 2) 'changed))
+           (append
+             '(:random)
+            ;; Just the directory monitor.  GKqueueFileMonitor does not
+            ;; report the `changed' event.
+             (make-list (/ n 2) 'created)))
+         (dotimes (i n)
+           (file-notify--test-wait-event)
+           (if (evenp i)
+               (write-region
+                "any text" nil file-notify--test-tmpfile t 'no-message)
+             (write-region
+              "any text" nil
+              (file-notify--test-make-temp-name) nil 'no-message)))))
+
+     ;; If we delete the file, the directory monitor shall still be
+     ;; active.  We receive the `deleted' event from both the directory
+     ;; and the file monitor.  The `stopped' event is from the file
+     ;; monitor.  It's undecided in which order the directory and the
+     ;; file monitor are triggered.
+     (file-notify--test-with-actions
+         '((:random deleted deleted stopped)
+           (:random deleted deleted deleted stopped))
+       (delete-file file-notify--test-tmpfile))
+     (should (file-notify-valid-p file-notify--test-desc1))
+     (should-not (file-notify-valid-p file-notify--test-desc2))
+
+     ;; 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.
+         ((memq (file-notify--test-monitor)
+                 '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
+           '(deleted stopped))
+         ((string-equal (file-notify--test-library) "kqueue")
+           '(deleted stopped))
+          (t (append
+              ;; The directory monitor raises a `deleted' event for
+              ;; every file contained in the directory, we must count
+              ;; them.
+              (make-list
+               (length
+                (directory-files
+                 file-notify--test-tmpdir nil
+                 directory-files-no-dot-files-regexp 'nosort))
+               'deleted)
+              ;; The events of the directory itself.
+              (cond
+              ;; w32notify does not raise `deleted' and `stopped'
+              ;; events for the watched directory.
+               ((string-equal (file-notify--test-library) "w32notify")
+                '())
+               (t '(deleted stopped))))))
+       (delete-directory file-notify--test-tmpdir 'recursive))
+     (unless (getenv "EMACS_EMBA_CI")
+       (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))
+
+     ;; The environment shall be cleaned up.
+     (file-notify--test-cleanup-p))))
 
 (file-notify--deftest-remote file-notify-test09-watched-file-in-watched-dir
   "Check `file-notify-test09-watched-file-in-watched-dir' for remote files.")
@@ -1616,39 +1486,31 @@ the file watch."
   ;; GKqueueFileMonitor, because `file-notify--test-desc' is not set yet.
   (skip-unless (string-equal (file-notify--test-library) "kqueue"))
 
-  (should
-   (setq file-notify--test-tmpfile
-        (make-temp-file "file-notify-test-parent" t)))
-  (unwind-protect
-      (let ((file-notify--test-tmpdir file-notify--test-tmpfile)
-           descs)
-       (should-error
-        (while t
-          ;; We watch directories, because we want to reach the upper
-          ;; limit.  Watching a file might not be sufficient, because
-          ;; most of the libraries implement this as watching the
-          ;; upper directory.
-          (setq file-notify--test-tmpfile1
-                (make-temp-file "file-notify-test-parent" t)
-                descs
-                (cons
-                 (should
-                  (file-notify-add-watch
-                   file-notify--test-tmpfile1 '(change) #'ignore))
-                 descs)))
-        :type 'file-notify-error)
-       ;; Remove watches.  If we don't do it prior removing
-       ;; directories, Emacs crashes in batch mode.
-       (dolist (desc descs)
-        (file-notify-rm-watch desc))
-       ;; Remove directories.
-        (delete-directory file-notify--test-tmpfile 'recursive)
-
-        ;; The environment shall be cleaned up.
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (let (descs tmpfile)
+     (should-error
+      (while t
+       ;; We watch directories, because we want to reach the upper
+       ;; limit.  Watching a file might not be sufficient, because most
+       ;; of the libraries implement this as watching the upper
+       ;; directory.
+       (setq tmpfile
+             (make-temp-file
+               (expand-file-name
+                "file-notify-test-parent" file-notify--test-tmpdir)
+               t)
+             descs
+             (cons
+              (should (file-notify-add-watch tmpfile '(change) #'ignore))
+              descs)))
+      :type 'file-notify-error)
+     ;; Remove watches.  If we don't do it prior removing directories,
+     ;; Emacs crashes in batch mode.
+     (dolist (desc descs)
+       (file-notify-rm-watch desc))
+
+     ;; 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.")
@@ -1660,52 +1522,51 @@ the file watch."
   ;; This test does not work for kqueue (yet).
   (skip-when (string-equal (file-notify--test-library) "kqueue"))
 
-  (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-        file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
-
-  ;; Symlink a file.
-  (unwind-protect
-      (progn
-       (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
-        ;; Some systems, like MS Windows without sufficient
-        ;; privileges, do not allow creation of symbolic links.
-        (condition-case nil
-            (make-symbolic-link
-             file-notify--test-tmpfile1 file-notify--test-tmpfile)
-         (error (ert-skip "`make-symbolic-link' not supported")))
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-                file-notify--test-tmpfile
-                '(attribute-change change) #'file-notify--test-event-handler)))
-        (should (file-notify-valid-p file-notify--test-desc))
-
-        ;; Writing to either the symlink or the target should not
-        ;; raise any event.
-        (file-notify--test-with-actions nil
-          (write-region
-           "another text" nil file-notify--test-tmpfile nil 'no-message)
-          (write-region
-           "another text" nil file-notify--test-tmpfile1 nil 'no-message))
-        ;; Sanity check.
-        (file-notify--test-wait-for-events
-         (file-notify--test-timeout)
-         (not (input-pending-p)))
-        (should-not file-notify--test-events)
-
-        ;; Changing timestamp of the target should not raise any
-        ;; event.  We don't use `nofollow'.
-        (file-notify--test-with-actions nil
-          (set-file-times file-notify--test-tmpfile1 '(0 0))
-          (set-file-times file-notify--test-tmpfile '(0 0)))
-        ;; Sanity check.
-        (file-notify--test-wait-for-events
-         (file-notify--test-timeout)
-         (not (input-pending-p)))
-        (should-not file-notify--test-events)
-
-        ;; Changing timestamp of the symlink shows the event.
-        (file-notify--test-with-actions
+  (with-file-notify-test
+   (ert-with-temp-file file-notify--test-tmpfile1
+     :prefix ert-temp-file-prefix :suffix ert-temp-file-suffix
+     (delete-file file-notify--test-tmpfile)
+     ;; Symlink a file.
+     (write-region "any text" nil file-notify--test-tmpfile1 nil 'no-message)
+     ;; Some systems, like MS Windows without sufficient privileges, do
+     ;; not allow creation of symbolic links.
+     (condition-case nil
+         (make-symbolic-link
+          file-notify--test-tmpfile1 file-notify--test-tmpfile)
+       (error (ert-skip "`make-symbolic-link' not supported")))
+     (should
+      (setq file-notify--test-desc
+           (file-notify--test-add-watch
+             file-notify--test-tmpfile
+             '(attribute-change change) #'file-notify--test-event-handler)))
+     (should (file-notify-valid-p file-notify--test-desc))
+
+     ;; Writing to either the symlink or the target should not raise any
+     ;; event.
+     (file-notify--test-with-actions nil
+       (write-region
+        "another text" nil file-notify--test-tmpfile nil 'no-message)
+       (write-region
+        "another text" nil file-notify--test-tmpfile1 nil 'no-message))
+     ;; Sanity check.
+     (file-notify--test-wait-for-events
+      (file-notify--test-timeout)
+      (not (input-pending-p)))
+     (should-not file-notify--test-events)
+
+     ;; Changing timestamp of the target should not raise any event.  We
+     ;; don't use `nofollow'.
+     (file-notify--test-with-actions nil
+       (set-file-times file-notify--test-tmpfile1 '(0 0))
+       (set-file-times file-notify--test-tmpfile '(0 0)))
+     ;; Sanity check.
+     (file-notify--test-wait-for-events
+      (file-notify--test-timeout)
+      (not (input-pending-p)))
+     (should-not file-notify--test-events)
+
+     ;; Changing timestamp of the symlink shows the event.
+     (file-notify--test-with-actions
         (cond
          ;; w32notify does not distinguish between `changed' and
          ;; `attribute-changed'.
@@ -1719,66 +1580,59 @@ the file watch."
                    GKqueueFileMonitor GPollFileMonitor))
            '())
           (t '(attribute-changed)))
-         (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow))
-
-        ;; Deleting the target should not raise any event.
-        (file-notify--test-with-actions nil
-          (delete-file file-notify--test-tmpfile1)
-          (delete-file file-notify--test-tmpfile))
-        ;; Sanity check.
-        (file-notify--test-wait-for-events
-         (file-notify--test-timeout)
-         (not (input-pending-p)))
-        (should-not file-notify--test-events)
-
-        ;; The environment shall be cleaned up.
-       (file-notify-rm-watch file-notify--test-desc)
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup))
-
-  (setq file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
-        file-notify--test-tmpfile (file-notify--test-make-temp-name))
-
-  ;; Symlink a directory.
-  (unwind-protect
-      (let ((tmpfile (expand-file-name "foo" file-notify--test-tmpfile))
-            (tmpfile1 (expand-file-name "foo" file-notify--test-tmpfile1)))
-       (make-directory file-notify--test-tmpfile1)
-        (make-symbolic-link file-notify--test-tmpfile1 file-notify--test-tmpfile)
-       (write-region "any text" nil tmpfile1 nil 'no-message)
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-                file-notify--test-tmpfile
-                '(attribute-change change) #'file-notify--test-event-handler)))
-        (should (file-notify-valid-p file-notify--test-desc))
-
-        ;; None of the actions on a file in the symlinked directory
-        ;; will be reported.
-        (file-notify--test-with-actions nil
-          (write-region "another text" nil tmpfile nil 'no-message)
-          (write-region "another text" nil tmpfile1 nil 'no-message)
-          (set-file-times tmpfile '(0 0))
-          (set-file-times tmpfile '(0 0) 'nofollow)
-          (set-file-times tmpfile1 '(0 0))
-          (set-file-times tmpfile1 '(0 0) 'nofollow)
-          (delete-file tmpfile)
-          (delete-file tmpfile1))
-        ;; Sanity check.
-        (file-notify--test-wait-for-events
-         (file-notify--test-timeout)
-         (not (input-pending-p)))
-        (should-not file-notify--test-events)
-
-        ;; The environment shall be cleaned up.
-        (delete-directory file-notify--test-tmpdir 'recursive)
-       (file-notify-rm-watch file-notify--test-desc)
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+       (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow))
+
+     ;; Deleting the target should not raise any event.
+     (file-notify--test-with-actions nil
+       (delete-file file-notify--test-tmpfile1)
+       (delete-file file-notify--test-tmpfile))
+     ;; Sanity check.
+     (file-notify--test-wait-for-events
+      (file-notify--test-timeout)
+      (not (input-pending-p)))
+     (should-not file-notify--test-events)
+
+     ;; The environment shall be cleaned up.
+     (file-notify-rm-watch file-notify--test-desc)
+     (file-notify--test-cleanup-p)))
+
+  (with-file-notify-test
+   (ert-with-temp-directory file-notify--test-tmpfile1
+     :prefix (concat ert-temp-file-prefix "-parent") :suffix ert-temp-file-suffix
+     (delete-file file-notify--test-tmpfile)
+     ;; Symlink a directory.
+     (let ((tmpfile (expand-file-name "foo" file-notify--test-tmpfile))
+           (tmpfile1 (expand-file-name "foo" file-notify--test-tmpfile1)))
+       (make-symbolic-link file-notify--test-tmpfile1 file-notify--test-tmpfile)
+       (write-region "any text" nil tmpfile1 nil 'no-message)
+       (should
+       (setq file-notify--test-desc
+             (file-notify--test-add-watch
+               file-notify--test-tmpfile
+               '(attribute-change change) #'file-notify--test-event-handler)))
+       (should (file-notify-valid-p file-notify--test-desc))
+
+       ;; None of the actions on a file in the symlinked directory
+       ;; will be reported.
+       (file-notify--test-with-actions nil
+         (write-region "another text" nil tmpfile nil 'no-message)
+         (write-region "another text" nil tmpfile1 nil 'no-message)
+         (set-file-times tmpfile '(0 0))
+         (set-file-times tmpfile '(0 0) 'nofollow)
+         (set-file-times tmpfile1 '(0 0))
+         (set-file-times tmpfile1 '(0 0) 'nofollow)
+         (delete-file tmpfile)
+         (delete-file tmpfile1))
+       ;; Sanity check.
+       (file-notify--test-wait-for-events
+        (file-notify--test-timeout)
+        (not (input-pending-p)))
+       (should-not file-notify--test-events)
+
+       ;; The environment shall be cleaned up.
+       (delete-directory file-notify--test-tmpdir 'recursive)
+       (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.")
@@ -1790,60 +1644,45 @@ the file watch."
   ;; This test does not work for w32notify snd smb-notify.
   (skip-when (member (file-notify--test-library) '("w32notify" "smb-notify")))
 
-  (unwind-protect
-      (progn
-       (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
-       ;; File monitors like kqueue insist, that the watched file
-       ;; exists.  Directory monitors are not bound to this
-       ;; restriction.
-       (when (string-equal (file-notify--test-library) "kqueue")
-         (write-region
-          "any text" nil file-notify--test-tmpfile nil 'no-message))
-
-       (should
-        (setq file-notify--test-desc
-              (file-notify--test-add-watch
-                file-notify--test-tmpfile
-                '(attribute-change change) #'file-notify--test-event-handler)))
-        (should (file-notify-valid-p file-notify--test-desc))
-
-        ;; Unmounting the filesystem should stop watching.
-        (file-notify--test-with-actions '(stopped)
-          ;; We emulate unmounting by calling
-          ;; `file-notify-handle-event' with a corresponding event.
-          (file-notify-handle-event
-           (make-file-notify
-            :-event
-            (list file-notify--test-desc
-                  (pcase (file-notify--test-library)
-                    ((or "inotify" "inotifywait") '(unmount isdir))
-                    ((or "gfilenotify" "gio") '(unmounted))
-                    ("kqueue" '(revoke))
-                    (err (ert-fail (format "Library %s not supported" err))))
-                  (pcase (file-notify--test-library)
-                    ("kqueue" (file-local-name file-notify--test-tmpfile))
-                    (_ (file-local-name file-notify--test-tmpdir)))
-                  ;; In the inotify case, there is a 4th slot `cookie'.
-                  ;; Since it is unused for `unmount', we ignore it.
-                  )
-            :-callback
-            (pcase (file-notify--test-library)
-              ("inotify" #'file-notify--callback-inotify)
-              ("gfilenotify" #'file-notify--callback-gfilenotify)
-              ("kqueue" #'file-notify--callback-kqueue)
-              ((or "inotifywait" "gio") #'file-notify-callback)
-              (err (ert-fail (format "Library %s not supported" err)))))))
-
-        ;; The watch has been stopped.
-        (should-not (file-notify-valid-p file-notify--test-desc))
-
-        ;; The environment shall be cleaned up.
-        (when (string-equal (file-notify--test-library) "kqueue")
-          (delete-file file-notify--test-tmpfile))
-        (file-notify--test-cleanup-p))
-
-    ;; Cleanup.
-    (file-notify--test-cleanup)))
+  (with-file-notify-test
+   (should
+    (setq file-notify--test-desc
+         (file-notify--test-add-watch
+           file-notify--test-tmpfile
+           '(attribute-change change) #'file-notify--test-event-handler)))
+   (should (file-notify-valid-p file-notify--test-desc))
+
+   ;; Unmounting the filesystem should stop watching.
+   (file-notify--test-with-actions '(stopped)
+     ;; We emulate unmounting by inserting a corresponding event.
+     (insert-special-event
+      (make-file-notify
+       :-event
+       (list file-notify--test-desc
+             (pcase (file-notify--test-library)
+               ((or "inotify" "inotifywait") '(unmount isdir))
+               ((or "gfilenotify" "gio") '(unmounted))
+               ("kqueue" '(revoke))
+               (err (ert-fail (format "Library %s not supported" err))))
+             (pcase (file-notify--test-library)
+               ("kqueue" (file-local-name file-notify--test-tmpfile))
+               (_ (file-local-name file-notify--test-tmpdir)))
+             ;; In the inotify case, there is a 4th slot `cookie'.
+             ;; Since it is unused for `unmount', we ignore it.
+             )
+       :-callback
+       (pcase (file-notify--test-library)
+         ("inotify" #'file-notify--callback-inotify)
+         ("gfilenotify" #'file-notify--callback-gfilenotify)
+         ("kqueue" #'file-notify--callback-kqueue)
+         ((or "inotifywait" "gio") #'file-notify-callback)
+         (err (ert-fail (format "Library %s not supported" err)))))))
+
+   ;; The watch has been stopped.
+   (should-not (file-notify-valid-p file-notify--test-desc))
+
+   ;; The environment shall be cleaned up.
+   (file-notify--test-cleanup-p)))
 
 (file-notify--deftest-remote file-notify-test12-unmount
   "Check `file-notify-test12-unmount' for remote files.")
index 1cf010f1d145baabdb686ef67abe06822ba49040..98328b99b2b75b2ac619169bd55079ec31a17aa7 100644 (file)
@@ -7736,7 +7736,10 @@ process sentinels.  They shall not disturb each other."
 ;; selector "remote".
 (ert-deftest tramp-test46-file-notifications ()
   "Check that Tramp handles file notifications."
+  :tags '(:unstable)
   (skip-unless (tramp--test-enabled))
+  ;; filenotify.el was reworked in Emacs 31.
+  (skip-unless (tramp--test-emacs31-p))
 
   (let* ((tmp-name (tramp--test-make-temp-name))
         ;(file-notify-debug t)
@@ -7755,12 +7758,13 @@ process sentinels.  They shall not disturb each other."
        (progn
          (tramp--test-message "%S" desc1)
          (should-not (file-exists-p tmp-name))
-         (should (file-notify-valid-p desc1))
          (should (file-notify-valid-p desc2))
 
-         ;; Create the file.
+         ;; Create the file.  `file-notify-valid-p' requires that the
+         ;; watched file exists, so we cannot check it earlier for `desc1'.
          (write-region "foo" nil tmp-name)
          (should (file-exists-p tmp-name))
+         (should (file-notify-valid-p desc1))
          ;; Modify.
          (write-region "foo" nil tmp-name)
          (should (file-exists-p tmp-name))