]> code.delx.au - gnu-emacs/blob - test/automated/file-notify-tests.el
Adapt test in file-notify-tests.el
[gnu-emacs] / test / automated / file-notify-tests.el
1 ;;; file-notify-tests.el --- Tests of file notifications -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2013-2015 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 (defun file-notify--test-cleanup ()
66 "Cleanup after a test."
67 (file-notify-rm-watch file-notify--test-desc)
68
69 (when (and file-notify--test-tmpfile
70 (file-exists-p file-notify--test-tmpfile))
71 (if (directory-name-p file-notify--test-tmpfile)
72 (delete-directory file-notify--test-tmpfile)
73 (delete-file file-notify--test-tmpfile)))
74 (when (and file-notify--test-tmpfile1
75 (file-exists-p file-notify--test-tmpfile1))
76 (if (directory-name-p file-notify--test-tmpfile1)
77 (delete-directory file-notify--test-tmpfile1)
78 (delete-file file-notify--test-tmpfile1)))
79
80 (setq file-notify--test-tmpfile nil)
81 (setq file-notify--test-tmpfile1 nil)
82 (setq file-notify--test-desc nil)
83 (setq file-notify--test-results nil)
84 (setq file-notify--test-events nil)
85 (when file-notify--test-event
86 (error "file-notify--test-event should not be set but bound dynamically")))
87
88 (setq password-cache-expiry nil
89 tramp-verbose 0
90 tramp-message-show-message nil)
91
92 ;; This shall happen on hydra only.
93 (when (getenv "NIX_STORE")
94 (add-to-list 'tramp-remote-path 'tramp-own-remote-path))
95
96 ;; We do not want to try and fail `file-notify-add-watch'.
97 (defun file-notify--test-local-enabled ()
98 "Whether local file notification is enabled.
99 This is needed for local `temporary-file-directory' only, in the
100 remote case we return always t."
101 (or file-notify--library
102 (file-remote-p temporary-file-directory)))
103
104 (defvar file-notify--test-remote-enabled-checked nil
105 "Cached result of `file-notify--test-remote-enabled'.
106 If the function did run, the value is a cons cell, the `cdr'
107 being the result.")
108
109 (defun file-notify--test-remote-enabled ()
110 "Whether remote file notification is enabled."
111 (unless (consp file-notify--test-remote-enabled-checked)
112 (let (desc)
113 (ignore-errors
114 (and
115 (file-remote-p file-notify-test-remote-temporary-file-directory)
116 (file-directory-p file-notify-test-remote-temporary-file-directory)
117 (file-writable-p file-notify-test-remote-temporary-file-directory)
118 (setq desc
119 (file-notify-add-watch
120 file-notify-test-remote-temporary-file-directory
121 '(change) 'ignore))))
122 (setq file-notify--test-remote-enabled-checked (cons t desc))
123 (when desc (file-notify-rm-watch desc))))
124 ;; Return result.
125 (cdr file-notify--test-remote-enabled-checked))
126
127 (defmacro file-notify--deftest-remote (test docstring)
128 "Define ert `TEST-remote' for remote files."
129 (declare (indent 1))
130 `(ert-deftest ,(intern (concat (symbol-name test) "-remote")) ()
131 ,docstring
132 (let* ((temporary-file-directory
133 file-notify-test-remote-temporary-file-directory)
134 (ert-test (ert-get-test ',test)))
135 (skip-unless (file-notify--test-remote-enabled))
136 (tramp-cleanup-connection
137 (tramp-dissect-file-name temporary-file-directory) nil 'keep-password)
138 (funcall (ert-test-body ert-test)))))
139
140 (ert-deftest file-notify-test00-availability ()
141 "Test availability of `file-notify'."
142 (skip-unless (file-notify--test-local-enabled))
143 ;; Report the native library which has been used.
144 (if (null (file-remote-p temporary-file-directory))
145 (message "Local library: `%s'" file-notify--library)
146 (message "Remote command: `%s'"
147 (replace-regexp-in-string
148 "<[[:digit:]]+>\\'" ""
149 (process-name (cdr file-notify--test-remote-enabled-checked)))))
150 (should
151 (setq file-notify--test-desc
152 (file-notify-add-watch temporary-file-directory '(change) 'ignore)))
153 (file-notify--test-cleanup))
154
155 (file-notify--deftest-remote file-notify-test00-availability
156 "Test availability of `file-notify' for remote files.")
157
158 (ert-deftest file-notify-test01-add-watch ()
159 "Check `file-notify-add-watch'."
160 (skip-unless (file-notify--test-local-enabled))
161 ;; Check, that different valid parameters are accepted.
162 (should
163 (setq file-notify--test-desc
164 (file-notify-add-watch temporary-file-directory '(change) 'ignore)))
165 (file-notify-rm-watch file-notify--test-desc)
166 (should
167 (setq file-notify--test-desc
168 (file-notify-add-watch
169 temporary-file-directory '(attribute-change) 'ignore)))
170 (file-notify-rm-watch file-notify--test-desc)
171 (should
172 (setq file-notify--test-desc
173 (file-notify-add-watch
174 temporary-file-directory '(change attribute-change) 'ignore)))
175 (file-notify-rm-watch file-notify--test-desc)
176
177 ;; Check error handling.
178 (should-error (file-notify-add-watch 1 2 3 4)
179 :type 'wrong-number-of-arguments)
180 (should
181 (equal (should-error
182 (file-notify-add-watch 1 2 3))
183 '(wrong-type-argument 1)))
184 (should
185 (equal (should-error
186 (file-notify-add-watch temporary-file-directory 2 3))
187 '(wrong-type-argument 2)))
188 (should
189 (equal (should-error
190 (file-notify-add-watch temporary-file-directory '(change) 3))
191 '(wrong-type-argument 3)))
192
193 (file-notify--test-cleanup))
194
195 (file-notify--deftest-remote file-notify-test01-add-watch
196 "Check `file-notify-add-watch' for remote files.")
197
198 (defun file-notify--test-event-test ()
199 "Ert test function to be called by `file-notify--test-event-handler'.
200 We cannot pass arguments, so we assume that `file-notify--test-event'
201 is bound somewhere."
202 ;;(message "Event %S" file-notify--test-event)
203 ;; Check the descriptor.
204 (should (equal (car file-notify--test-event) file-notify--test-desc))
205 ;; Check the file name.
206 (should
207 (string-equal (file-notify--event-file-name file-notify--test-event)
208 file-notify--test-tmpfile))
209 ;; Check the second file name if exists.
210 (when (eq (nth 1 file-notify--test-event) 'renamed)
211 (should
212 (string-equal
213 (file-notify--event-file1-name file-notify--test-event)
214 file-notify--test-tmpfile1))))
215
216 (defun file-notify--test-event-handler (event)
217 "Run a test over FILE-NOTIFY--TEST-EVENT.
218 For later analysis, append the test result to
219 `file-notify--test-results' and the event to
220 `file-notify--test-events'."
221 (let* ((file-notify--test-event event)
222 (result (ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
223 (setq file-notify--test-events
224 (append file-notify--test-events `(,file-notify--test-event)))
225 (setq file-notify--test-results
226 (append file-notify--test-results `(,result)))))
227
228 (defun file-notify--test-make-temp-name ()
229 "Create a temporary file name for test."
230 (expand-file-name
231 (make-temp-name "file-notify-test") temporary-file-directory))
232
233 (defmacro file-notify--wait-for-events (timeout until)
234 "Wait for and return file notification events until form UNTIL is true.
235 TIMEOUT is the maximum time to wait for, in seconds."
236 `(with-timeout (,timeout (ignore))
237 (while (null ,until)
238 (read-event nil nil 0.1))))
239
240 (defmacro file-notify--test-with-events (n timeout assert-fn &rest body)
241 "Run BODY collecting N events and then run ASSERT-FN.
242 Don't wait longer than TIMEOUT seconds for the events to be delivered."
243 (declare (indent 3))
244 (let ((outer (make-symbol "outer")))
245 `(let ((,outer file-notify--test-events))
246 (let ((file-notify--test-events nil))
247 ,@body
248 (file-notify--wait-for-events
249 ,timeout (= ,n (length file-notify--test-events)))
250 (funcall ,assert-fn file-notify--test-events)
251 (setq ,outer (append ,outer file-notify--test-events)))
252 (setq file-notify--test-events ,outer))))
253
254 (ert-deftest file-notify-test02-events ()
255 "Check file creation/change/removal notifications."
256 (skip-unless (file-notify--test-local-enabled))
257 (unwind-protect
258 (progn
259 (setq file-notify--test-results nil
260 file-notify--test-events nil
261 file-notify--test-tmpfile (file-notify--test-make-temp-name)
262 file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
263 file-notify--test-desc
264 (file-notify-add-watch
265 file-notify--test-tmpfile
266 '(change) 'file-notify--test-event-handler))
267 (should file-notify--test-desc)
268
269 ;; Check creation, change, and deletion.
270 (file-notify--test-with-events
271 3 3 (lambda (events)
272 (should (equal '(created changed deleted)
273 (mapcar #'cadr events))))
274 (write-region
275 "any text" nil file-notify--test-tmpfile nil 'no-message)
276 (delete-file file-notify--test-tmpfile))
277
278 ;; Check copy.
279 (file-notify--test-with-events
280 3 3 (lambda (events)
281 (should (equal '(created changed deleted)
282 (mapcar #'cadr events))))
283 (write-region
284 "any text" nil file-notify--test-tmpfile nil 'no-message)
285 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
286 (delete-file file-notify--test-tmpfile)
287 (delete-file file-notify--test-tmpfile1))
288
289 ;; Check rename.
290 (file-notify--test-with-events
291 3 3 (lambda (events)
292 (should (equal '(created changed renamed)
293 (mapcar #'cadr events))))
294 (write-region
295 "any text" nil file-notify--test-tmpfile nil 'no-message)
296 (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
297 ;; After the rename, we won't get events anymore.
298 (delete-file file-notify--test-tmpfile1))
299
300 ;; Check the global sequence again just to make sure that
301 ;; `file-notify--test-events' has been set correctly.
302 (should (equal (mapcar #'cadr file-notify--test-events)
303 '(created changed deleted
304 created changed deleted
305 created changed renamed)))
306
307 (should file-notify--test-results)
308 (dolist (result file-notify--test-results)
309 ;;(message "%s" (ert-test-result-messages result))
310 (when (ert-test-failed-p result)
311 (ert-fail
312 (cadr (ert-test-result-with-condition-condition result))))))
313 (file-notify--test-cleanup)))
314
315 (file-notify--deftest-remote file-notify-test02-events
316 "Check file creation/change/removal notifications for remote files.")
317
318 (require 'autorevert)
319 (setq auto-revert-notify-exclude-dir-regexp "nothing-to-be-excluded"
320 auto-revert-remote-files t
321 auto-revert-stop-on-user-input nil)
322
323 (ert-deftest file-notify-test03-autorevert ()
324 "Check autorevert via file notification."
325 (skip-unless (file-notify--test-local-enabled))
326 ;; `auto-revert-buffers' runs every 5". And we must wait, until the
327 ;; file has been reverted.
328 (let ((timeout (if (file-remote-p temporary-file-directory) 60 10))
329 buf)
330 (unwind-protect
331 (progn
332 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
333
334 (write-region
335 "any text" nil file-notify--test-tmpfile nil 'no-message)
336 (setq buf (find-file-noselect file-notify--test-tmpfile))
337 (with-current-buffer buf
338 (should (string-equal (buffer-string) "any text"))
339 ;; `buffer-stale--default-function' checks for
340 ;; `verify-visited-file-modtime'. We must ensure that it
341 ;; returns nil.
342 (sleep-for 1)
343 (auto-revert-mode 1)
344
345 ;; `auto-revert-buffers' runs every 5".
346 (with-timeout (timeout (ignore))
347 (while (null auto-revert-notify-watch-descriptor)
348 (sleep-for 1)))
349
350 ;; Check, that file notification has been used.
351 (should auto-revert-mode)
352 (should auto-revert-use-notify)
353 (should auto-revert-notify-watch-descriptor)
354
355 ;; Modify file. We wait for a second, in order to
356 ;; have another timestamp.
357 (sleep-for 1)
358 (write-region
359 "another text" nil file-notify--test-tmpfile nil 'no-message)
360
361 ;; Check, that the buffer has been reverted.
362 (with-current-buffer (get-buffer-create "*Messages*")
363 (file-notify--wait-for-events
364 timeout
365 (string-match
366 (format-message "Reverting buffer `%s'." (buffer-name buf))
367 (buffer-string))))
368 (should (string-match "another text" (buffer-string)))))
369
370 ;; Exit.
371 (ignore-errors (kill-buffer buf))
372 (file-notify--test-cleanup))))
373
374 (file-notify--deftest-remote file-notify-test03-autorevert
375 "Check autorevert via file notification for remote files.")
376
377 (ert-deftest file-notify-test04-file-validity ()
378 "Check `file-notify-valid-p' for files."
379 (skip-unless (file-notify--test-local-enabled))
380 ;; The batch-mode operation of w32notify is fragile (there's no
381 ;; input threads to send the message to).
382 (skip-unless (not (and noninteractive (eq file-notify--library 'w32notify))))
383 (unwind-protect
384 (let ((temporary-file-directory (make-temp-file
385 "file-notify-test-parent" t)))
386 (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
387 (setq file-notify--test-desc (file-notify-add-watch
388 file-notify--test-tmpfile
389 '(change)
390 #'file-notify--test-event-handler))
391 (file-notify--test-with-events
392 2 3 (lambda (events)
393 (should (equal '(created changed)
394 (mapcar #'cadr events))))
395 (should (file-notify-valid-p file-notify--test-desc))
396 (write-region
397 "any text" nil file-notify--test-tmpfile nil 'no-message)
398 (should (file-notify-valid-p file-notify--test-desc)))
399 ;; After deleting the parent, the descriptor must not be valid
400 ;; anymore.
401 (delete-directory temporary-file-directory t)
402 (read-event nil nil 0.5)
403 (should-not (file-notify-valid-p file-notify--test-desc)))
404
405 ;; Exit.
406 (file-notify--test-cleanup)))
407
408 (file-notify--deftest-remote file-notify-test04-file-validity
409 "Check `file-notify-valid-p' via file notification for remote files.")
410
411 (ert-deftest file-notify-test05-dir-validity ()
412 "Check `file-notify-valid-p' for directories."
413 (skip-unless (file-notify--test-local-enabled))
414 (unwind-protect
415 (progn
416 (setq file-notify--test-tmpfile (file-name-as-directory
417 (file-notify--test-make-temp-name)))
418 (make-directory file-notify--test-tmpfile)
419 (setq file-notify--test-desc (file-notify-add-watch
420 file-notify--test-tmpfile
421 '(change)
422 #'file-notify--test-event-handler))
423 (should (file-notify-valid-p file-notify--test-desc))
424 (delete-directory file-notify--test-tmpfile t)
425 ;; After deleting the directory, the descriptor must not be
426 ;; valid anymore.
427 (read-event nil nil 0.5)
428 (should-not (file-notify-valid-p file-notify--test-desc)))
429
430 ;; Exit.
431 (file-notify--test-cleanup)))
432
433 (file-notify--deftest-remote file-notify-test05-dir-validity
434 "Check `file-notify-valid-p' via file notification for remote directories.")
435
436 (defun file-notify-test-all (&optional interactive)
437 "Run all tests for \\[file-notify]."
438 (interactive "p")
439 (if interactive
440 (ert-run-tests-interactively "^file-notify-")
441 (ert-run-tests-batch "^file-notify-")))
442
443 (provide 'file-notify-tests)
444 ;;; file-notify-tests.el ends here