1 ;;; file-notify-tests.el --- Tests of file notifications -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2013-2015 Free Software Foundation, Inc.
5 ;; Author: Michael Albinus <michael.albinus@gmx.de>
7 ;; This program is free software: you can redistribute it and/or
8 ;; modify it under the terms of the GNU General Public License as
9 ;; published by the Free Software Foundation, either version 3 of the
10 ;; License, or (at your option) any later version.
12 ;; This program is distributed in the hope that it will be useful, but
13 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ;; General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program. If not, see `http://www.gnu.org/licenses/'.
22 ;; Some of the tests require access to a remote host files. Since
23 ;; this could be problematic, a mock-up connection method "mock" is
24 ;; used. Emulating a remote connection, it simply calls "sh -i".
25 ;; Tramp's file name handlers still run, so this test is sufficient
26 ;; except for connection establishing.
28 ;; If you want to test a real Tramp connection, set
29 ;; $REMOTE_TEMPORARY_FILE_DIRECTORY to a suitable value in order to
30 ;; overwrite the default value. If you want to skip tests accessing a
31 ;; remote host, set this environment variable to "/dev/null" or
32 ;; whatever is appropriate on your system.
34 ;; A whole test run can be performed calling the command `file-notify-test-all'.
42 ;; There is no default value on w32 systems, which could work out of the box.
43 (defconst file-notify-test-remote-temporary-file-directory
45 ((getenv "REMOTE_TEMPORARY_FILE_DIRECTORY"))
46 ((eq system-type
'windows-nt
) null-device
)
50 (tramp-login-program "sh")
51 (tramp-login-args (("-i")))
52 (tramp-remote-shell "/bin/sh")
53 (tramp-remote-shell-args ("-c"))
54 (tramp-connection-timeout 10)))
55 (format "/mock::%s" temporary-file-directory
)))
56 "Temporary directory for Tramp tests.")
58 (defvar file-notify--test-tmpfile nil
)
59 (defvar file-notify--test-tmpfile1 nil
)
60 (defvar file-notify--test-desc nil
)
61 (defvar file-notify--test-results nil
)
62 (defvar file-notify--test-event nil
)
63 (defvar file-notify--test-events nil
)
64 (defvar file-notify--test-expected-events nil
)
66 (defun file-notify--test-timeout ()
67 "Timeout to wait for arriving events, in seconds."
68 (if (file-remote-p temporary-file-directory
) 6 3))
70 (defun file-notify--test-cleanup ()
71 "Cleanup after a test."
72 (file-notify-rm-watch file-notify--test-desc
)
74 (when (and file-notify--test-tmpfile
75 (file-exists-p file-notify--test-tmpfile
))
76 (if (file-directory-p file-notify--test-tmpfile
)
77 (delete-directory file-notify--test-tmpfile
'recursive
)
78 (delete-file file-notify--test-tmpfile
)))
79 (when (and file-notify--test-tmpfile1
80 (file-exists-p file-notify--test-tmpfile1
))
81 (if (file-directory-p file-notify--test-tmpfile1
)
82 (delete-directory file-notify--test-tmpfile1
'recursive
)
83 (delete-file file-notify--test-tmpfile1
)))
84 (when (file-remote-p temporary-file-directory
)
85 (tramp-cleanup-connection
86 (tramp-dissect-file-name temporary-file-directory
) nil
'keep-password
))
88 (setq file-notify--test-tmpfile nil
89 file-notify--test-tmpfile1 nil
90 file-notify--test-desc nil
91 file-notify--test-results nil
92 file-notify--test-events nil
93 file-notify--test-expected-events nil
)
94 (when file-notify--test-event
95 (error "file-notify--test-event should not be set but bound dynamically")))
97 (setq password-cache-expiry nil
99 tramp-message-show-message nil
)
101 ;; This shall happen on hydra only.
102 (when (getenv "NIX_STORE")
103 (add-to-list 'tramp-remote-path
'tramp-own-remote-path
))
105 ;; We do not want to try and fail `file-notify-add-watch'.
106 (defun file-notify--test-local-enabled ()
107 "Whether local file notification is enabled.
108 This is needed for local `temporary-file-directory' only, in the
109 remote case we return always t."
110 (or file-notify--library
111 (file-remote-p temporary-file-directory
)))
113 (defvar file-notify--test-remote-enabled-checked nil
114 "Cached result of `file-notify--test-remote-enabled'.
115 If the function did run, the value is a cons cell, the `cdr'
118 (defun file-notify--test-remote-enabled ()
119 "Whether remote file notification is enabled."
120 (unless (consp file-notify--test-remote-enabled-checked
)
124 (file-remote-p file-notify-test-remote-temporary-file-directory
)
125 (file-directory-p file-notify-test-remote-temporary-file-directory
)
126 (file-writable-p file-notify-test-remote-temporary-file-directory
)
128 (file-notify-add-watch
129 file-notify-test-remote-temporary-file-directory
130 '(change) 'ignore
))))
131 (setq file-notify--test-remote-enabled-checked
(cons t desc
))
132 (when desc
(file-notify-rm-watch desc
))))
134 (cdr file-notify--test-remote-enabled-checked
))
136 (defmacro file-notify--deftest-remote
(test docstring
)
137 "Define ert `TEST-remote' for remote files."
139 `(ert-deftest ,(intern (concat (symbol-name test
) "-remote")) ()
141 (let* ((temporary-file-directory
142 file-notify-test-remote-temporary-file-directory
)
143 (ert-test (ert-get-test ',test
)))
144 (skip-unless (file-notify--test-remote-enabled))
145 (tramp-cleanup-connection
146 (tramp-dissect-file-name temporary-file-directory
) nil
'keep-password
)
147 (funcall (ert-test-body ert-test
)))))
149 (ert-deftest file-notify-test00-availability
()
150 "Test availability of `file-notify'."
151 (skip-unless (file-notify--test-local-enabled))
152 ;; Report the native library which has been used.
153 (if (null (file-remote-p temporary-file-directory
))
154 (message "Local library: `%s'" file-notify--library
)
155 (message "Remote command: `%s'"
156 (replace-regexp-in-string
157 "<[[:digit:]]+>\\'" ""
158 (process-name (cdr file-notify--test-remote-enabled-checked
)))))
160 (setq file-notify--test-desc
161 (file-notify-add-watch temporary-file-directory
'(change) 'ignore
)))
164 (file-notify--test-cleanup))
166 (file-notify--deftest-remote file-notify-test00-availability
167 "Test availability of `file-notify' for remote files.")
169 (ert-deftest file-notify-test01-add-watch
()
170 "Check `file-notify-add-watch'."
171 (skip-unless (file-notify--test-local-enabled))
173 (setq file-notify--test-tmpfile
(file-notify--test-make-temp-name)
174 file-notify--test-tmpfile1
175 (format "%s/%s" file-notify--test-tmpfile
(md5 (current-time-string))))
177 ;; Check, that different valid parameters are accepted.
179 (setq file-notify--test-desc
180 (file-notify-add-watch temporary-file-directory
'(change) 'ignore
)))
181 (file-notify-rm-watch file-notify--test-desc
)
183 (setq file-notify--test-desc
184 (file-notify-add-watch
185 temporary-file-directory
'(attribute-change) 'ignore
)))
186 (file-notify-rm-watch file-notify--test-desc
)
188 (setq file-notify--test-desc
189 (file-notify-add-watch
190 temporary-file-directory
'(change attribute-change
) 'ignore
)))
191 (file-notify-rm-watch file-notify--test-desc
)
192 ;; The file does not need to exist, just the upper directory.
194 (setq file-notify--test-desc
195 (file-notify-add-watch
196 file-notify--test-tmpfile
'(change attribute-change
) 'ignore
)))
197 (file-notify-rm-watch file-notify--test-desc
)
199 ;; Check error handling.
200 (should-error (file-notify-add-watch 1 2 3 4)
201 :type
'wrong-number-of-arguments
)
204 (file-notify-add-watch 1 2 3))
205 '(wrong-type-argument 1)))
208 (file-notify-add-watch temporary-file-directory
2 3))
209 '(wrong-type-argument 2)))
212 (file-notify-add-watch temporary-file-directory
'(change) 3))
213 '(wrong-type-argument 3)))
214 ;; The upper directory of a file must exist.
217 (file-notify-add-watch
218 file-notify--test-tmpfile1
'(change attribute-change
) 'ignore
))
220 "Directory does not exist" ,file-notify--test-tmpfile
)))
223 (file-notify--test-cleanup))
225 (file-notify--deftest-remote file-notify-test01-add-watch
226 "Check `file-notify-add-watch' for remote files.")
228 (defun file-notify--test-event-test ()
229 "Ert test function to be called by `file-notify--test-event-handler'.
230 We cannot pass arguments, so we assume that `file-notify--test-event'
232 ;; Check the descriptor.
233 (should (equal (car file-notify--test-event
) file-notify--test-desc
))
234 ;; Check the file name.
236 (or (string-equal (file-notify--event-file-name file-notify--test-event
)
237 file-notify--test-tmpfile
)
238 (string-equal (directory-file-name
240 (file-notify--event-file-name file-notify--test-event
)))
241 file-notify--test-tmpfile
)))
242 ;; Check the second file name if exists.
243 (when (eq (nth 1 file-notify--test-event
) 'renamed
)
246 (file-notify--event-file1-name file-notify--test-event
)
247 file-notify--test-tmpfile1
))))
249 (defun file-notify--test-event-handler (event)
250 "Run a test over FILE-NOTIFY--TEST-EVENT.
251 For later analysis, append the test result to `file-notify--test-results'
252 and the event to `file-notify--test-events'."
253 (let* ((file-notify--test-event event
)
255 (ert-run-test (make-ert-test :body
'file-notify--test-event-test
))))
256 ;; Do not add temporary files, this would confuse the checks.
257 (unless (string-match
259 (file-notify--event-file-name file-notify--test-event
))
260 ;;(message "file-notify--test-event-handler %S" file-notify--test-event)
261 (setq file-notify--test-events
262 (append file-notify--test-events
`(,file-notify--test-event
))
263 file-notify--test-results
264 (append file-notify--test-results
`(,result
))))))
266 (defun file-notify--test-make-temp-name ()
267 "Create a temporary file name for test."
269 (make-temp-name "file-notify-test") temporary-file-directory
))
271 (defmacro file-notify--wait-for-events
(timeout until
)
272 "Wait for and return file notification events until form UNTIL is true.
273 TIMEOUT is the maximum time to wait for, in seconds."
274 `(with-timeout (,timeout
(ignore))
276 (read-event nil nil
0.1))))
278 (defmacro file-notify--test-with-events
(events &rest body
)
279 "Run BODY collecting events and then compare with EVENTS.
280 Don't wait longer than timeout seconds for the events to be delivered."
282 (let ((outer (make-symbol "outer")))
283 `(let ((,outer file-notify--test-events
))
284 (setq file-notify--test-expected-events
285 (append file-notify--test-expected-events
,events
))
286 (let (file-notify--test-events)
288 (file-notify--wait-for-events
289 (file-notify--test-timeout)
290 (= (length ,events
) (length file-notify--test-events
)))
291 (should (equal ,events
(mapcar #'cadr file-notify--test-events
)))
292 (setq ,outer
(append ,outer file-notify--test-events
)))
293 (setq file-notify--test-events
,outer
))))
295 (ert-deftest file-notify-test02-events
()
296 "Check file creation/change/removal notifications."
297 (skip-unless (file-notify--test-local-enabled))
298 ;; Under cygwin there are so bad timings that it doesn't make sense to test.
299 (skip-unless (not (eq system-type
'cygwin
)))
301 (setq file-notify--test-tmpfile
(file-notify--test-make-temp-name)
302 file-notify--test-tmpfile1
(file-notify--test-make-temp-name))
306 ;; Check creation, change and deletion.
307 (setq file-notify--test-desc
308 (file-notify-add-watch
309 file-notify--test-tmpfile
310 '(change) 'file-notify--test-event-handler
))
311 (file-notify--test-with-events '(created changed deleted
)
313 "any text" nil file-notify--test-tmpfile nil
'no-message
)
314 (delete-file file-notify--test-tmpfile
))
315 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
316 (let (file-notify--test-events)
317 (file-notify-rm-watch file-notify--test-desc
))
319 ;; Check creation, change and deletion. There must be a
320 ;; `stopped' event when deleting the directory. It doesn't
321 ;; work for w32notify.
322 (unless (eq file-notify--library
'w32notify
)
323 (make-directory file-notify--test-tmpfile
)
324 (setq file-notify--test-desc
325 (file-notify-add-watch
326 file-notify--test-tmpfile
327 '(change) 'file-notify--test-event-handler
))
328 (file-notify--test-with-events
329 ;; There are two `deleted' events, for the file and for
331 '(created changed deleted deleted stopped
)
333 "any text" nil
(expand-file-name "foo" file-notify--test-tmpfile
)
335 (delete-directory file-notify--test-tmpfile
'recursive
))
336 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
337 (let (file-notify--test-events)
338 (file-notify-rm-watch file-notify--test-desc
)))
341 (setq file-notify--test-desc
342 (file-notify-add-watch
343 file-notify--test-tmpfile
344 '(change) 'file-notify--test-event-handler
))
345 (should file-notify--test-desc
)
346 (file-notify--test-with-events
347 ;; w32notify does not distinguish between `changed' and
348 ;; `attribute-changed'.
349 (if (eq file-notify--library
'w32notify
)
350 '(created changed changed deleted
)
351 '(created changed deleted
))
353 "any text" nil file-notify--test-tmpfile nil
'no-message
)
354 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1
)
355 ;; The next two events shall not be visible.
356 (set-file-modes file-notify--test-tmpfile
000)
357 (read-event nil nil
0.1) ; In order to distinguish the events.
358 (set-file-times file-notify--test-tmpfile
'(0 0))
359 (delete-file file-notify--test-tmpfile
)
360 (delete-file file-notify--test-tmpfile1
))
361 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
362 (let (file-notify--test-events)
363 (file-notify-rm-watch file-notify--test-desc
))
366 (setq file-notify--test-desc
367 (file-notify-add-watch
368 file-notify--test-tmpfile
369 '(change) 'file-notify--test-event-handler
))
370 (should file-notify--test-desc
)
371 (file-notify--test-with-events '(created changed renamed
)
373 "any text" nil file-notify--test-tmpfile nil
'no-message
)
374 (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1
)
375 ;; After the rename, we won't get events anymore.
376 (delete-file file-notify--test-tmpfile1
))
377 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
378 (let (file-notify--test-events)
379 (file-notify-rm-watch file-notify--test-desc
))
381 ;; Check attribute change. It doesn't work for w32notify.
382 (unless (eq file-notify--library
'w32notify
)
383 (setq file-notify--test-desc
384 (file-notify-add-watch
385 file-notify--test-tmpfile
386 '(attribute-change) 'file-notify--test-event-handler
))
387 (file-notify--test-with-events
388 (if (file-remote-p temporary-file-directory
)
389 ;; In the remote case, `write-region' raises also an
390 ;; `attribute-changed' event.
391 '(attribute-changed attribute-changed attribute-changed
)
392 '(attribute-changed attribute-changed
))
393 ;; We must use short delays between the operations.
394 ;; Otherwise, not all events arrive us in the remote case.
396 "any text" nil file-notify--test-tmpfile nil
'no-message
)
397 (read-event nil nil
0.1)
398 (set-file-modes file-notify--test-tmpfile
000)
399 (read-event nil nil
0.1)
400 (set-file-times file-notify--test-tmpfile
'(0 0))
401 (read-event nil nil
0.1)
402 (delete-file file-notify--test-tmpfile
))
403 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
404 (let (file-notify--test-events)
405 (file-notify-rm-watch file-notify--test-desc
)))
407 ;; Check the global sequence again just to make sure that
408 ;; `file-notify--test-events' has been set correctly.
409 (should (equal (mapcar #'cadr file-notify--test-events
)
410 file-notify--test-expected-events
))
411 (should file-notify--test-results
)
412 (dolist (result file-notify--test-results
)
413 (when (ert-test-failed-p result
)
415 (cadr (ert-test-result-with-condition-condition result
))))))
418 (file-notify--test-cleanup)))
420 (file-notify--deftest-remote file-notify-test02-events
421 "Check file creation/change/removal notifications for remote files.")
423 (require 'autorevert
)
424 (setq auto-revert-notify-exclude-dir-regexp
"nothing-to-be-excluded"
425 auto-revert-remote-files t
426 auto-revert-stop-on-user-input nil
)
428 (ert-deftest file-notify-test03-autorevert
()
429 "Check autorevert via file notification."
430 (skip-unless (file-notify--test-local-enabled))
431 ;; `auto-revert-buffers' runs every 5". And we must wait, until the
432 ;; file has been reverted.
433 (let ((timeout (if (file-remote-p temporary-file-directory
) 60 10))
437 (setq file-notify--test-tmpfile
(file-notify--test-make-temp-name))
440 "any text" nil file-notify--test-tmpfile nil
'no-message
)
441 (setq buf
(find-file-noselect file-notify--test-tmpfile
))
442 (with-current-buffer buf
443 (should (string-equal (buffer-string) "any text"))
444 ;; `buffer-stale--default-function' checks for
445 ;; `verify-visited-file-modtime'. We must ensure that it
450 ;; `auto-revert-buffers' runs every 5".
451 (with-timeout (timeout (ignore))
452 (while (null auto-revert-notify-watch-descriptor
)
455 ;; Check, that file notification has been used.
456 (should auto-revert-mode
)
457 (should auto-revert-use-notify
)
458 (should auto-revert-notify-watch-descriptor
)
460 ;; Modify file. We wait for a second, in order to have
461 ;; another timestamp.
462 (with-current-buffer (get-buffer-create "*Messages*")
463 (narrow-to-region (point-max) (point-max)))
466 "another text" nil file-notify--test-tmpfile nil
'no-message
)
468 ;; Check, that the buffer has been reverted.
469 (with-current-buffer (get-buffer-create "*Messages*")
470 (file-notify--wait-for-events
473 (format-message "Reverting buffer `%s'." (buffer-name buf
))
475 (should (string-match "another text" (buffer-string)))
477 ;; Stop file notification. Autorevert shall still work via polling.
478 (file-notify-rm-watch auto-revert-notify-watch-descriptor
)
479 (file-notify--wait-for-events
480 timeout
(null auto-revert-use-notify
))
481 (should-not auto-revert-use-notify
)
482 (should-not auto-revert-notify-watch-descriptor
)
484 ;; Modify file. We wait for two seconds, in order to have
485 ;; another timestamp. One second seems to be too short.
486 (with-current-buffer (get-buffer-create "*Messages*")
487 (narrow-to-region (point-max) (point-max)))
490 "foo bla" nil file-notify--test-tmpfile nil
'no-message
)
492 ;; Check, that the buffer has been reverted.
493 (with-current-buffer (get-buffer-create "*Messages*")
494 (file-notify--wait-for-events
497 (format-message "Reverting buffer `%s'." (buffer-name buf
))
499 (should (string-match "foo bla" (buffer-string)))))
502 (with-current-buffer "*Messages*" (widen))
503 (ignore-errors (kill-buffer buf
))
504 (file-notify--test-cleanup))))
506 (file-notify--deftest-remote file-notify-test03-autorevert
507 "Check autorevert via file notification for remote files.")
509 (ert-deftest file-notify-test04-file-validity
()
510 "Check `file-notify-valid-p' for files."
511 (skip-unless (file-notify--test-local-enabled))
512 ;; Under cygwin there are so bad timings that it doesn't make sense to test.
513 (skip-unless (not (eq system-type
'cygwin
)))
517 (setq file-notify--test-tmpfile
(file-notify--test-make-temp-name)
518 file-notify--test-desc
519 (file-notify-add-watch
520 file-notify--test-tmpfile
521 '(change) #'file-notify--test-event-handler
))
522 (file-notify--test-with-events '(created changed deleted
)
523 (should (file-notify-valid-p file-notify--test-desc
))
525 "any text" nil file-notify--test-tmpfile nil
'no-message
)
526 (delete-file file-notify--test-tmpfile
))
527 ;; After deleting the file, the descriptor is still valid.
528 (should (file-notify-valid-p file-notify--test-desc
))
529 ;; After removing the watch, the descriptor must not be valid
531 (file-notify-rm-watch file-notify--test-desc
)
532 (should-not (file-notify-valid-p file-notify--test-desc
)))
535 (file-notify--test-cleanup))
538 ;; The batch-mode operation of w32notify is fragile (there's no
539 ;; input threads to send the message to).
540 ;(unless (and noninteractive (eq file-notify--library 'w32notify))
541 (unless (eq file-notify--library
'w32notify
)
542 (let ((temporary-file-directory
543 (make-temp-file "file-notify-test-parent" t
)))
544 (setq file-notify--test-tmpfile
(file-notify--test-make-temp-name)
545 file-notify--test-desc
546 (file-notify-add-watch
547 file-notify--test-tmpfile
548 '(change) #'file-notify--test-event-handler
))
549 (file-notify--test-with-events '(created changed deleted stopped
)
550 (should (file-notify-valid-p file-notify--test-desc
))
552 "any text" nil file-notify--test-tmpfile nil
'no-message
)
553 (delete-directory temporary-file-directory t
))
554 ;; After deleting the parent directory, the descriptor must
555 ;; not be valid anymore.
556 (should-not (file-notify-valid-p file-notify--test-desc
))))
559 (file-notify--test-cleanup)))
561 (file-notify--deftest-remote file-notify-test04-file-validity
562 "Check `file-notify-valid-p' via file notification for remote files.")
564 (ert-deftest file-notify-test05-dir-validity
()
565 "Check `file-notify-valid-p' for directories."
566 (skip-unless (file-notify--test-local-enabled))
570 (setq file-notify--test-tmpfile
571 (file-name-as-directory (file-notify--test-make-temp-name)))
572 (make-directory file-notify--test-tmpfile
)
573 (setq file-notify--test-desc
574 (file-notify-add-watch
575 file-notify--test-tmpfile
576 '(change) #'file-notify--test-event-handler
))
577 (should (file-notify-valid-p file-notify--test-desc
))
578 ;; After removing the watch, the descriptor must not be valid
580 (file-notify-rm-watch file-notify--test-desc
)
581 (file-notify--wait-for-events
582 (file-notify--test-timeout)
583 (not (file-notify-valid-p file-notify--test-desc
)))
584 (should-not (file-notify-valid-p file-notify--test-desc
)))
587 (file-notify--test-cleanup))
590 ;; The batch-mode operation of w32notify is fragile (there's no
591 ;; input threads to send the message to).
592 (unless (and noninteractive
(eq file-notify--library
'w32notify
))
593 (setq file-notify--test-tmpfile
594 (file-name-as-directory (file-notify--test-make-temp-name)))
595 (make-directory file-notify--test-tmpfile
)
596 (setq file-notify--test-desc
597 (file-notify-add-watch
598 file-notify--test-tmpfile
599 '(change) #'file-notify--test-event-handler
))
600 (should (file-notify-valid-p file-notify--test-desc
))
601 ;; After deleting the directory, the descriptor must not be
603 (delete-directory file-notify--test-tmpfile t
)
604 (file-notify--wait-for-events
605 (file-notify--test-timeout)
606 (not (file-notify-valid-p file-notify--test-desc
)))
607 (should-not (file-notify-valid-p file-notify--test-desc
)))
610 (file-notify--test-cleanup)))
612 (file-notify--deftest-remote file-notify-test05-dir-validity
613 "Check `file-notify-valid-p' via file notification for remote directories.")
615 (defun file-notify-test-all (&optional interactive
)
616 "Run all tests for \\[file-notify]."
619 (ert-run-tests-interactively "^file-notify-")
620 (ert-run-tests-batch "^file-notify-")))
624 ;; * For w32notify, no stopped events arrive when a directory is removed.
625 ;; * Try to handle arriving events under cygwin reliably.
627 (provide 'file-notify-tests
)
628 ;;; file-notify-tests.el ends here