]> code.delx.au - gnu-emacs/blob - test/lisp/filenotify-tests.el
-
[gnu-emacs] / test / lisp / filenotify-tests.el
1 ;;; file-notify-tests.el --- Tests of file notifications -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2013-2016 Free Software Foundation, Inc.
4
5 ;; Author: Michael Albinus <michael.albinus@gmx.de>
6
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.
11 ;;
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.
16 ;;
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/'.
19
20 ;;; Commentary:
21
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.
27
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.
33
34 ;; A whole test run can be performed calling the command `file-notify-test-all'.
35
36 ;;; Code:
37
38 (require 'ert)
39 (require 'filenotify)
40 (require 'tramp)
41
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
44 (cond
45 ((getenv "REMOTE_TEMPORARY_FILE_DIRECTORY"))
46 ((eq system-type 'windows-nt) null-device)
47 (t (add-to-list
48 'tramp-methods
49 '("mock"
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.")
57
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
65 (defconst file-notify--test-read-event-timeout 0.02
66 "Timeout for `read-event' calls.
67 It is different for local and remote file notification libraries.")
68
69 (defun file-notify--test-timeout ()
70 "Timeout to wait for arriving events, in seconds."
71 (cond
72 ((file-remote-p temporary-file-directory) 6)
73 ((string-equal (file-notify--test-library) "w32notify") 10)
74 ((eq system-type 'cygwin) 10)
75 (t 3)))
76
77 (defun file-notify--test-cleanup ()
78 "Cleanup after a test."
79 (file-notify-rm-watch file-notify--test-desc)
80
81 (ignore-errors
82 (delete-file (file-newest-backup file-notify--test-tmpfile)))
83 (ignore-errors
84 (if (file-directory-p file-notify--test-tmpfile)
85 (delete-directory file-notify--test-tmpfile 'recursive)
86 (delete-file file-notify--test-tmpfile)))
87 (ignore-errors
88 (if (file-directory-p file-notify--test-tmpfile1)
89 (delete-directory file-notify--test-tmpfile1 'recursive)
90 (delete-file file-notify--test-tmpfile1)))
91 (ignore-errors
92 (when (file-remote-p temporary-file-directory)
93 (tramp-cleanup-connection
94 (tramp-dissect-file-name temporary-file-directory) nil 'keep-password)))
95
96 (setq file-notify--test-tmpfile nil
97 file-notify--test-tmpfile1 nil
98 file-notify--test-desc nil
99 file-notify--test-results nil
100 file-notify--test-events nil)
101 (when file-notify--test-event
102 (error "file-notify--test-event should not be set but bound dynamically")))
103
104 (setq password-cache-expiry nil
105 tramp-verbose 0
106 tramp-message-show-message nil)
107
108 ;; This shall happen on hydra only.
109 (when (getenv "NIX_STORE")
110 (add-to-list 'tramp-remote-path 'tramp-own-remote-path))
111
112 ;; We do not want to try and fail `file-notify-add-watch'.
113 (defun file-notify--test-local-enabled ()
114 "Whether local file notification is enabled.
115 This is needed for local `temporary-file-directory' only, in the
116 remote case we return always t."
117 (or file-notify--library
118 (file-remote-p temporary-file-directory)))
119
120 (defvar file-notify--test-remote-enabled-checked nil
121 "Cached result of `file-notify--test-remote-enabled'.
122 If the function did run, the value is a cons cell, the `cdr'
123 being the result.")
124
125 (defun file-notify--test-remote-enabled ()
126 "Whether remote file notification is enabled."
127 (unless (consp file-notify--test-remote-enabled-checked)
128 (let (desc)
129 (ignore-errors
130 (and
131 (file-remote-p file-notify-test-remote-temporary-file-directory)
132 (file-directory-p file-notify-test-remote-temporary-file-directory)
133 (file-writable-p file-notify-test-remote-temporary-file-directory)
134 (setq desc
135 (file-notify-add-watch
136 file-notify-test-remote-temporary-file-directory
137 '(change) 'ignore))))
138 (setq file-notify--test-remote-enabled-checked (cons t desc))
139 (when desc (file-notify-rm-watch desc))))
140 ;; Return result.
141 (cdr file-notify--test-remote-enabled-checked))
142
143 (defun file-notify--test-library ()
144 "The used library for the test, as a string.
145 In the remote case, it is the process name which runs on the
146 remote host, or nil."
147 (if (null (file-remote-p temporary-file-directory))
148 (symbol-name file-notify--library)
149 (and (consp file-notify--test-remote-enabled-checked)
150 (processp (cdr file-notify--test-remote-enabled-checked))
151 (replace-regexp-in-string
152 "<[[:digit:]]+>\\'" ""
153 (process-name (cdr file-notify--test-remote-enabled-checked))))))
154
155 (defmacro file-notify--deftest-remote (test docstring)
156 "Define ert `TEST-remote' for remote files."
157 (declare (indent 1))
158 `(ert-deftest ,(intern (concat (symbol-name test) "-remote")) ()
159 ,docstring
160 :tags '(:expensive-test)
161 (let* ((temporary-file-directory
162 file-notify-test-remote-temporary-file-directory)
163 (file-notify--test-read-event-timeout 0.1)
164 (ert-test (ert-get-test ',test)))
165 (skip-unless (file-notify--test-remote-enabled))
166 (tramp-cleanup-connection
167 (tramp-dissect-file-name temporary-file-directory) nil 'keep-password)
168 (funcall (ert-test-body ert-test)))))
169
170 (ert-deftest file-notify-test00-availability ()
171 "Test availability of `file-notify'."
172 (skip-unless (file-notify--test-local-enabled))
173 ;; Report the native library which has been used.
174 (message "Library: `%s'" (file-notify--test-library))
175 (should
176 (setq file-notify--test-desc
177 (file-notify-add-watch temporary-file-directory '(change) 'ignore)))
178
179 ;; Cleanup.
180 (file-notify--test-cleanup))
181
182 (file-notify--deftest-remote file-notify-test00-availability
183 "Test availability of `file-notify' for remote files.")
184
185 (ert-deftest file-notify-test01-add-watch ()
186 "Check `file-notify-add-watch'."
187 (skip-unless (file-notify--test-local-enabled))
188
189 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
190 file-notify--test-tmpfile1
191 (format "%s/%s" file-notify--test-tmpfile (md5 (current-time-string))))
192
193 ;; Check, that different valid parameters are accepted.
194 (should
195 (setq file-notify--test-desc
196 (file-notify-add-watch temporary-file-directory '(change) 'ignore)))
197 (file-notify-rm-watch file-notify--test-desc)
198 (should
199 (setq file-notify--test-desc
200 (file-notify-add-watch
201 temporary-file-directory '(attribute-change) 'ignore)))
202 (file-notify-rm-watch file-notify--test-desc)
203 (should
204 (setq file-notify--test-desc
205 (file-notify-add-watch
206 temporary-file-directory '(change attribute-change) 'ignore)))
207 (file-notify-rm-watch file-notify--test-desc)
208 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
209 (should
210 (setq file-notify--test-desc
211 (file-notify-add-watch
212 file-notify--test-tmpfile '(change attribute-change) 'ignore)))
213 (file-notify-rm-watch file-notify--test-desc)
214 (delete-file file-notify--test-tmpfile)
215
216 ;; Check error handling.
217 (should-error (file-notify-add-watch 1 2 3 4)
218 :type 'wrong-number-of-arguments)
219 (should
220 (equal (should-error
221 (file-notify-add-watch 1 2 3))
222 '(wrong-type-argument 1)))
223 (should
224 (equal (should-error
225 (file-notify-add-watch temporary-file-directory 2 3))
226 '(wrong-type-argument 2)))
227 (should
228 (equal (should-error
229 (file-notify-add-watch temporary-file-directory '(change) 3))
230 '(wrong-type-argument 3)))
231 ;; The upper directory of a file must exist.
232 (should
233 (equal (should-error
234 (file-notify-add-watch
235 file-notify--test-tmpfile1 '(change attribute-change) 'ignore))
236 `(file-notify-error
237 "Directory does not exist" ,file-notify--test-tmpfile)))
238
239 ;; Cleanup.
240 (file-notify--test-cleanup))
241
242 (file-notify--deftest-remote file-notify-test01-add-watch
243 "Check `file-notify-add-watch' for remote files.")
244
245 (defun file-notify--test-event-test ()
246 "Ert test function to be called by `file-notify--test-event-handler'.
247 We cannot pass arguments, so we assume that `file-notify--test-event'
248 is bound somewhere."
249 ;; Check the descriptor.
250 (should (equal (car file-notify--test-event) file-notify--test-desc))
251 ;; Check the file name.
252 (should
253 (or (string-equal (file-notify--event-file-name file-notify--test-event)
254 file-notify--test-tmpfile)
255 (string-equal (file-notify--event-file-name file-notify--test-event)
256 file-notify--test-tmpfile1)
257 (string-equal (file-notify--event-file-name file-notify--test-event)
258 temporary-file-directory)))
259 ;; Check the second file name if exists.
260 (when (eq (nth 1 file-notify--test-event) 'renamed)
261 (should
262 (or (string-equal (file-notify--event-file1-name file-notify--test-event)
263 file-notify--test-tmpfile1)
264 (string-equal (file-notify--event-file1-name file-notify--test-event)
265 temporary-file-directory)))))
266
267 (defun file-notify--test-event-handler (event)
268 "Run a test over FILE-NOTIFY--TEST-EVENT.
269 For later analysis, append the test result to `file-notify--test-results'
270 and the event to `file-notify--test-events'."
271 (let* ((file-notify--test-event event)
272 (result
273 (ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
274 ;; Do not add lock files, this would confuse the checks.
275 (unless (string-match
276 (regexp-quote ".#")
277 (file-notify--event-file-name file-notify--test-event))
278 ;;(message "file-notify--test-event-handler %S" file-notify--test-event)
279 (setq file-notify--test-events
280 (append file-notify--test-events `(,file-notify--test-event))
281 file-notify--test-results
282 (append file-notify--test-results `(,result))))))
283
284 (defun file-notify--test-make-temp-name ()
285 "Create a temporary file name for test."
286 (expand-file-name
287 (make-temp-name "file-notify-test") temporary-file-directory))
288
289 (defmacro file-notify--wait-for-events (timeout until)
290 "Wait for and return file notification events until form UNTIL is true.
291 TIMEOUT is the maximum time to wait for, in seconds."
292 `(with-timeout (,timeout (ignore))
293 (while (null ,until)
294 (read-event nil nil file-notify--test-read-event-timeout))))
295
296 (defun file-notify--test-with-events-check (events)
297 "Check whether received events match one of the EVENTS alternatives."
298 (let (result)
299 (dolist (elt events result)
300 (setq result
301 (or result
302 (equal elt (mapcar #'cadr file-notify--test-events)))))))
303
304 (defun file-notify--test-with-events-explainer (events)
305 "Explain why `file-notify--test-with-events-check' fails."
306 (if (null (cdr events))
307 (format "Received events `%s' do not match expected events `%s'"
308 (mapcar #'cadr file-notify--test-events) (car events))
309 (format
310 "Received events `%s' do not match any sequence of expected events `%s'"
311 (mapcar #'cadr file-notify--test-events) events)))
312
313 (put 'file-notify--test-with-events-check 'ert-explainer
314 'file-notify--test-with-events-explainer)
315
316 (defmacro file-notify--test-with-events (events &rest body)
317 "Run BODY collecting events and then compare with EVENTS.
318 EVENTS is either a simple list of events, or a list of lists of
319 events, which represent different possible results. Don't wait
320 longer than timeout seconds for the events to be delivered."
321 (declare (indent 1))
322 (let ((outer (make-symbol "outer")))
323 `(let* ((,outer file-notify--test-events)
324 (events (if (consp (car ,events)) ,events (list ,events)))
325 (max-length (apply 'max (mapcar 'length events)))
326 create-lockfiles)
327 ;; Flush pending events.
328 (file-notify--wait-for-events
329 (file-notify--test-timeout)
330 (input-pending-p))
331 (let (file-notify--test-events)
332 ,@body
333 (file-notify--wait-for-events
334 ;; More events need more time. Use some fudge factor.
335 (* (ceiling max-length 100) (file-notify--test-timeout))
336 (= max-length (length file-notify--test-events)))
337 ;; One of the possible results shall match.
338 (should (file-notify--test-with-events-check events))
339 (setq ,outer (append ,outer file-notify--test-events)))
340 (setq file-notify--test-events ,outer))))
341
342 (ert-deftest file-notify-test02-events ()
343 "Check file creation/change/removal notifications."
344 (skip-unless (file-notify--test-local-enabled))
345
346 (unwind-protect
347 (progn
348 ;; Check file creation, change and deletion. It doesn't work
349 ;; for cygwin and kqueue, because we don't use an implicit
350 ;; directory monitor (kqueue), or the timings are too bad (cygwin).
351 (unless (or (eq system-type 'cygwin)
352 (string-equal (file-notify--test-library) "kqueue"))
353 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
354 (should
355 (setq file-notify--test-desc
356 (file-notify-add-watch
357 file-notify--test-tmpfile
358 '(change) 'file-notify--test-event-handler)))
359 (file-notify--test-with-events
360 (cond
361 ;; cygwin recognizes only `deleted' and `stopped' events.
362 ((eq system-type 'cygwin)
363 '(deleted stopped))
364 (t '(created changed deleted stopped)))
365 (write-region
366 "another text" nil file-notify--test-tmpfile nil 'no-message)
367 (read-event nil nil file-notify--test-read-event-timeout)
368 (delete-file file-notify--test-tmpfile))
369 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
370 (let (file-notify--test-events)
371 (file-notify-rm-watch file-notify--test-desc)))
372
373 ;; Check file change and deletion.
374 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
375 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
376 (should
377 (setq file-notify--test-desc
378 (file-notify-add-watch
379 file-notify--test-tmpfile
380 '(change) 'file-notify--test-event-handler)))
381 (file-notify--test-with-events
382 (cond
383 ;; cygwin recognizes only `deleted' and `stopped' events.
384 ((eq system-type 'cygwin)
385 '(deleted stopped))
386 ;; inotify and kqueue raise just one `changed' event.
387 ((or (string-equal "inotify" (file-notify--test-library))
388 (string-equal "kqueue" (file-notify--test-library)))
389 '(changed deleted stopped))
390 ;; gfilenotify raises one or two `changed' events
391 ;; randomly, no chance to test. So we accept both cases.
392 ((string-equal "gfilenotify" (file-notify--test-library))
393 '((changed deleted stopped)
394 (changed changed deleted stopped)))
395 (t '(changed changed deleted stopped)))
396 (read-event nil nil file-notify--test-read-event-timeout)
397 (write-region
398 "another text" nil file-notify--test-tmpfile nil 'no-message)
399 (read-event nil nil file-notify--test-read-event-timeout)
400 (delete-file file-notify--test-tmpfile))
401 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
402 (let (file-notify--test-events)
403 (file-notify-rm-watch file-notify--test-desc))
404
405 ;; Check file creation, change and deletion when watching a
406 ;; directory. There must be a `stopped' event when deleting
407 ;; the directory.
408 (let ((temporary-file-directory
409 (make-temp-file "file-notify-test-parent" t)))
410 (should
411 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
412 file-notify--test-desc
413 (file-notify-add-watch
414 temporary-file-directory
415 '(change) 'file-notify--test-event-handler)))
416 (file-notify--test-with-events
417 (cond
418 ;; w32notify does raise a `stopped' event when a
419 ;; watched directory is deleted.
420 ((string-equal (file-notify--test-library) "w32notify")
421 '(created changed deleted))
422 ;; cygwin recognizes only `deleted' and `stopped' events.
423 ((eq system-type 'cygwin)
424 '(deleted stopped))
425 ;; There are two `deleted' events, for the file and for
426 ;; the directory. Except for kqueue.
427 ((string-equal (file-notify--test-library) "kqueue")
428 '(created changed deleted stopped))
429 (t '(created changed deleted deleted stopped)))
430 (read-event nil nil file-notify--test-read-event-timeout)
431 (write-region
432 "any text" nil file-notify--test-tmpfile nil 'no-message)
433 (read-event nil nil file-notify--test-read-event-timeout)
434 (delete-directory temporary-file-directory 'recursive))
435 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
436 (let (file-notify--test-events)
437 (file-notify-rm-watch file-notify--test-desc)))
438
439 ;; Check copy of files inside a directory.
440 (let ((temporary-file-directory
441 (make-temp-file "file-notify-test-parent" t)))
442 (should
443 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
444 file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
445 file-notify--test-desc
446 (file-notify-add-watch
447 temporary-file-directory
448 '(change) 'file-notify--test-event-handler)))
449 (file-notify--test-with-events
450 (cond
451 ;; w32notify does not distinguish between `changed' and
452 ;; `attribute-changed'.
453 ((string-equal (file-notify--test-library) "w32notify")
454 '(created changed created changed changed changed changed
455 deleted deleted))
456 ;; cygwin recognizes only `deleted' and `stopped' events.
457 ((eq system-type 'cygwin)
458 '(deleted stopped))
459 ;; There are three `deleted' events, for two files and
460 ;; for the directory. Except for kqueue.
461 ((string-equal (file-notify--test-library) "kqueue")
462 '(created changed created changed deleted stopped))
463 (t '(created changed created changed
464 deleted deleted deleted stopped)))
465 (read-event nil nil file-notify--test-read-event-timeout)
466 (write-region
467 "any text" nil file-notify--test-tmpfile nil 'no-message)
468 (read-event nil nil file-notify--test-read-event-timeout)
469 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
470 ;; The next two events shall not be visible.
471 (read-event nil nil file-notify--test-read-event-timeout)
472 (set-file-modes file-notify--test-tmpfile 000)
473 (read-event nil nil file-notify--test-read-event-timeout)
474 (set-file-times file-notify--test-tmpfile '(0 0))
475 (read-event nil nil file-notify--test-read-event-timeout)
476 (delete-directory temporary-file-directory 'recursive))
477 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
478 (let (file-notify--test-events)
479 (file-notify-rm-watch file-notify--test-desc)))
480
481 ;; Check rename of files inside a directory.
482 (let ((temporary-file-directory
483 (make-temp-file "file-notify-test-parent" t)))
484 (should
485 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
486 file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
487 file-notify--test-desc
488 (file-notify-add-watch
489 temporary-file-directory
490 '(change) 'file-notify--test-event-handler)))
491 (file-notify--test-with-events
492 (cond
493 ;; w32notify does not distinguish between `changed' and
494 ;; `attribute-changed'.
495 ((string-equal (file-notify--test-library) "w32notify")
496 '(created changed renamed deleted))
497 ;; cygwin recognizes only `deleted' and `stopped' events.
498 ((eq system-type 'cygwin)
499 '(deleted stopped))
500 ;; There are two `deleted' events, for the file and for
501 ;; the directory. Except for kqueue.
502 ((string-equal (file-notify--test-library) "kqueue")
503 '(created changed renamed deleted stopped))
504 (t '(created changed renamed deleted deleted stopped)))
505 (read-event nil nil file-notify--test-read-event-timeout)
506 (write-region
507 "any text" nil file-notify--test-tmpfile nil 'no-message)
508 (read-event nil nil file-notify--test-read-event-timeout)
509 (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
510 ;; After the rename, we won't get events anymore.
511 (read-event nil nil file-notify--test-read-event-timeout)
512 (delete-directory temporary-file-directory 'recursive))
513 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
514 (let (file-notify--test-events)
515 (file-notify-rm-watch file-notify--test-desc)))
516
517 ;; Check attribute change. Does not work for cygwin.
518 (unless (eq system-type 'cygwin)
519 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
520 (write-region
521 "any text" nil file-notify--test-tmpfile nil 'no-message)
522 (should
523 (setq file-notify--test-desc
524 (file-notify-add-watch
525 file-notify--test-tmpfile
526 '(attribute-change) 'file-notify--test-event-handler)))
527 (file-notify--test-with-events
528 (cond
529 ;; w32notify does not distinguish between `changed' and
530 ;; `attribute-changed'.
531 ((string-equal (file-notify--test-library) "w32notify")
532 '(changed changed changed changed))
533 ;; For kqueue and in the remote case, `write-region'
534 ;; raises also an `attribute-changed' event.
535 ((or (string-equal (file-notify--test-library) "kqueue")
536 (file-remote-p temporary-file-directory))
537 '(attribute-changed attribute-changed attribute-changed))
538 (t '(attribute-changed attribute-changed)))
539 (read-event nil nil file-notify--test-read-event-timeout)
540 (write-region
541 "any text" nil file-notify--test-tmpfile nil 'no-message)
542 (read-event nil nil file-notify--test-read-event-timeout)
543 (set-file-modes file-notify--test-tmpfile 000)
544 (read-event nil nil file-notify--test-read-event-timeout)
545 (set-file-times file-notify--test-tmpfile '(0 0))
546 (read-event nil nil file-notify--test-read-event-timeout)
547 (delete-file file-notify--test-tmpfile))
548 ;; `file-notify-rm-watch' fires the `stopped' event. Suppress it.
549 (let (file-notify--test-events)
550 (file-notify-rm-watch file-notify--test-desc)))
551
552 ;; Check the global sequence again just to make sure that
553 ;; `file-notify--test-events' has been set correctly.
554 (should file-notify--test-results)
555 (dolist (result file-notify--test-results)
556 (when (ert-test-failed-p result)
557 (ert-fail
558 (cadr (ert-test-result-with-condition-condition result))))))
559
560 ;; Cleanup.
561 (file-notify--test-cleanup)))
562
563 (file-notify--deftest-remote file-notify-test02-events
564 "Check file creation/change/removal notifications for remote files.")
565
566 (require 'autorevert)
567 (setq auto-revert-notify-exclude-dir-regexp "nothing-to-be-excluded"
568 auto-revert-remote-files t
569 auto-revert-stop-on-user-input nil)
570
571 (ert-deftest file-notify-test03-autorevert ()
572 "Check autorevert via file notification."
573 (skip-unless (file-notify--test-local-enabled))
574 ;; `auto-revert-buffers' runs every 5". And we must wait, until the
575 ;; file has been reverted.
576 (let ((timeout (if (file-remote-p temporary-file-directory) 60 10))
577 buf)
578 (unwind-protect
579 (progn
580 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
581
582 (write-region
583 "any text" nil file-notify--test-tmpfile nil 'no-message)
584 (setq buf (find-file-noselect file-notify--test-tmpfile))
585 (with-current-buffer buf
586 (should (string-equal (buffer-string) "any text"))
587 ;; `buffer-stale--default-function' checks for
588 ;; `verify-visited-file-modtime'. We must ensure that it
589 ;; returns nil.
590 (sleep-for 1)
591 (auto-revert-mode 1)
592
593 ;; `auto-revert-buffers' runs every 5".
594 (with-timeout (timeout (ignore))
595 (while (null auto-revert-notify-watch-descriptor)
596 (sleep-for 1)))
597
598 ;; Check, that file notification has been used.
599 (should auto-revert-mode)
600 (should auto-revert-use-notify)
601 (should auto-revert-notify-watch-descriptor)
602
603 ;; Modify file. We wait for a second, in order to have
604 ;; another timestamp.
605 (with-current-buffer (get-buffer-create "*Messages*")
606 (narrow-to-region (point-max) (point-max)))
607 (sleep-for 1)
608 (write-region
609 "another text" nil file-notify--test-tmpfile nil 'no-message)
610
611 ;; Check, that the buffer has been reverted.
612 (with-current-buffer (get-buffer-create "*Messages*")
613 (file-notify--wait-for-events
614 timeout
615 (string-match
616 (format-message "Reverting buffer `%s'." (buffer-name buf))
617 (buffer-string))))
618 (should (string-match "another text" (buffer-string)))
619
620 ;; Stop file notification. Autorevert shall still work via polling.
621 ;; It doesn't work for w32notify.
622 (unless (string-equal (file-notify--test-library) "w32notify")
623 (file-notify-rm-watch auto-revert-notify-watch-descriptor)
624 (file-notify--wait-for-events
625 timeout (null auto-revert-use-notify))
626 (should-not auto-revert-use-notify)
627 (should-not auto-revert-notify-watch-descriptor)
628
629 ;; Modify file. We wait for two seconds, in order to
630 ;; have another timestamp. One second seems to be too
631 ;; short.
632 (with-current-buffer (get-buffer-create "*Messages*")
633 (narrow-to-region (point-max) (point-max)))
634 (sleep-for 2)
635 (write-region
636 "foo bla" nil file-notify--test-tmpfile nil 'no-message)
637
638 ;; Check, that the buffer has been reverted.
639 (with-current-buffer (get-buffer-create "*Messages*")
640 (file-notify--wait-for-events
641 timeout
642 (string-match
643 (format-message "Reverting buffer `%s'." (buffer-name buf))
644 (buffer-string))))
645 (should (string-match "foo bla" (buffer-string))))))
646
647 ;; Cleanup.
648 (with-current-buffer "*Messages*" (widen))
649 (ignore-errors (kill-buffer buf))
650 (file-notify--test-cleanup))))
651
652 (file-notify--deftest-remote file-notify-test03-autorevert
653 "Check autorevert via file notification for remote files.")
654
655 (ert-deftest file-notify-test04-file-validity ()
656 "Check `file-notify-valid-p' for files."
657 (skip-unless (file-notify--test-local-enabled))
658
659 (unwind-protect
660 (progn
661 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
662 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
663 (should
664 (setq file-notify--test-desc
665 (file-notify-add-watch
666 file-notify--test-tmpfile
667 '(change) #'file-notify--test-event-handler)))
668 (should (file-notify-valid-p file-notify--test-desc))
669 ;; After calling `file-notify-rm-watch', the descriptor is not
670 ;; valid anymore.
671 (file-notify-rm-watch file-notify--test-desc)
672 (should-not (file-notify-valid-p file-notify--test-desc))
673 (delete-file file-notify--test-tmpfile))
674
675 ;; Cleanup.
676 (file-notify--test-cleanup))
677
678 (unwind-protect
679 (progn
680 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
681 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
682 (should
683 (setq file-notify--test-desc
684 (file-notify-add-watch
685 file-notify--test-tmpfile
686 '(change) #'file-notify--test-event-handler)))
687 (file-notify--test-with-events
688 (cond
689 ;; cygwin recognizes only `deleted' and `stopped' events.
690 ((eq system-type 'cygwin)
691 '(deleted stopped))
692 ;; inotify and kqueue raise just one `changed' event.
693 ((or (string-equal "inotify" (file-notify--test-library))
694 (string-equal "kqueue" (file-notify--test-library)))
695 '(changed deleted stopped))
696 ;; gfilenotify raises one or two `changed' events
697 ;; randomly, no chance to test. So we accept both cases.
698 ((string-equal "gfilenotify" (file-notify--test-library))
699 '((changed deleted stopped)
700 (changed changed deleted stopped)))
701 (t '(changed changed deleted stopped)))
702 (should (file-notify-valid-p file-notify--test-desc))
703 (read-event nil nil file-notify--test-read-event-timeout)
704 (write-region
705 "another text" nil file-notify--test-tmpfile nil 'no-message)
706 (read-event nil nil file-notify--test-read-event-timeout)
707 (delete-file file-notify--test-tmpfile))
708 ;; After deleting the file, the descriptor is not valid anymore.
709 (should-not (file-notify-valid-p file-notify--test-desc))
710 (file-notify-rm-watch file-notify--test-desc))
711
712 ;; Cleanup.
713 (file-notify--test-cleanup))
714
715 (unwind-protect
716 ;; w32notify does not send a `stopped' event when deleting a
717 ;; directory. The test does not work, therefore.
718 (unless (string-equal (file-notify--test-library) "w32notify")
719 (let ((temporary-file-directory
720 (make-temp-file "file-notify-test-parent" t)))
721 (should
722 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
723 file-notify--test-desc
724 (file-notify-add-watch
725 temporary-file-directory
726 '(change) #'file-notify--test-event-handler)))
727 (file-notify--test-with-events
728 (cond
729 ;; cygwin recognizes only `deleted' and `stopped' events.
730 ((eq system-type 'cygwin)
731 '(deleted stopped))
732 ;; There are two `deleted' events, for the file and for
733 ;; the directory. Except for kqueue.
734 ((string-equal (file-notify--test-library) "kqueue")
735 '(created changed deleted stopped))
736 (t '(created changed deleted deleted stopped)))
737 (should (file-notify-valid-p file-notify--test-desc))
738 (read-event nil nil file-notify--test-read-event-timeout)
739 (write-region
740 "any text" nil file-notify--test-tmpfile nil 'no-message)
741 (read-event nil nil file-notify--test-read-event-timeout)
742 (delete-directory temporary-file-directory t))
743 ;; After deleting the parent directory, the descriptor must
744 ;; not be valid anymore.
745 (should-not (file-notify-valid-p file-notify--test-desc))))
746
747 ;; Cleanup.
748 (file-notify--test-cleanup)))
749
750 (file-notify--deftest-remote file-notify-test04-file-validity
751 "Check `file-notify-valid-p' via file notification for remote files.")
752
753 (ert-deftest file-notify-test05-dir-validity ()
754 "Check `file-notify-valid-p' for directories."
755 (skip-unless (file-notify--test-local-enabled))
756
757 (unwind-protect
758 (progn
759 (setq file-notify--test-tmpfile
760 (file-name-as-directory (file-notify--test-make-temp-name)))
761 (make-directory file-notify--test-tmpfile)
762 (should
763 (setq file-notify--test-desc
764 (file-notify-add-watch
765 file-notify--test-tmpfile
766 '(change) #'file-notify--test-event-handler)))
767 (should (file-notify-valid-p file-notify--test-desc))
768 ;; After removing the watch, the descriptor must not be valid
769 ;; anymore.
770 (file-notify-rm-watch file-notify--test-desc)
771 (file-notify--wait-for-events
772 (file-notify--test-timeout)
773 (not (file-notify-valid-p file-notify--test-desc)))
774 (should-not (file-notify-valid-p file-notify--test-desc)))
775
776 ;; Cleanup.
777 (file-notify--test-cleanup))
778
779 (unwind-protect
780 ;; The batch-mode operation of w32notify is fragile (there's no
781 ;; input threads to send the message to).
782 (unless (and noninteractive
783 (string-equal (file-notify--test-library) "w32notify"))
784 (setq file-notify--test-tmpfile
785 (file-name-as-directory (file-notify--test-make-temp-name)))
786 (make-directory file-notify--test-tmpfile)
787 (should
788 (setq file-notify--test-desc
789 (file-notify-add-watch
790 file-notify--test-tmpfile
791 '(change) #'file-notify--test-event-handler)))
792 (should (file-notify-valid-p file-notify--test-desc))
793 ;; After deleting the directory, the descriptor must not be
794 ;; valid anymore.
795 (delete-directory file-notify--test-tmpfile t)
796 (file-notify--wait-for-events
797 (file-notify--test-timeout)
798 (not (file-notify-valid-p file-notify--test-desc)))
799 (should-not (file-notify-valid-p file-notify--test-desc)))
800
801 ;; Cleanup.
802 (file-notify--test-cleanup)))
803
804 (file-notify--deftest-remote file-notify-test05-dir-validity
805 "Check `file-notify-valid-p' via file notification for remote directories.")
806
807 (ert-deftest file-notify-test06-many-events ()
808 "Check that events are not dropped."
809 :tags '(:expensive-test)
810 (skip-unless (file-notify--test-local-enabled))
811 ;; Under cygwin events arrive in random order. Impossible to define a test.
812 (skip-unless (not (eq system-type 'cygwin)))
813
814 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
815 (make-directory file-notify--test-tmpfile)
816 (should
817 (setq file-notify--test-desc
818 (file-notify-add-watch
819 file-notify--test-tmpfile
820 '(change) 'file-notify--test-event-handler)))
821 (unwind-protect
822 (let ((n 1000)
823 source-file-list target-file-list
824 (default-directory file-notify--test-tmpfile))
825 (dotimes (i n)
826 ;; It matters which direction we rename, at least for
827 ;; kqueue. This backend parses directories in alphabetic
828 ;; order (x%d before y%d). So we rename both directions.
829 (if (zerop (mod i 2))
830 (progn
831 (push (expand-file-name (format "x%d" i)) source-file-list)
832 (push (expand-file-name (format "y%d" i)) target-file-list))
833 (push (expand-file-name (format "y%d" i)) source-file-list)
834 (push (expand-file-name (format "x%d" i)) target-file-list)))
835 (file-notify--test-with-events (make-list (+ n n) 'created)
836 (let ((source-file-list source-file-list)
837 (target-file-list target-file-list))
838 (while (and source-file-list target-file-list)
839 (read-event nil nil file-notify--test-read-event-timeout)
840 (write-region "" nil (pop source-file-list) nil 'no-message)
841 (read-event nil nil file-notify--test-read-event-timeout)
842 (write-region "" nil (pop target-file-list) nil 'no-message))))
843 (file-notify--test-with-events
844 (cond
845 ;; w32notify fires both `deleted' and `renamed' events.
846 ((string-equal (file-notify--test-library) "w32notify")
847 (let (r)
848 (dotimes (_i n r)
849 (setq r (append '(deleted renamed) r)))))
850 (t (make-list n 'renamed)))
851 (let ((source-file-list source-file-list)
852 (target-file-list target-file-list))
853 (while (and source-file-list target-file-list)
854 (read-event nil nil file-notify--test-read-event-timeout)
855 (rename-file (pop source-file-list) (pop target-file-list) t))))
856 (file-notify--test-with-events (make-list n 'deleted)
857 (dolist (file target-file-list)
858 (read-event nil nil file-notify--test-read-event-timeout)
859 (delete-file file) file-notify--test-read-event-timeout)))
860
861 ;; Cleanup.
862 (file-notify--test-cleanup)))
863
864 (file-notify--deftest-remote file-notify-test06-many-events
865 "Check that events are not dropped for remote directories.")
866
867 (ert-deftest file-notify-test07-backup ()
868 "Check that backup keeps file notification."
869 (skip-unless (file-notify--test-local-enabled))
870
871 (unwind-protect
872 (progn
873 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
874 (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
875 (should
876 (setq file-notify--test-desc
877 (file-notify-add-watch
878 file-notify--test-tmpfile
879 '(change) #'file-notify--test-event-handler)))
880 (should (file-notify-valid-p file-notify--test-desc))
881 (file-notify--test-with-events
882 (cond
883 ;; For w32notify and in the remote case, there are two
884 ;; `changed' events.
885 ((or (string-equal (file-notify--test-library) "w32notify")
886 (file-remote-p temporary-file-directory))
887 '(changed changed))
888 (t '(changed)))
889 ;; There shouldn't be any problem, because the file is kept.
890 (with-temp-buffer
891 (let ((buffer-file-name file-notify--test-tmpfile)
892 (make-backup-files t)
893 (backup-by-copying t)
894 (kept-new-versions 1)
895 (delete-old-versions t))
896 (insert "another text")
897 (save-buffer))))
898 ;; After saving the buffer, the descriptor is still valid.
899 (should (file-notify-valid-p file-notify--test-desc))
900 (delete-file file-notify--test-tmpfile))
901
902 ;; Cleanup.
903 (file-notify--test-cleanup))
904
905 (unwind-protect
906 (progn
907 ;; It doesn't work for kqueue, because we don't use an
908 ;; implicit directory monitor.
909 (unless (string-equal (file-notify--test-library) "kqueue")
910 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
911 (write-region
912 "any text" nil file-notify--test-tmpfile nil 'no-message)
913 (should
914 (setq file-notify--test-desc
915 (file-notify-add-watch
916 file-notify--test-tmpfile
917 '(change) #'file-notify--test-event-handler)))
918 (should (file-notify-valid-p file-notify--test-desc))
919 (file-notify--test-with-events '(renamed created changed)
920 ;; The file is renamed when creating a backup. It shall
921 ;; still be watched.
922 (with-temp-buffer
923 (let ((buffer-file-name file-notify--test-tmpfile)
924 (make-backup-files t)
925 (backup-by-copying nil)
926 (backup-by-copying-when-mismatch nil)
927 (kept-new-versions 1)
928 (delete-old-versions t))
929 (insert "another text")
930 (save-buffer))))
931 ;; After saving the buffer, the descriptor is still valid.
932 (should (file-notify-valid-p file-notify--test-desc))
933 (delete-file file-notify--test-tmpfile)))
934
935 ;; Cleanup.
936 (file-notify--test-cleanup)))
937
938 (file-notify--deftest-remote file-notify-test07-backup
939 "Check that backup keeps file notification for remote files.")
940
941 (defun file-notify-test-all (&optional interactive)
942 "Run all tests for \\[file-notify]."
943 (interactive "p")
944 (if interactive
945 (ert-run-tests-interactively "^file-notify-")
946 (ert-run-tests-batch "^file-notify-")))
947
948 ;; TODO:
949
950 ;; * For w32notify, no stopped events arrive when a directory is removed.
951 ;; * Check, why cygwin recognizes only `deleted' and `stopped' events.
952
953 (provide 'file-notify-tests)
954 ;;; file-notify-tests.el ends here