aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Albinus2018-07-18 16:51:56 +0200
committerMichael Albinus2018-07-18 16:51:56 +0200
commit7a258fa0bb9f9e7600229b95ed54ee5ee0bc9a0c (patch)
tree9654a226674f2153c703512f1298adc1866d1ceb
parentcb50077b1eb7c1467f2f200e01599b391d025bfa (diff)
downloademacs-7a258fa0bb9f9e7600229b95ed54ee5ee0bc9a0c.tar.gz
emacs-7a258fa0bb9f9e7600229b95ed54ee5ee0bc9a0c.zip
Adapt shadowfile.el for Tramp (Bug#4526, Bug#4846)
* etc/NEWS: Mention changes in shadowfile.el. * lisp/shadowfile.el (top): Require 'tramp instead of 'ange-ftp. (shadow-cluster): New defstruct. (shadow-make-cluster, shadow-cluster-name, shadow-cluster-primary) (shadow-cluster-regexp, shadow-get-user) (shadow-parse-fullname): Remove. (shadow-info-file, shadow-todo-file, shadow-system-name) (shadow-homedir, shadow-regexp-superquote, shadow-suffix) (shadow-set-cluster, shadow-get-cluster, shadow-site-name) (shadow-name-site, shadow-site-primary, shadow-site-cluster) (shadow-read-site, shadow-parse-name, shadow-make-fullname) (shadow-replace-name-component, shadow-local-file) (shadow-expand-cluster-in-file-name, shadow-contract-file-name) (shadow-same-site, shadow-file-match, shadow-define-cluster) (shadow-define-literal-group, shadow-define-regexp-group) (shadow-make-group, shadow-shadows-of-1, shadow-read-files) (shadow-write-info-file, shadow-write-todo-file) (shadow-initialize): Adapt variables and functions. * test/lisp/shadowfile-tests.el: New file.
-rw-r--r--etc/NEWS8
-rw-r--r--lisp/shadowfile.el462
-rw-r--r--test/lisp/shadowfile-tests.el876
3 files changed, 1114 insertions, 232 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 92331108e95..1551c36c5a3 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -94,12 +94,20 @@ it now shows the global revision number, in the form of its changeset
94hash value. To get back the previous behavior, customize the new 94hash value. To get back the previous behavior, customize the new
95option 'vc-hg-symbolic-revision-styles' to the value '("{rev}")'. 95option 'vc-hg-symbolic-revision-styles' to the value '("{rev}")'.
96 96
97---
98** shadowfile.el has been rewritten to support Tramp file names.
99
97 100
98* New Modes and Packages in Emacs 26.2 101* New Modes and Packages in Emacs 26.2
99 102
100 103
101* Incompatible Lisp Changes in Emacs 26.2 104* Incompatible Lisp Changes in Emacs 26.2
102 105
106---
107** shadowfile config files have changed their syntax.
108Existing files "~/.emacs.d/shadows" and "~/.emacs.d/shadow_todo" must
109be removed prior using the changed 'shadow-*' commands.
110
103 111
104* Lisp Changes in Emacs 26.2 112* Lisp Changes in Emacs 26.2
105 113
diff --git a/lisp/shadowfile.el b/lisp/shadowfile.el
index 0095d6959ef..e1a9b8e1d98 100644
--- a/lisp/shadowfile.el
+++ b/lisp/shadowfile.el
@@ -25,37 +25,38 @@
25;; This package helps you to keep identical copies of files in more than one 25;; This package helps you to keep identical copies of files in more than one
26;; place - possibly on different machines. When you save a file, it checks 26;; place - possibly on different machines. When you save a file, it checks
27;; whether it is on the list of files with "shadows", and if so, it tries to 27;; whether it is on the list of files with "shadows", and if so, it tries to
28;; copy it when you exit Emacs (or use the shadow-copy-files command). 28;; copy it when you exit Emacs (or use the `shadow-copy-files' command).
29 29
30;; Installation & Use: 30;; Installation & Use:
31 31
32;; Add clusters (if necessary) and file groups with shadow-define-cluster, 32;; Add clusters (if necessary) and file groups with `shadow-define-cluster',
33;; shadow-define-literal-group, and shadow-define-regexp-group (see the 33;; `shadow-define-literal-group', and `shadow-define-regexp-group' (see the
34;; documentation for these functions for information on how and when to use 34;; documentation for these functions for information on how and when to use
35;; them). After doing this once, everything should be automatic. 35;; them). After doing this once, everything should be automatic.
36 36
37;; The lists of clusters and shadows are saved in a ~/.emacs.d/shadows 37;; The lists of clusters and shadows are saved in `shadow-info-file',
38;; (`shadow-info-file') file, so that they can be remembered from one 38;; so that they can be remembered from one Emacs session to another,
39;; Emacs session to another, even (as much as possible) if the Emacs 39;; even (as much as possible) if the Emacs session terminates
40;; session terminates abnormally. The files needing to be copied are 40;; abnormally. The files needing to be copied are stored in
41;; stored in `shadow-todo-file'; if a file cannot be copied for any 41;; `shadow-todo-file'; if a file cannot be copied for any reason, it
42;; reason, it will stay on the list to be tried again next time. The 42;; will stay on the list to be tried again next time. The
43;; `shadow-info-file' file should itself have shadows on all your accounts 43;; `shadow-info-file' file should itself have shadows on all your
44;; so that the information in it is consistent everywhere, but 44;; accounts so that the information in it is consistent everywhere,
45;; `shadow-todo-file' is local information and should have no shadows. 45;; but `shadow-todo-file' is local information and should have no
46;; shadows.
46 47
47;; If you do not want to copy a particular file, you can answer "no" and 48;; If you do not want to copy a particular file, you can answer "no" and
48;; be asked again next time you hit C-x 4 s or exit Emacs. If you do not 49;; be asked again next time you hit "C-x 4 s" or exit Emacs. If you do not
49;; want to be asked again, use shadow-cancel, and you will not be asked 50;; want to be asked again, use "M-x shadow-cancel", and you will not be asked
50;; until you change the file and save it again. If you do not want to 51;; until you change the file and save it again. If you do not want to
51;; shadow that file ever again, you can edit it out of the shadows 52;; shadow that file ever again, you can edit it out of the shadows
52;; buffer. Anytime you edit the shadows buffer, you must type M-x 53;; buffer. Anytime you edit the shadows buffer, you must type "M-x
53;; shadow-read-files to load in the new information, or your changes will 54;; shadow-read-files" to load in the new information, or your changes will
54;; be overwritten! 55;; be overwritten!
55 56
56;; Bugs & Warnings: 57;; Bugs & Warnings:
57;; 58;;
58;; - It is bad to have two emacses both running shadowfile at the same 59;; - It is bad to have two Emacsen both running shadowfile at the same
59;; time. It tries to detect this condition, but is not always successful. 60;; time. It tries to detect this condition, but is not always successful.
60;; 61;;
61;; - You have to be careful not to edit a file in two locations 62;; - You have to be careful not to edit a file in two locations
@@ -64,19 +65,16 @@
64;; 65;;
65;; - It ought to check modification times of both files to make sure 66;; - It ought to check modification times of both files to make sure
66;; it is doing the right thing. This will have to wait until 67;; it is doing the right thing. This will have to wait until
67;; file-newer-than-file-p works between machines. 68;; `file-newer-than-file-p' works between machines.
68;; 69;;
69;; - It will not make directories for you, it just fails to copy files 70;; - It will not make directories for you, it just fails to copy files
70;; that belong in non-existent directories. 71;; that belong in non-existent directories.
71;;
72;; Please report any bugs to me (boris@gnu.org). Also let me know
73;; if you have suggestions or would like to be informed of updates.
74 72
75 73
76;;; Code: 74;;; Code:
77 75
78(require 'cl-lib) 76(require 'cl-lib)
79(require 'ange-ftp) 77(require 'tramp)
80 78
81;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
82;;; Variables 80;;; Variables
@@ -107,35 +105,35 @@ files that have been changed and need to be copied to other systems."
107 :type 'boolean 105 :type 'boolean
108 :group 'shadow) 106 :group 'shadow)
109 107
110;; FIXME in a sense, this changed in 24.4 (addition of locate-user-emacs-file), 108(defcustom shadow-info-file (locate-user-emacs-file "shadows" ".shadows")
111;; but due to the weird way this variable is initialized to nil, it didn't
112;; literally change. Same for shadow-todo-file.
113(defcustom shadow-info-file nil
114 "File to keep shadow information in. 109 "File to keep shadow information in.
115The `shadow-info-file' should be shadowed to all your accounts to 110The `shadow-info-file' should be shadowed to all your accounts to
116ensure consistency. Default: ~/.emacs.d/shadows" 111ensure consistency. Default: ~/.emacs.d/shadows"
117 :type '(choice (const nil) file) 112 :type 'file
118 :group 'shadow) 113 :group 'shadow
114 :version "26.2")
119 115
120(defcustom shadow-todo-file nil 116(defcustom shadow-todo-file
117 (locate-user-emacs-file "shadow_todo" ".shadow_todo")
121 "File to store the list of uncopied shadows in. 118 "File to store the list of uncopied shadows in.
122This means that if a remote system is down, or for any reason you cannot or 119This means that if a remote system is down, or for any reason you cannot or
123decide not to copy your shadow files at the end of one Emacs session, it will 120decide not to copy your shadow files at the end of one Emacs session, it will
124remember and ask you again in your next Emacs session. 121remember and ask you again in your next Emacs session.
125This file must NOT be shadowed to any other system, it is host-specific. 122This file must NOT be shadowed to any other system, it is host-specific.
126Default: ~/.emacs.d/shadow_todo" 123Default: ~/.emacs.d/shadow_todo"
127 :type '(choice (const nil) file) 124 :type 'file
128 :group 'shadow) 125 :group 'shadow
126 :version "26.2")
129 127
130 128
131;;; The following two variables should in most cases initialize themselves 129;;; The following two variables should in most cases initialize themselves
132;;; correctly. They are provided as variables in case the defaults are wrong 130;;; correctly. They are provided as variables in case the defaults are wrong
133;;; on your machine (and for efficiency). 131;;; on your machine (and for efficiency).
134 132
135(defvar shadow-system-name (system-name) 133(defvar shadow-system-name (concat "/" (system-name) ":")
136 "The complete hostname of this machine.") 134 "The identification for local files on this machine.")
137 135
138(defvar shadow-homedir nil 136(defvar shadow-homedir "~"
139 "Your home directory on this machine.") 137 "Your home directory on this machine.")
140 138
141;;; 139;;;
@@ -186,12 +184,12 @@ created by `shadow-define-regexp-group'.")
186 (car list)) 184 (car list))
187 185
188(defun shadow-regexp-superquote (string) 186(defun shadow-regexp-superquote (string)
189 "Like `regexp-quote', but includes the ^ and $. 187 "Like `regexp-quote', but includes the \\` and \\'.
190This makes sure regexp matches nothing but STRING." 188This makes sure regexp matches nothing but STRING."
191 (concat "^" (regexp-quote string) "$")) 189 (concat "\\`" (regexp-quote string) "\\'"))
192 190
193(defun shadow-suffix (prefix string) 191(defun shadow-suffix (prefix string)
194 "If PREFIX begins STRING, return the rest. 192 "If PREFIX begins with STRING, return the rest.
195Return value is non-nil if PREFIX and STRING are `string=' up to the length of 193Return value is non-nil if PREFIX and STRING are `string=' up to the length of
196PREFIX." 194PREFIX."
197 (let ((lp (length prefix)) 195 (let ((lp (length prefix))
@@ -204,70 +202,66 @@ PREFIX."
204;;; Clusters and sites 202;;; Clusters and sites
205;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 203;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
206 204
207;;; I use the term `site' to refer to a string which may be the name of a 205;;; I use the term `site' to refer to a string which may be the
208;;; cluster or a literal hostname. All user-level commands should accept 206;;; cluster identification "/name:", a remote identification
209;;; either. 207;;; "/method:user@host:", or "/system-name:' (the value of
210 208;;; `shadow-system-name') for the location of local files. All
211(defun shadow-make-cluster (name primary regexp) 209;;; user-level commands should accept either.
212 "Create a shadow cluster.
213It is called NAME, uses the PRIMARY hostname and REGEXP matching all
214hosts in the cluster. The variable `shadow-clusters' associates the
215names of clusters to these structures. This function is for program
216use: to create clusters interactively, use `shadow-define-cluster'
217instead."
218 (list name primary regexp))
219
220(defmacro shadow-cluster-name (cluster)
221 "Return the name of the CLUSTER."
222 (list 'elt cluster 0))
223 210
224(defmacro shadow-cluster-primary (cluster) 211(cl-defstruct (shadow-cluster (:type list) :named) name primary regexp)
225 "Return the primary hostname of a CLUSTER."
226 (list 'elt cluster 1))
227
228(defmacro shadow-cluster-regexp (cluster)
229 "Return the regexp matching hosts in a CLUSTER."
230 (list 'elt cluster 2))
231 212
232(defun shadow-set-cluster (name primary regexp) 213(defun shadow-set-cluster (name primary regexp)
233 "Put cluster NAME on the list of clusters. 214 "Put cluster NAME on the list of clusters.
234Replace old definition, if any. PRIMARY and REGEXP are the 215Replace old definition, if any. PRIMARY and REGEXP are the
235information defining the cluster. For interactive use, call 216information defining the cluster. For interactive use, call
236`shadow-define-cluster' instead." 217`shadow-define-cluster' instead."
237 (let ((rest (cl-remove-if (lambda (x) (equal name (car x))) 218 (let ((rest (cl-remove-if (lambda (x) (equal name (shadow-cluster-name x)))
238 shadow-clusters))) 219 shadow-clusters)))
239 (setq shadow-clusters 220 (setq shadow-clusters
240 (cons (shadow-make-cluster name primary regexp) 221 (cons (make-shadow-cluster :name name :primary primary :regexp regexp)
241 rest)))) 222 rest))))
242 223
243(defmacro shadow-get-cluster (name) 224(defun shadow-get-cluster (name)
244 "Return cluster named NAME, or nil." 225 "Return cluster named NAME, or nil."
245 (list 'assoc name 'shadow-clusters)) 226 (shadow-find
227 (lambda (x) (string-equal (shadow-cluster-name x) name))
228 shadow-clusters))
229
230;;; SITES
231
232(defun shadow-site-name (site)
233 "Return name if SITE has the form \"/name:\", otherwise SITE."
234 (if (string-match "\\`/\\(\\w+\\):\\'" site)
235 (match-string 1 site) site))
236
237(defun shadow-name-site (name)
238 "Return \"/name:\" if NAME has word syntax, otherwise NAME."
239 (if (string-match "\\`\\w+\\'" name)
240 (format "/%s:"name) name))
246 241
247(defun shadow-site-primary (site) 242(defun shadow-site-primary (site)
248 "If SITE is a cluster, return primary host, otherwise return SITE." 243 "If SITE is a cluster, return primary identification, otherwise return SITE."
249 (let ((c (shadow-get-cluster site))) 244 (let ((cluster (shadow-get-cluster (shadow-site-name site))))
250 (if c 245 (if cluster
251 (shadow-cluster-primary c) 246 (shadow-cluster-primary cluster)
252 site))) 247 site)))
253 248
254;;; SITES
255
256(defun shadow-site-cluster (site) 249(defun shadow-site-cluster (site)
257 "Given a SITE (hostname or cluster name), return cluster it is in, or nil." 250 "Given a SITE, return cluster it is in, or nil."
258 (or (assoc site shadow-clusters) 251 (or (shadow-get-cluster (shadow-site-name site))
259 (shadow-find 252 (shadow-find
260 (function (lambda (x) 253 (lambda (x)
261 (string-match (shadow-cluster-regexp x) 254 (string-match (shadow-cluster-regexp x) (shadow-name-site site)))
262 site)))
263 shadow-clusters))) 255 shadow-clusters)))
264 256
265(defun shadow-read-site () 257(defun shadow-read-site ()
266 "Read a cluster name or hostname from the minibuffer." 258 "Read a cluster name or host identification from the minibuffer."
267 (let ((ans (completing-read "Host or cluster name [RET when done]: " 259 (let ((ans (completing-read "Host identification or cluster name: "
268 shadow-clusters))) 260 shadow-clusters)))
269 (if (equal "" ans) 261 (when (or (shadow-get-cluster (shadow-site-name ans))
270 nil 262 (string-equal ans shadow-system-name)
263 (string-equal ans (shadow-site-name shadow-system-name))
264 (setq ans (file-remote-p ans)))
271 ans))) 265 ans)))
272 266
273(defun shadow-site-match (site1 site2) 267(defun shadow-site-match (site1 site2)
@@ -281,63 +275,95 @@ be matched against the primary of SITE2."
281 (string-match (shadow-cluster-regexp cluster1) primary2) 275 (string-match (shadow-cluster-regexp cluster1) primary2)
282 (string-equal site1 primary2))))) 276 (string-equal site1 primary2)))))
283 277
284(defun shadow-get-user (site)
285 "Return the default username for a SITE."
286 (ange-ftp-get-user (shadow-site-primary site)))
287
288;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 278;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
289;;; Filename manipulation 279;;; Filename manipulation
290;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 280;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
291 281
292(defun shadow-parse-fullname (fullname)
293 "Parse FULLNAME into (site user path) list.
294Leave it alone if it already is one. Return nil if the argument is
295not a full ange-ftp pathname."
296 (if (listp fullname)
297 fullname
298 (ange-ftp-ftp-name fullname)))
299
300(defun shadow-parse-name (name) 282(defun shadow-parse-name (name)
301 "Parse any NAME into (site user name) list. 283 "Parse any NAME into a `tramp-file-name' structure.
302Argument can be a simple name, full ange-ftp name, or already a hup list." 284Argument can be a simple name, remote file name, or already a
303 (or (shadow-parse-fullname name) 285`tramp-file-name' structure."
304 (list shadow-system-name 286 (cond
305 (user-login-name) 287 ((null name) nil)
306 name))) 288 ((tramp-file-name-p name) name)
307 289 ((file-remote-p name) (tramp-dissect-file-name name))
308(defsubst shadow-make-fullname (host user name) 290 ((shadow-local-file name)
309 "Make an ange-ftp style fullname out of HOST, USER (optional), and NAME. 291 (make-tramp-file-name
310This is probably not as general as it ought to be." 292 :host (shadow-site-name shadow-system-name)
311 (concat "/" 293 :localname (shadow-local-file name)))
312 (if user (concat user "@")) 294 ;; Cluster name.
313 host ":" 295 ((string-match "^/\\([^:/]+\\):\\([^:]*\\)$" name)
314 name)) 296 (let ((name (match-string 1 name))
297 (file (match-string 2 name)))
298 (when (shadow-get-cluster name)
299 (make-tramp-file-name :host name :localname file))))))
300
301(defsubst shadow-make-fullname (hup &optional host name)
302 "Make a Tramp style fullname out of HUP, a `tramp-file-name' structure.
303Replace HOST, and NAME when non-nil."
304 (let ((hup (copy-tramp-file-name hup)))
305 (when host (setf (tramp-file-name-host hup) host))
306 (when name (setf (tramp-file-name-localname hup) name))
307 (if (null (tramp-file-name-method hup))
308 (format
309 "/%s:%s" (tramp-file-name-host hup) (tramp-file-name-localname hup))
310 (tramp-make-tramp-file-name
311 (tramp-file-name-method hup)
312 (tramp-file-name-user hup)
313 (tramp-file-name-domain hup)
314 (tramp-file-name-host hup)
315 (tramp-file-name-port hup)
316 (tramp-file-name-localname hup)
317 (tramp-file-name-hop hup)))))
315 318
316(defun shadow-replace-name-component (fullname newname) 319(defun shadow-replace-name-component (fullname newname)
317 "Return FULLNAME with the name component changed to NEWNAME." 320 "Return FULLNAME with the name component changed to NEWNAME."
318 (let ((hup (shadow-parse-fullname fullname))) 321 (concat (file-remote-p fullname) newname))
319 (shadow-make-fullname (nth 0 hup) (nth 1 hup) newname)))
320 322
321(defun shadow-local-file (file) 323(defun shadow-local-file (file)
322 "If FILE is at this site, remove /user@host part. 324 "If FILE is not remote, return it.
323If refers to a different system or a different user on this system, 325If it refers to a different system, return nil."
324return nil." 326 (cond
325 (let ((hup (shadow-parse-fullname file))) 327 ((null file) nil)
326 (cond ((null hup) file) 328 ;; `tramp-file-name' structure.
327 ((and (shadow-site-match (nth 0 hup) shadow-system-name) 329 ((and (tramp-file-name-p file) (null (tramp-file-name-method file)))
328 (string-equal (nth 1 hup) (user-login-name))) 330 (tramp-file-name-localname file))
329 (nth 2 hup)) 331 ((tramp-file-name-p file) nil)
330 (t nil)))) 332 ;; Local host name.
333 ((string-match
334 (format "^%s\\([^:]*\\)$" (regexp-quote shadow-system-name)) file)
335 (match-string 1 file))
336 ;; Cluster name.
337 ((and (string-match "^/\\([^:/]+\\):\\([^:]*\\)$" file)
338 (shadow-get-cluster (match-string 1 file)))
339 (let ((file (match-string 2 file))
340 (primary
341 (shadow-cluster-primary
342 (shadow-get-cluster (match-string 1 file)))))
343 (when (string-equal primary shadow-system-name) (setq primary nil))
344 (shadow-local-file (concat primary file))))
345 ;; Local name.
346 ((null (file-remote-p file)) file)))
331 347
332(defun shadow-expand-cluster-in-file-name (file) 348(defun shadow-expand-cluster-in-file-name (file)
333 "If hostname part of FILE is a cluster, expand it to cluster's primary hostname. 349 "If hostname part of FILE is a cluster, expand it to cluster's primary hostname.
334Will return the name bare if it is a local file." 350Will return the name bare if it is a local file."
335 (let ((hup (shadow-parse-name file))) 351 (when (stringp file)
336 (cond ((null hup) file) 352 (cond
337 ((shadow-local-file hup)) 353 ;; Local file.
338 ((shadow-make-fullname (shadow-site-primary (nth 0 hup)) 354 ((shadow-local-file file))
339 (nth 1 hup) 355 ;; Cluster name.
340 (nth 2 hup)))))) 356 ((string-match "^\\(/[^:/]+:\\)[^:]*$" file)
357 (let ((primary
358 (save-match-data
359 (shadow-cluster-primary
360 (shadow-get-cluster
361 (shadow-site-name (match-string 1 file)))))))
362 (if (not primary)
363 file
364 (setq file (replace-match primary nil nil file 1))
365 (or (shadow-local-file file) file))))
366 (t file))))
341 367
342(defun shadow-expand-file-name (file &optional default) 368(defun shadow-expand-file-name (file &optional default)
343 "Expand file name and get FILE's true name." 369 "Expand file name and get FILE's true name."
@@ -352,46 +378,50 @@ true."
352 (homedir (if (shadow-local-file hup) 378 (homedir (if (shadow-local-file hup)
353 shadow-homedir 379 shadow-homedir
354 (file-name-as-directory 380 (file-name-as-directory
355 (nth 2 (shadow-parse-fullname 381 (file-local-name
356 (expand-file-name 382 (expand-file-name (shadow-make-fullname hup nil "~"))))))
357 (shadow-make-fullname 383 (suffix (shadow-suffix homedir (tramp-file-name-localname hup)))
358 (nth 0 hup) (nth 1 hup) "~"))))))) 384 (cluster (shadow-site-cluster (shadow-make-fullname hup nil ""))))
359 (suffix (shadow-suffix homedir (nth 2 hup))) 385 (when cluster
360 (cluster (shadow-site-cluster (nth 0 hup)))) 386 (setf (tramp-file-name-method hup) nil
387 (tramp-file-name-host hup) (shadow-cluster-name cluster)))
361 (shadow-make-fullname 388 (shadow-make-fullname
362 (if cluster 389 hup nil
363 (shadow-cluster-name cluster)
364 (nth 0 hup))
365 (nth 1 hup)
366 (if suffix 390 (if suffix
367 (concat "~/" suffix) 391 (concat "~/" suffix)
368 (nth 2 hup))))) 392 (tramp-file-name-localname hup)))))
369 393
370(defun shadow-same-site (pattern file) 394(defun shadow-same-site (pattern file)
371 "True if the site of PATTERN and of FILE are on the same site. 395 "True if the site of PATTERN and of FILE are on the same site.
372If usernames are supplied, they must also match exactly. PATTERN and FILE may 396PATTERN and FILE may be Tramp vectors, or remote file names.
373be lists of host, user, name, or ange-ftp file names. FILE may also be just a 397FILE may also be just a local filename."
374local filename." 398 (let ((pattern-sup (shadow-parse-name pattern))
375 (let ((pattern-sup (shadow-parse-fullname pattern))
376 (file-sup (shadow-parse-name file))) 399 (file-sup (shadow-parse-name file)))
377 (and 400 (and
378 (shadow-site-match (nth 0 pattern-sup) (nth 0 file-sup)) 401 (shadow-site-match
379 (or (null (nth 1 pattern-sup)) 402 (tramp-file-name-host pattern-sup) (tramp-file-name-host file-sup))
380 (string-equal (nth 1 pattern-sup) (nth 1 file-sup)))))) 403 (or (null (tramp-file-name-user pattern-sup))
404 (string-equal
405 (tramp-file-name-user pattern-sup)
406 (tramp-file-name-user file-sup))))))
381 407
382(defun shadow-file-match (pattern file &optional regexp) 408(defun shadow-file-match (pattern file &optional regexp)
383 "Return t if PATTERN matches FILE. 409 "Return t if PATTERN matches FILE.
384If REGEXP is supplied and non-nil, the file part of the pattern is a regular 410If REGEXP is supplied and non-nil, the file part of the pattern is a regular
385expression, otherwise it must match exactly. The sites and usernames must 411expression, otherwise it must match exactly. The sites must
386match---see `shadow-same-site'. The pattern must be in full ange-ftp format, 412match---see `shadow-same-site'. The pattern must be in full Tramp format,
387but the file can be any valid filename. This function does not do any 413but the file can be any valid filename. This function does not do any
388filename expansion or contraction, you must do that yourself first." 414filename expansion or contraction, you must do that yourself first."
389 (let* ((pattern-sup (shadow-parse-fullname pattern)) 415 (let* ((pattern-sup (shadow-parse-name pattern))
390 (file-sup (shadow-parse-name file))) 416 (file-sup (shadow-parse-name file)))
391 (and (shadow-same-site pattern-sup file-sup) 417 (and (shadow-same-site pattern-sup file-sup)
392 (if regexp 418 (if regexp
393 (string-match (nth 2 pattern-sup) (nth 2 file-sup)) 419 (string-match
394 (string-equal (nth 2 pattern-sup) (nth 2 file-sup)))))) 420 (tramp-file-name-localname pattern-sup)
421 (tramp-file-name-localname file-sup))
422 (string-equal
423 (tramp-file-name-localname pattern-sup)
424 (tramp-file-name-localname file-sup))))))
395 425
396;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 426;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
397;;; User-level Commands 427;;; User-level Commands
@@ -405,30 +435,34 @@ one of them is sufficient to update the file on all of them. Clusters are
405defined by a name, the network address of a primary host (the one we copy 435defined by a name, the network address of a primary host (the one we copy
406files to), and a regular expression that matches the hostnames of all the 436files to), and a regular expression that matches the hostnames of all the
407sites in the cluster." 437sites in the cluster."
408 (interactive (list (completing-read "Cluster name: " shadow-clusters () ()))) 438 (interactive (list (completing-read "Cluster name: " shadow-clusters)))
409 (let* ((old (shadow-get-cluster name)) 439 (let* ((old (shadow-get-cluster name))
410 (primary (read-string "Primary host: " 440 (primary (let (try-primary)
411 (if old (shadow-cluster-primary old) 441 (while (not
412 name))) 442 (or
413 (regexp (let (try-regexp) 443 (string-equal
414 (while (not 444 (setq try-primary
415 (string-match
416 (setq try-regexp
417 (read-string 445 (read-string
418 "Regexp matching all host names: " 446 "Primary host: "
419 (if old (shadow-cluster-regexp old) 447 (if old (shadow-cluster-primary old)
420 (shadow-regexp-superquote primary)))) 448 name)))
421 primary)) 449 shadow-system-name)
422 (message "Regexp doesn't include the primary host!") 450 (file-remote-p try-primary)))
423 (sit-for 2)) 451 (message "Not a valid primary!")
424 try-regexp)) 452 (sit-for 2))
425; (username (read-no-blanks-input 453 try-primary))
426; (format "Username (default %s): " 454 (regexp (let (try-regexp)
427; (shadow-get-user primary)) 455 (while (not
428; (if old (or (shadow-cluster-username old) "") 456 (string-match
429; (user-login-name)))) 457 (setq try-regexp
430 ) 458 (read-string
431; (if (string-equal "" username) (setq username nil)) 459 "Regexp matching all host names: "
460 (if old (shadow-cluster-regexp old)
461 (shadow-regexp-superquote primary))))
462 primary))
463 (message "Regexp doesn't include the primary host!")
464 (sit-for 2))
465 try-regexp)))
432 (shadow-set-cluster name primary regexp))) 466 (shadow-set-cluster name primary regexp)))
433 467
434;;;###autoload 468;;;###autoload
@@ -438,20 +472,14 @@ It may have different filenames on each site. When this file is edited, the
438new version will be copied to each of the other locations. Sites can be 472new version will be copied to each of the other locations. Sites can be
439specific hostnames, or names of clusters (see `shadow-define-cluster')." 473specific hostnames, or names of clusters (see `shadow-define-cluster')."
440 (interactive) 474 (interactive)
441 (let* ((hup (shadow-parse-fullname 475 (let* ((hup (shadow-parse-name
442 (shadow-contract-file-name (buffer-file-name)))) 476 (shadow-contract-file-name (buffer-file-name))))
443 (name (nth 2 hup)) 477 (name (tramp-file-name-localname hup))
444 user site group) 478 site group)
445 (while (setq site (shadow-read-site)) 479 (while (setq site (shadow-read-site))
446 (setq user (read-string (format "Username (default %s): " 480 (setq name (read-string "Filename: " name)
447 (shadow-get-user site))) 481 hup (shadow-parse-name (shadow-contract-file-name name))
448 name (read-string "Filename: " name)) 482 group (cons (shadow-make-fullname hup site) group)))
449 (setq group (cons (shadow-make-fullname site
450 (if (string-equal "" user)
451 (shadow-get-user site)
452 user)
453 name)
454 group)))
455 (setq shadow-literal-groups (cons group shadow-literal-groups))) 483 (setq shadow-literal-groups (cons group shadow-literal-groups)))
456 (shadow-write-info-file)) 484 (shadow-write-info-file))
457 485
@@ -468,19 +496,12 @@ function). Each site can be either a hostname or the name of a cluster (see
468 "Filename regexp: " 496 "Filename regexp: "
469 (if (buffer-file-name) 497 (if (buffer-file-name)
470 (shadow-regexp-superquote 498 (shadow-regexp-superquote
471 (nth 2 499 (file-local-name (buffer-file-name))))))
472 (shadow-parse-name 500 site sites)
473 (shadow-contract-file-name
474 (buffer-file-name))))))))
475 site sites usernames)
476 (while (setq site (shadow-read-site)) 501 (while (setq site (shadow-read-site))
477 (setq sites (cons site sites)) 502 (setq sites (cons site sites)))
478 (setq usernames
479 (cons (read-string (format "Username for %s: " site)
480 (shadow-get-user site))
481 usernames)))
482 (setq shadow-regexp-groups 503 (setq shadow-regexp-groups
483 (cons (shadow-make-group regexp sites usernames) 504 (cons (shadow-make-group regexp sites)
484 shadow-regexp-groups)) 505 shadow-regexp-groups))
485 (shadow-write-info-file))) 506 (shadow-write-info-file)))
486 507
@@ -537,14 +558,14 @@ permanently, remove the group from `shadow-literal-groups' or
537;;; Internal functions 558;;; Internal functions
538;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 559;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
539 560
540(defun shadow-make-group (regexp sites usernames) 561(defun shadow-make-group (regexp sites)
541 "Make a description of a file group--- 562 "Make a description of a file group---
542actually a list of regexp ange-ftp file names---from REGEXP (name of file to 563actually a list of regexp Tramp file names---from REGEXP (name of file to
543be shadowed), list of SITES, and corresponding list of USERNAMES for each 564be shadowed), and list of SITES"
544site."
545 (if sites 565 (if sites
546 (cons (shadow-make-fullname (car sites) (car usernames) regexp) 566 (cons (shadow-make-fullname
547 (shadow-make-group regexp (cdr sites) (cdr usernames))) 567 (shadow-parse-name (shadow-site-primary (car sites))) nil regexp)
568 (shadow-make-group regexp (cdr sites)))
548 nil)) 569 nil))
549 570
550(defun shadow-copy-file (s) 571(defun shadow-copy-file (s)
@@ -601,7 +622,9 @@ Consider them as regular expressions if third arg REGEXP is true."
601 (car groups)))) 622 (car groups))))
602 (append (cond ((equal nonmatching (car groups)) nil) 623 (append (cond ((equal nonmatching (car groups)) nil)
603 (regexp 624 (regexp
604 (let ((realname (nth 2 (shadow-parse-fullname file)))) 625 (let ((realname
626 (tramp-file-name-localname
627 (shadow-parse-name file))))
605 (mapcar 628 (mapcar
606 (function 629 (function
607 (lambda (x) 630 (lambda (x)
@@ -636,9 +659,8 @@ PAIR must be `eq' to one of the elements of that list."
636Thus restores shadowfile's state from your last Emacs session. 659Thus restores shadowfile's state from your last Emacs session.
637Return t unless files were locked; then return nil." 660Return t unless files were locked; then return nil."
638 (interactive) 661 (interactive)
639 (if (and (fboundp 'file-locked-p) 662 (if (or (stringp (file-locked-p shadow-info-file))
640 (or (stringp (file-locked-p shadow-info-file)) 663 (stringp (file-locked-p shadow-todo-file)))
641 (stringp (file-locked-p shadow-todo-file))))
642 (progn 664 (progn
643 (message "Shadowfile is running in another Emacs; can't have two.") 665 (message "Shadowfile is running in another Emacs; can't have two.")
644 (beep) 666 (beep)
@@ -647,7 +669,7 @@ Return t unless files were locked; then return nil."
647 (save-current-buffer 669 (save-current-buffer
648 (when shadow-info-file 670 (when shadow-info-file
649 (set-buffer (setq shadow-info-buffer 671 (set-buffer (setq shadow-info-buffer
650 (find-file-noselect shadow-info-file))) 672 (find-file-noselect shadow-info-file 'nowarn)))
651 (when (and (not (buffer-modified-p)) 673 (when (and (not (buffer-modified-p))
652 (file-newer-than-file-p (make-auto-save-file-name) 674 (file-newer-than-file-p (make-auto-save-file-name)
653 shadow-info-file)) 675 shadow-info-file))
@@ -680,6 +702,7 @@ defined, the old hashtable info is invalid."
680 (if (not shadow-info-buffer) 702 (if (not shadow-info-buffer)
681 (setq shadow-info-buffer (find-file-noselect shadow-info-file))) 703 (setq shadow-info-buffer (find-file-noselect shadow-info-file)))
682 (set-buffer shadow-info-buffer) 704 (set-buffer shadow-info-buffer)
705 (setq buffer-read-only nil)
683 (delete-region (point-min) (point-max)) 706 (delete-region (point-min) (point-max))
684 (shadow-insert-var 'shadow-clusters) 707 (shadow-insert-var 'shadow-clusters)
685 (shadow-insert-var 'shadow-literal-groups) 708 (shadow-insert-var 'shadow-literal-groups)
@@ -692,6 +715,7 @@ With non-nil argument also saves the buffer."
692 (if (not shadow-todo-buffer) 715 (if (not shadow-todo-buffer)
693 (setq shadow-todo-buffer (find-file-noselect shadow-todo-file))) 716 (setq shadow-todo-buffer (find-file-noselect shadow-todo-file)))
694 (set-buffer shadow-todo-buffer) 717 (set-buffer shadow-todo-buffer)
718 (setq buffer-read-only nil)
695 (delete-region (point-min) (point-max)) 719 (delete-region (point-min) (point-max))
696 (shadow-insert-var 'shadow-files-to-copy) 720 (shadow-insert-var 'shadow-files-to-copy)
697 (if save (shadow-save-todo-file)))) 721 (if save (shadow-save-todo-file))))
@@ -765,24 +789,6 @@ look for files that have been changed and need to be copied to other systems."
765 (kill-emacs))) 789 (kill-emacs)))
766 790
767;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 791;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
768;;; Lucid Emacs compatibility
769;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
770
771;; This is on hold until someone tells me about a working version of
772;; map-ynp for Lucid Emacs.
773
774;(when (string-match "Lucid" emacs-version)
775; (require 'symlink-fix)
776; (require 'ange-ftp)
777; (require 'map-ynp)
778; (if (not (fboundp 'file-truename))
779; (fset 'shadow-expand-file-name
780; (symbol-function 'symlink-expand-file-name)))
781; (if (not (fboundp 'ange-ftp-ftp-name))
782; (fset 'ange-ftp-ftp-name
783; (symbol-function 'ange-ftp-ftp-name))))
784
785;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
786;;; Hook us up 792;;; Hook us up
787;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 793;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
788 794
@@ -790,18 +796,10 @@ look for files that have been changed and need to be copied to other systems."
790(defun shadow-initialize () 796(defun shadow-initialize ()
791 "Set up file shadowing." 797 "Set up file shadowing."
792 (interactive) 798 (interactive)
793 (if (null shadow-homedir) 799 (setq shadow-homedir
794 (setq shadow-homedir 800 (file-name-as-directory (shadow-expand-file-name shadow-homedir))
795 (file-name-as-directory (shadow-expand-file-name "~")))) 801 shadow-info-file (shadow-expand-file-name shadow-info-file)
796 (if (null shadow-info-file) 802 shadow-todo-file (shadow-expand-file-name shadow-todo-file))
797 (setq shadow-info-file
798 ;; FIXME: Move defaults to their defcustom.
799 (shadow-expand-file-name
800 (locate-user-emacs-file "shadows" ".shadows"))))
801 (if (null shadow-todo-file)
802 (setq shadow-todo-file
803 (shadow-expand-file-name
804 (locate-user-emacs-file "shadow_todo" ".shadow_todo"))))
805 (if (not (shadow-read-files)) 803 (if (not (shadow-read-files))
806 (progn 804 (progn
807 (message "Shadowfile information files not found - aborting") 805 (message "Shadowfile information files not found - aborting")
diff --git a/test/lisp/shadowfile-tests.el b/test/lisp/shadowfile-tests.el
new file mode 100644
index 00000000000..5ded94480ec
--- /dev/null
+++ b/test/lisp/shadowfile-tests.el
@@ -0,0 +1,876 @@
1;;; shadowfile-tests.el --- Tests of shadowfile
2
3;; Copyright (C) 2018 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;; A whole test run can be performed calling the command `shadowfile-test-all'.
23
24;;; Code:
25
26(require 'ert)
27(require 'shadowfile)
28(require 'tramp)
29
30;; There is no default value on w32 systems, which could work out of the box.
31(defconst shadow-test-remote-temporary-file-directory
32 (cond
33 ((getenv "REMOTE_TEMPORARY_FILE_DIRECTORY"))
34 ((eq system-type 'windows-nt) null-device)
35 (t (add-to-list
36 'tramp-methods
37 '("mock"
38 (tramp-login-program "sh")
39 (tramp-login-args (("-i")))
40 (tramp-remote-shell "/bin/sh")
41 (tramp-remote-shell-args ("-c"))
42 (tramp-connection-timeout 10)))
43 (add-to-list
44 'tramp-default-host-alist
45 `("\\`mock\\'" nil ,(system-name)))
46 ;; Emacs' Makefile sets $HOME to a nonexistent value. Needed in
47 ;; batch mode only, therefore. It cannot be
48 ;; `temporary-directory', because the tests with "~" would fail.
49 (unless (and (null noninteractive) (file-directory-p "~/"))
50 (setenv "HOME" invocation-directory))
51 (format "/mock::%s" temporary-file-directory)))
52 "Temporary directory for Tramp tests.")
53
54(defconst shadow-test-info-file
55 (expand-file-name "shadows_test" temporary-file-directory)
56 "File to keep shadow information in during tests.")
57
58(defconst shadow-test-todo-file
59 (expand-file-name "shadow_todo_test" temporary-file-directory)
60 "File to store the list of uncopied shadows in during tests.")
61
62(ert-deftest shadow-test00-clusters ()
63 "Check cluster definitions.
64Per definition, all files are identical on the different hosts of
65a cluster (or site). This is not tested here; it must be
66guaranteed by the originator of a cluster definition."
67 (skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
68
69 (let ((text-quoting-style 'grave) ;; We inspect the *Messages* buffer!
70 (inhibit-message t)
71 (shadow-info-file shadow-test-info-file)
72 (shadow-todo-file shadow-test-todo-file)
73 shadow-clusters
74 cluster primary regexp mocked-input)
75 (unwind-protect
76 ;; We must mock `read-from-minibuffer' and `read-string', in
77 ;; order to avoid interactive arguments.
78 (cl-letf* (((symbol-function 'read-from-minibuffer)
79 (lambda (&rest args) (pop mocked-input)))
80 ((symbol-function 'read-string)
81 (lambda (&rest args) (pop mocked-input))))
82
83 ;; Cleanup.
84 (when (file-exists-p shadow-info-file)
85 (delete-file shadow-info-file))
86 (when (file-exists-p shadow-todo-file)
87 (delete-file shadow-todo-file))
88
89 ;; Define a cluster.
90 (setq cluster "cluster"
91 primary shadow-system-name
92 regexp (shadow-regexp-superquote primary)
93 mocked-input `(,cluster ,primary ,regexp))
94 (call-interactively 'shadow-define-cluster)
95 (should
96 (string-equal
97 (shadow-cluster-name (shadow-get-cluster cluster)) cluster))
98 (should
99 (string-equal
100 (shadow-cluster-primary (shadow-get-cluster cluster)) primary))
101 (should
102 (string-equal
103 (shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
104 (should-not (shadow-get-cluster "non-existent-cluster-name"))
105
106 ;; Test `shadow-set-cluster' and `make-shadow-cluster'.
107 (shadow-set-cluster cluster primary regexp)
108 (should
109 (equal (shadow-get-cluster cluster)
110 (make-shadow-cluster
111 :name cluster :primary primary :regexp regexp)))
112
113 ;; The primary must be either `shadow-system-name', or a remote file.
114 (setq ;; The second "cluster" is wrong.
115 mocked-input `(,cluster ,cluster ,primary ,regexp))
116 (with-current-buffer (messages-buffer)
117 (narrow-to-region (point-max) (point-max)))
118 (call-interactively 'shadow-define-cluster)
119 (should
120 (string-match
121 (regexp-quote "Not a valid primary!")
122 (with-current-buffer (messages-buffer) (buffer-string))))
123 ;; The first cluster definition is still valid.
124 (should
125 (string-equal
126 (shadow-cluster-name (shadow-get-cluster cluster)) cluster))
127 (should
128 (string-equal
129 (shadow-cluster-primary (shadow-get-cluster cluster)) primary))
130 (should
131 (string-equal
132 (shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
133
134 ;; The regexp must match the primary name.
135 (setq ;; The second "cluster" is wrong.
136 mocked-input `(,cluster ,primary ,cluster ,regexp))
137 (with-current-buffer (messages-buffer)
138 (narrow-to-region (point-max) (point-max)))
139 (call-interactively 'shadow-define-cluster)
140 (should
141 (string-match
142 (regexp-quote "Regexp doesn't include the primary host!")
143 (with-current-buffer (messages-buffer) (buffer-string))))
144 ;; The first cluster definition is still valid.
145 (should
146 (string-equal
147 (shadow-cluster-name (shadow-get-cluster cluster)) cluster))
148 (should
149 (string-equal
150 (shadow-cluster-primary (shadow-get-cluster cluster)) primary))
151 (should
152 (string-equal
153 (shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
154
155 ;; Redefine the cluster.
156 (setq primary
157 (file-remote-p shadow-test-remote-temporary-file-directory)
158 regexp (shadow-regexp-superquote primary)
159 mocked-input `(,cluster ,primary ,regexp))
160 (call-interactively 'shadow-define-cluster)
161 (should
162 (string-equal
163 (shadow-cluster-name (shadow-get-cluster cluster)) cluster))
164 (should
165 (string-equal
166 (shadow-cluster-primary (shadow-get-cluster cluster)) primary))
167 (should
168 (string-equal
169 (shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
170
171 ;; Test `shadow-set-cluster' and `make-shadow-cluster'.
172 (shadow-set-cluster cluster primary regexp)
173 (should
174 (equal (shadow-get-cluster cluster)
175 (make-shadow-cluster
176 :name cluster :primary primary :regexp regexp))))
177
178 ;; Cleanup.
179 (with-current-buffer (messages-buffer) (widen))
180 (when (file-exists-p shadow-info-file)
181 (delete-file shadow-info-file))
182 (when (file-exists-p shadow-todo-file)
183 (delete-file shadow-todo-file)))))
184
185(ert-deftest shadow-test01-sites ()
186 "Check site definitions.
187Per definition, all files are identical on the different hosts of
188a cluster (or site). This is not tested here; it must be
189guaranteed by the originator of a cluster definition."
190 (skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
191
192 (let ((shadow-info-file shadow-test-info-file)
193 (shadow-todo-file shadow-test-todo-file)
194 shadow-clusters
195 cluster1 cluster2 primary1 primary2 regexp1 regexp2 mocked-input)
196 (unwind-protect
197 ;; We must mock `read-from-minibuffer' and `read-string', in
198 ;; order to avoid interactive arguments.
199 (cl-letf* (((symbol-function 'read-from-minibuffer)
200 (lambda (&rest args) (pop mocked-input)))
201 ((symbol-function 'read-string)
202 (lambda (&rest args) (pop mocked-input))))
203
204 ;; Cleanup.
205 (when (file-exists-p shadow-info-file)
206 (delete-file shadow-info-file))
207 (when (file-exists-p shadow-todo-file)
208 (delete-file shadow-todo-file))
209
210 ;; Define a cluster.
211 (setq cluster1 "cluster1"
212 primary1 shadow-system-name
213 regexp1 (shadow-regexp-superquote primary1))
214 (shadow-set-cluster cluster1 primary1 regexp1)
215
216 ;; A site is either a cluster identification, or a primary host.
217 (should (string-equal cluster1 (shadow-site-name cluster1)))
218 (should (string-equal primary1 (shadow-name-site primary1)))
219 (should
220 (string-equal (format "/%s:" cluster1) (shadow-name-site cluster1)))
221 (should (string-equal (system-name) (shadow-site-name primary1)))
222 (should
223 (string-equal
224 (file-remote-p shadow-test-remote-temporary-file-directory)
225 (shadow-name-site
226 (file-remote-p shadow-test-remote-temporary-file-directory))))
227 (should
228 (string-equal
229 (file-remote-p shadow-test-remote-temporary-file-directory)
230 (shadow-site-name
231 (file-remote-p shadow-test-remote-temporary-file-directory))))
232
233 (should (equal (shadow-site-cluster cluster1)
234 (shadow-get-cluster cluster1)))
235 (should (equal (shadow-site-cluster (shadow-name-site cluster1))
236 (shadow-get-cluster cluster1)))
237 (should (equal (shadow-site-cluster primary1)
238 (shadow-get-cluster cluster1)))
239 (should (equal (shadow-site-cluster (shadow-site-name primary1))
240 (shadow-get-cluster cluster1)))
241 (should (string-equal (shadow-site-primary cluster1) primary1))
242 (should (string-equal (shadow-site-primary primary1) primary1))
243
244 ;; `shadow-read-site' accepts "cluster", "/cluster:",
245 ;; "system", "/system:". It shall reject bad site names.
246 (setq mocked-input
247 `(,cluster1 ,(shadow-name-site cluster1)
248 ,primary1 ,(shadow-site-name primary1)
249 ,shadow-system-name "" "bad" "/bad:"))
250 (should (string-equal (shadow-read-site) cluster1))
251 (should (string-equal (shadow-read-site) (shadow-name-site cluster1)))
252 (should (string-equal (shadow-read-site) primary1))
253 (should (string-equal (shadow-read-site) (shadow-site-name primary1)))
254 (should (string-equal (shadow-read-site) shadow-system-name))
255 (should-not (shadow-read-site)) ; ""
256 (should-not (shadow-read-site)) ; "bad"
257 (should-not (shadow-read-site)) ; "/bad:"
258 (should-error (shadow-read-site)) ; no input at all
259
260 ;; Define a second cluster.
261 (setq cluster2 "cluster2"
262 primary2
263 (file-remote-p shadow-test-remote-temporary-file-directory)
264 regexp2 (format "^\\(%s\\|%s\\)$" shadow-system-name primary2))
265 (shadow-set-cluster cluster2 primary2 regexp2)
266
267 ;; `shadow-site-match' shall know all different kind of site names.
268 (should (shadow-site-match cluster1 cluster1))
269 (should (shadow-site-match primary1 primary1))
270 (should (shadow-site-match cluster1 primary1))
271 (should (shadow-site-match primary1 cluster1))
272 (should (shadow-site-match cluster2 cluster2))
273 (should (shadow-site-match primary2 primary2))
274 (should (shadow-site-match cluster2 primary2))
275 (should (shadow-site-match primary2 cluster2))
276
277 ;; The regexp of `cluster2' matches the primary of
278 ;; `cluster1'. Not vice versa.
279 (should (shadow-site-match cluster2 cluster1))
280 (should-not (shadow-site-match cluster1 cluster2))
281
282 ;; If we use the primaries of a cluster, it doesn't match.
283 (should-not
284 (shadow-site-match (shadow-site-primary cluster2) cluster1))
285 (should-not
286 (shadow-site-match (shadow-site-primary cluster1) cluster2)))
287
288 ;; Cleanup.
289 (when (file-exists-p shadow-info-file)
290 (delete-file shadow-info-file))
291 (when (file-exists-p shadow-todo-file)
292 (delete-file shadow-todo-file)))))
293
294(ert-deftest shadow-test02-files ()
295 "Check file manipulation functions."
296 (skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
297
298 (let ((shadow-info-file shadow-test-info-file)
299 (shadow-todo-file shadow-test-todo-file)
300 shadow-clusters
301 cluster primary regexp file hup)
302 (unwind-protect
303 (progn
304 ;; Cleanup.
305 (when (file-exists-p shadow-info-file)
306 (delete-file shadow-info-file))
307 (when (file-exists-p shadow-todo-file)
308 (delete-file shadow-todo-file))
309
310 ;; Define a cluster.
311 (setq cluster "cluster"
312 primary shadow-system-name
313 regexp (shadow-regexp-superquote primary)
314 file (make-temp-name
315 (expand-file-name
316 "shadowfile-tests" temporary-file-directory)))
317 (shadow-set-cluster cluster primary regexp)
318
319 ;; The constant structure to compare with.
320 (setq hup (make-tramp-file-name :host (system-name) :localname file))
321
322 ;; The structure a local file is transformed in.
323 (should (equal (shadow-parse-name file) hup))
324 (should (equal (shadow-parse-name (concat "/" cluster ":" file)) hup))
325 (should (equal (shadow-parse-name (concat primary file)) hup))
326
327 ;; A local file name is kept.
328 (should
329 (string-equal (shadow-local-file file) file))
330 ;; A file on this cluster is also local.
331 (should
332 (string-equal
333 (shadow-local-file (concat "/" cluster ":" file)) file))
334 ;; A file on the primary host is also local.
335 (should
336 (string-equal (shadow-local-file (concat primary file)) file))
337
338 ;; Redefine the cluster.
339 (setq primary
340 (file-remote-p shadow-test-remote-temporary-file-directory)
341 regexp (shadow-regexp-superquote primary))
342 (shadow-set-cluster cluster primary regexp)
343
344 ;; The structure of the local file is still the same.
345 (should (equal (shadow-parse-name file) hup))
346 ;; The cluster name must be used.
347 (setf (tramp-file-name-host hup) cluster)
348 (should (equal (shadow-parse-name (concat "/" cluster ":" file)) hup))
349 ;; The structure of a remote file is different.
350 (should
351 (equal (shadow-parse-name (concat primary file))
352 (tramp-dissect-file-name (concat primary file))))
353
354 ;; A local file is still local.
355 (should (shadow-local-file file))
356 ;; A file on this cluster is not local.
357 (should-not (shadow-local-file (concat "/" cluster ":" file)))
358 ;; A file on the primary host is not local.
359 (should-not (shadow-local-file (concat primary file)))
360 ;; There's no error on wrong FILE.
361 (should-not (shadow-local-file nil)))
362
363 ;; Cleanup.
364 (when (file-exists-p shadow-info-file)
365 (delete-file shadow-info-file))
366 (when (file-exists-p shadow-todo-file)
367 (delete-file shadow-todo-file)))))
368
369(ert-deftest shadow-test03-expand-cluster-in-file-name ()
370 "Check canonical file name of a cluster or site."
371 (skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
372
373 (let ((shadow-info-file shadow-test-info-file)
374 (shadow-todo-file shadow-test-todo-file)
375 shadow-clusters
376 cluster primary regexp file1 file2)
377 (unwind-protect
378 (progn
379 ;; Cleanup.
380 (when (file-exists-p shadow-info-file)
381 (delete-file shadow-info-file))
382 (when (file-exists-p shadow-todo-file)
383 (delete-file shadow-todo-file))
384
385 ;; Define a cluster.
386 (setq cluster "cluster"
387 primary shadow-system-name
388 regexp (shadow-regexp-superquote primary))
389 (shadow-set-cluster cluster primary regexp)
390
391 (setq file1
392 (make-temp-name
393 (expand-file-name "shadowfile-tests" temporary-file-directory))
394 file2
395 (make-temp-name
396 (expand-file-name
397 "shadowfile-tests"
398 shadow-test-remote-temporary-file-directory)))
399
400 ;; A local file name is kept.
401 (should
402 (string-equal (shadow-expand-cluster-in-file-name file1) file1))
403 ;; A remote file is kept.
404 (should
405 (string-equal (shadow-expand-cluster-in-file-name file2) file2))
406 ;; A cluster name is expanded to the primary name.
407 (should
408 (string-equal
409 (shadow-expand-cluster-in-file-name (format "/%s:%s" cluster file1))
410 (shadow-expand-cluster-in-file-name (concat primary file1))))
411 ;; A primary name is expanded if it is a local file name.
412 (should
413 (string-equal
414 (shadow-expand-cluster-in-file-name (concat primary file1)) file1))
415
416 ;; Redefine the cluster.
417 (setq primary
418 (file-remote-p shadow-test-remote-temporary-file-directory)
419 regexp (shadow-regexp-superquote primary))
420 (shadow-set-cluster cluster primary regexp)
421
422 ;; A cluster name is expanded to the primary name.
423 (should
424 (string-equal
425 (shadow-expand-cluster-in-file-name (format "/%s:%s" cluster file1))
426 (shadow-expand-cluster-in-file-name (concat primary file1))))
427 ;; A primary name is not expanded if it isn't is a local file name.
428 (should
429 (string-equal
430 (shadow-expand-cluster-in-file-name (concat primary file1))
431 (concat primary file1))))
432
433 ;; Cleanup.
434 (when (file-exists-p shadow-info-file)
435 (delete-file shadow-info-file))
436 (when (file-exists-p shadow-todo-file)
437 (delete-file shadow-todo-file)))))
438
439(ert-deftest shadow-test04-contract-file-name ()
440 "Check canonical file name of a cluster or site."
441 (skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
442
443 (let ((shadow-info-file shadow-test-info-file)
444 (shadow-todo-file shadow-test-todo-file)
445 shadow-clusters
446 cluster primary regexp file)
447 (unwind-protect
448 (progn
449 ;; Cleanup.
450 (when (file-exists-p shadow-info-file)
451 (delete-file shadow-info-file))
452 (when (file-exists-p shadow-todo-file)
453 (delete-file shadow-todo-file))
454
455 ;; Define a cluster.
456 (setq cluster "cluster"
457 primary shadow-system-name
458 regexp (shadow-regexp-superquote primary)
459 file (make-temp-name
460 (expand-file-name
461 "shadowfile-tests" temporary-file-directory)))
462 (shadow-set-cluster cluster primary regexp)
463
464 ;; The cluster name is prepended for local files.
465 (should
466 (string-equal
467 (shadow-contract-file-name file) (concat "/cluster:" file)))
468 ;; A cluster file name is preserved.
469 (should
470 (string-equal
471 (shadow-contract-file-name (concat "/cluster:" file))
472 (concat "/cluster:" file)))
473 ;; `shadow-system-name' is mapped to the cluster.
474 (should
475 (string-equal
476 (shadow-contract-file-name (concat shadow-system-name file))
477 (concat "/cluster:" file)))
478
479 ;; Redefine the cluster.
480 (setq primary
481 (file-remote-p shadow-test-remote-temporary-file-directory)
482 regexp (shadow-regexp-superquote primary))
483 (shadow-set-cluster cluster primary regexp)
484
485 ;; A remote file name is mapped to the cluster.
486 (should
487 (string-equal
488 (shadow-contract-file-name
489 (concat
490 (file-remote-p shadow-test-remote-temporary-file-directory) file))
491 (concat "/cluster:" file))))
492
493 ;; Cleanup.
494 (when (file-exists-p shadow-info-file)
495 (delete-file shadow-info-file))
496 (when (file-exists-p shadow-todo-file)
497 (delete-file shadow-todo-file)))))
498
499(ert-deftest shadow-test05-file-match ()
500 "Check `shadow-same-site' and `shadow-file-match'."
501 (skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
502
503 (let ((shadow-info-file shadow-test-info-file)
504 (shadow-todo-file shadow-test-todo-file)
505 shadow-clusters
506 cluster primary regexp file)
507 (unwind-protect
508 (progn
509 ;; Cleanup.
510 (when (file-exists-p shadow-info-file)
511 (delete-file shadow-info-file))
512 (when (file-exists-p shadow-todo-file)
513 (delete-file shadow-todo-file))
514
515 ;; Define a cluster.
516 (setq cluster "cluster"
517 primary shadow-system-name
518 regexp (shadow-regexp-superquote primary)
519 file (make-temp-name
520 (expand-file-name
521 "shadowfile-tests" temporary-file-directory)))
522 (shadow-set-cluster cluster primary regexp)
523
524 (should (shadow-same-site (shadow-parse-name "/cluster:") file))
525 (should
526 (shadow-same-site (shadow-parse-name shadow-system-name) file))
527 (should (shadow-same-site (shadow-parse-name file) file))
528
529 (should
530 (shadow-file-match
531 (shadow-parse-name (concat "/cluster:" file)) file))
532 (should
533 (shadow-file-match
534 (shadow-parse-name (concat shadow-system-name file)) file))
535 (should (shadow-file-match (shadow-parse-name file) file))
536
537 ;; Redefine the cluster.
538 (setq primary
539 (file-remote-p shadow-test-remote-temporary-file-directory)
540 regexp (shadow-regexp-superquote primary))
541 (shadow-set-cluster cluster primary regexp)
542
543 (should
544 (shadow-file-match
545 (shadow-parse-name
546 (concat
547 (file-remote-p shadow-test-remote-temporary-file-directory)
548 file))
549 file)))
550
551 ;; Cleanup.
552 (when (file-exists-p shadow-info-file)
553 (delete-file shadow-info-file))
554 (when (file-exists-p shadow-todo-file)
555 (delete-file shadow-todo-file)))))
556
557(ert-deftest shadow-test06-literal-groups ()
558 "Check literal group definitions."
559 (let ((shadow-info-file shadow-test-info-file)
560 (shadow-todo-file shadow-test-todo-file)
561 shadow-clusters shadow-literal-groups
562 cluster1 cluster2 primary regexp file1 file2 mocked-input)
563 (unwind-protect
564 ;; We must mock `read-from-minibuffer' and `read-string', in
565 ;; order to avoid interactive arguments.
566 (cl-letf* (((symbol-function 'read-from-minibuffer)
567 (lambda (&rest args) (pop mocked-input)))
568 ((symbol-function 'read-string)
569 (lambda (&rest args) (pop mocked-input))))
570
571 ;; Cleanup.
572 (when (file-exists-p shadow-info-file)
573 (delete-file shadow-info-file))
574 (when (file-exists-p shadow-todo-file)
575 (delete-file shadow-todo-file))
576
577 ;; Define clusters.
578 (setq cluster1 "cluster1"
579 primary shadow-system-name
580 regexp (shadow-regexp-superquote primary))
581 (shadow-set-cluster cluster1 primary regexp)
582
583 (setq cluster2 "cluster2"
584 primary
585 (file-remote-p shadow-test-remote-temporary-file-directory)
586 regexp (format "^\\(%s\\|%s\\)$" shadow-system-name primary))
587 (shadow-set-cluster cluster2 primary regexp)
588
589 ;; Define a literal group.
590 (setq file1
591 (make-temp-name
592 (expand-file-name "shadowfile-tests" temporary-file-directory))
593 file2
594 (make-temp-name
595 (expand-file-name
596 "shadowfile-tests"
597 shadow-test-remote-temporary-file-directory))
598 mocked-input `(,cluster1 ,file1 ,cluster2 ,file2 ,(kbd "RET")))
599 (with-temp-buffer
600 (setq-local buffer-file-name file1)
601 (call-interactively 'shadow-define-literal-group))
602
603 ;; `shadow-literal-groups' is a list of lists.
604 (should (consp shadow-literal-groups))
605 (should (consp (car shadow-literal-groups)))
606 (should-not (cdr shadow-literal-groups))
607
608 (should (member (format "/%s:%s" cluster1 (file-local-name file1))
609 (car shadow-literal-groups)))
610 (should (member (format "/%s:%s" cluster2 (file-local-name file2))
611 (car shadow-literal-groups))))
612
613 ;; Cleanup.
614 (when (file-exists-p shadow-info-file)
615 (delete-file shadow-info-file))
616 (when (file-exists-p shadow-todo-file)
617 (delete-file shadow-todo-file)))))
618
619(ert-deftest shadow-test07-regexp-groups ()
620 "Check regexp group definitions."
621 (let ((shadow-info-file shadow-test-info-file)
622 (shadow-todo-file shadow-test-todo-file)
623 shadow-clusters shadow-regexp-groups
624 cluster1 cluster2 primary regexp file mocked-input)
625 (unwind-protect
626 ;; We must mock `read-from-minibuffer' and `read-string', in
627 ;; order to avoid interactive arguments.
628 (cl-letf* (((symbol-function 'read-from-minibuffer)
629 (lambda (&rest args) (pop mocked-input)))
630 ((symbol-function 'read-string)
631 (lambda (&rest args) (pop mocked-input))))
632
633 ;; Cleanup.
634 (when (file-exists-p shadow-info-file)
635 (delete-file shadow-info-file))
636 (when (file-exists-p shadow-todo-file)
637 (delete-file shadow-todo-file))
638
639 ;; Define clusters.
640 (setq cluster1 "cluster1"
641 primary shadow-system-name
642 regexp (shadow-regexp-superquote primary))
643 (shadow-set-cluster cluster1 primary regexp)
644
645 (setq cluster2 "cluster2"
646 primary
647 (file-remote-p shadow-test-remote-temporary-file-directory)
648 regexp (format "^\\(%s\\|%s\\)$" shadow-system-name primary))
649 (shadow-set-cluster cluster2 primary regexp)
650
651 ;; Define a regexp group.
652 (setq file
653 (make-temp-name
654 (expand-file-name "shadowfile-tests" temporary-file-directory))
655 mocked-input `(,(shadow-regexp-superquote file)
656 ,cluster1 ,cluster2 ,(kbd "RET")))
657 (with-temp-buffer
658 (setq-local buffer-file-name nil)
659 (call-interactively 'shadow-define-regexp-group))
660
661 ;; `shadow-regexp-groups' is a list of lists.
662 (should (consp shadow-regexp-groups))
663 (should (consp (car shadow-regexp-groups)))
664 (should-not (cdr shadow-regexp-groups))
665
666 (should
667 (member
668 (concat
669 (shadow-site-primary cluster1) (shadow-regexp-superquote file))
670 (car shadow-regexp-groups)))
671 (should
672 (member
673 (concat
674 (shadow-site-primary cluster2) (shadow-regexp-superquote file))
675 (car shadow-regexp-groups))))
676
677 ;; Cleanup.
678 (when (file-exists-p shadow-info-file)
679 (delete-file shadow-info-file))
680 (when (file-exists-p shadow-todo-file)
681 (delete-file shadow-todo-file)))))
682
683(ert-deftest shadow-test08-shadow-todo ()
684 "Check that needed shadows are added to todo."
685 (let ((backup-inhibited t)
686 (shadow-info-file shadow-test-info-file)
687 (shadow-todo-file shadow-test-todo-file)
688 (shadow-inhibit-message t)
689 shadow-clusters shadow-literal-groups shadow-regexp-groups
690 shadow-files-to-copy
691 cluster1 cluster2 primary regexp file)
692 (unwind-protect
693 (progn
694 ;; Cleanup.
695 (when (file-exists-p shadow-info-file)
696 (delete-file shadow-info-file))
697 (when (file-exists-p shadow-todo-file)
698 (delete-file shadow-todo-file))
699
700 ;; Define clusters.
701 (setq cluster1 "cluster1"
702 primary shadow-system-name
703 regexp (shadow-regexp-superquote primary))
704 (shadow-set-cluster cluster1 primary regexp)
705
706 (setq cluster2 "cluster2"
707 primary
708 (file-remote-p shadow-test-remote-temporary-file-directory)
709 regexp (shadow-regexp-superquote primary))
710 (shadow-set-cluster cluster2 primary regexp)
711
712 ;; Define a literal group.
713 (setq file
714 (make-temp-name
715 (expand-file-name "shadowfile-tests" temporary-file-directory))
716 shadow-literal-groups
717 `((,(concat "/cluster1:" file) ,(concat "/cluster2:" file))))
718
719 ;; Save file from "cluster1" definition.
720 (with-temp-buffer
721 (setq buffer-file-name file)
722 (insert "foo")
723 (save-buffer))
724 (should
725 (member
726 (cons file (shadow-contract-file-name (concat "/cluster2:" file)))
727 shadow-files-to-copy))
728
729 ;; Save file from "cluster2" definition.
730 (with-temp-buffer
731 (setq buffer-file-name (concat (shadow-site-primary cluster2) file))
732 (insert "foo")
733 (save-buffer))
734 (should
735 (member
736 (cons
737 (concat (shadow-site-primary cluster2) file)
738 (shadow-contract-file-name (concat "/cluster1:" file)))
739 shadow-files-to-copy))
740
741 ;; Define a regexp group.
742 (setq shadow-files-to-copy nil
743 shadow-regexp-groups
744 `((,(concat (shadow-site-primary cluster1)
745 (shadow-regexp-superquote file))
746 ,(concat (shadow-site-primary cluster2)
747 (shadow-regexp-superquote file)))))
748
749 ;; Save file from "cluster1" definition.
750 (with-temp-buffer
751 (setq buffer-file-name file)
752 (insert "foo")
753 (save-buffer))
754 (should
755 (member
756 (cons file (shadow-contract-file-name (concat "/cluster2:" file)))
757 shadow-files-to-copy))
758
759 ;; Save file from "cluster2" definition.
760 (with-temp-buffer
761 (setq buffer-file-name (concat (shadow-site-primary cluster2) file))
762 (insert "foo")
763 (save-buffer))
764 (should
765 (member
766 (cons
767 (concat (shadow-site-primary cluster2) file)
768 (shadow-contract-file-name (concat "/cluster1:" file)))
769 shadow-files-to-copy)))
770
771 ;; Cleanup.
772 (when (file-exists-p shadow-info-file)
773 (delete-file shadow-info-file))
774 (when (file-exists-p shadow-todo-file)
775 (delete-file shadow-todo-file))
776 (when (file-exists-p file)
777 (delete-file file))
778 (when (file-exists-p (concat (shadow-site-primary cluster2) file))
779 (delete-file (concat (shadow-site-primary cluster2) file))))))
780
781(ert-deftest shadow-test09-shadow-copy-files ()
782 "Check that needed shadow files are copied."
783 (let ((backup-inhibited t)
784 (shadow-info-file shadow-test-info-file)
785 (shadow-todo-file shadow-test-todo-file)
786 (shadow-inhibit-message t)
787 (shadow-noquery t)
788 shadow-clusters shadow-files-to-copy
789 cluster1 cluster2 primary regexp file mocked-input)
790 (unwind-protect
791 (progn
792 ;; Cleanup.
793 (when (file-exists-p shadow-info-file)
794 (delete-file shadow-info-file))
795 (when (file-exists-p shadow-todo-file)
796 (delete-file shadow-todo-file))
797 (when (buffer-live-p shadow-todo-buffer)
798 (with-current-buffer shadow-todo-buffer (erase-buffer)))
799
800 ;; Define clusters.
801 (setq cluster1 "cluster1"
802 primary shadow-system-name
803 regexp (shadow-regexp-superquote primary))
804 (shadow-set-cluster cluster1 primary regexp)
805
806 (setq cluster2 "cluster2"
807 primary
808 (file-remote-p shadow-test-remote-temporary-file-directory)
809 regexp (shadow-regexp-superquote primary))
810 (shadow-set-cluster cluster2 primary regexp)
811
812 ;; Define files to copy.
813 (setq file
814 (make-temp-name
815 (expand-file-name "shadowfile-tests" temporary-file-directory))
816 shadow-literal-groups
817 `((,(concat "/cluster1:" file) ,(concat "/cluster2:" file)))
818 shadow-regexp-groups
819 `((,(concat (shadow-site-primary cluster1)
820 (shadow-regexp-superquote file))
821 ,(concat (shadow-site-primary cluster2)
822 (shadow-regexp-superquote file))))
823 mocked-input `(,(concat (shadow-site-primary cluster2) file)
824 ,file))
825
826 ;; Save files.
827 (with-temp-buffer
828 (setq buffer-file-name file)
829 (insert "foo")
830 (save-buffer))
831 (with-temp-buffer
832 (setq buffer-file-name (concat (shadow-site-primary cluster2) file))
833 (insert "foo")
834 (save-buffer))
835
836 ;; We must mock `write-region', in order to check proper
837 ;; action.
838 (add-function
839 :before (symbol-function 'write-region)
840 (lambda (&rest args)
841 (when (and (buffer-file-name) mocked-input)
842 (should (equal (buffer-file-name) (pop mocked-input)))))
843 '((name . "write-region-mock")))
844
845 ;; Copy the files.
846 (shadow-copy-files 'noquery)
847 (should-not shadow-files-to-copy)
848 (with-current-buffer shadow-todo-buffer
849 (goto-char (point-min))
850 (should
851 (looking-at (regexp-quote "(setq shadow-files-to-copy nil)")))))
852
853 ;; Cleanup.
854 (remove-function (symbol-function 'write-region) "write-region-mock")
855 (when (file-exists-p shadow-info-file)
856 (delete-file shadow-info-file))
857 (when (file-exists-p shadow-todo-file)
858 (delete-file shadow-todo-file))
859 (when (file-exists-p file)
860 (delete-file file))
861 (when (file-exists-p (concat (shadow-site-primary cluster2) file))
862 (delete-file (concat (shadow-site-primary cluster2) file))))))
863
864(defun shadowfile-test-all (&optional interactive)
865 "Run all tests for \\[shadowfile]."
866 (interactive "p")
867 (if interactive
868 (ert-run-tests-interactively "^shadowfile-")
869 (ert-run-tests-batch "^shadowfile-")))
870
871(let ((shadow-info-file shadow-test-info-file)
872 (shadow-todo-file shadow-test-todo-file))
873 (shadow-initialize))
874
875(provide 'shadowfile-tests)
876;;; shadowfile-tests.el ends here