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.
This commit is contained in:
parent
cb50077b1e
commit
7a258fa0bb
3 changed files with 1114 additions and 232 deletions
8
etc/NEWS
8
etc/NEWS
|
@ -94,12 +94,20 @@ it now shows the global revision number, in the form of its changeset
|
|||
hash value. To get back the previous behavior, customize the new
|
||||
option 'vc-hg-symbolic-revision-styles' to the value '("{rev}")'.
|
||||
|
||||
---
|
||||
** shadowfile.el has been rewritten to support Tramp file names.
|
||||
|
||||
|
||||
* New Modes and Packages in Emacs 26.2
|
||||
|
||||
|
||||
* Incompatible Lisp Changes in Emacs 26.2
|
||||
|
||||
---
|
||||
** shadowfile config files have changed their syntax.
|
||||
Existing files "~/.emacs.d/shadows" and "~/.emacs.d/shadow_todo" must
|
||||
be removed prior using the changed 'shadow-*' commands.
|
||||
|
||||
|
||||
* Lisp Changes in Emacs 26.2
|
||||
|
||||
|
|
|
@ -25,37 +25,38 @@
|
|||
;; This package helps you to keep identical copies of files in more than one
|
||||
;; place - possibly on different machines. When you save a file, it checks
|
||||
;; whether it is on the list of files with "shadows", and if so, it tries to
|
||||
;; copy it when you exit Emacs (or use the shadow-copy-files command).
|
||||
;; copy it when you exit Emacs (or use the `shadow-copy-files' command).
|
||||
|
||||
;; Installation & Use:
|
||||
|
||||
;; Add clusters (if necessary) and file groups with shadow-define-cluster,
|
||||
;; shadow-define-literal-group, and shadow-define-regexp-group (see the
|
||||
;; Add clusters (if necessary) and file groups with `shadow-define-cluster',
|
||||
;; `shadow-define-literal-group', and `shadow-define-regexp-group' (see the
|
||||
;; documentation for these functions for information on how and when to use
|
||||
;; them). After doing this once, everything should be automatic.
|
||||
|
||||
;; The lists of clusters and shadows are saved in a ~/.emacs.d/shadows
|
||||
;; (`shadow-info-file') file, so that they can be remembered from one
|
||||
;; Emacs session to another, even (as much as possible) if the Emacs
|
||||
;; session terminates abnormally. The files needing to be copied are
|
||||
;; stored in `shadow-todo-file'; if a file cannot be copied for any
|
||||
;; reason, it will stay on the list to be tried again next time. The
|
||||
;; `shadow-info-file' file should itself have shadows on all your accounts
|
||||
;; so that the information in it is consistent everywhere, but
|
||||
;; `shadow-todo-file' is local information and should have no shadows.
|
||||
;; The lists of clusters and shadows are saved in `shadow-info-file',
|
||||
;; so that they can be remembered from one Emacs session to another,
|
||||
;; even (as much as possible) if the Emacs session terminates
|
||||
;; abnormally. The files needing to be copied are stored in
|
||||
;; `shadow-todo-file'; if a file cannot be copied for any reason, it
|
||||
;; will stay on the list to be tried again next time. The
|
||||
;; `shadow-info-file' file should itself have shadows on all your
|
||||
;; accounts so that the information in it is consistent everywhere,
|
||||
;; but `shadow-todo-file' is local information and should have no
|
||||
;; shadows.
|
||||
|
||||
;; If you do not want to copy a particular file, you can answer "no" and
|
||||
;; be asked again next time you hit C-x 4 s or exit Emacs. If you do not
|
||||
;; want to be asked again, use shadow-cancel, and you will not be asked
|
||||
;; be asked again next time you hit "C-x 4 s" or exit Emacs. If you do not
|
||||
;; want to be asked again, use "M-x shadow-cancel", and you will not be asked
|
||||
;; until you change the file and save it again. If you do not want to
|
||||
;; shadow that file ever again, you can edit it out of the shadows
|
||||
;; buffer. Anytime you edit the shadows buffer, you must type M-x
|
||||
;; shadow-read-files to load in the new information, or your changes will
|
||||
;; buffer. Anytime you edit the shadows buffer, you must type "M-x
|
||||
;; shadow-read-files" to load in the new information, or your changes will
|
||||
;; be overwritten!
|
||||
|
||||
;; Bugs & Warnings:
|
||||
;;
|
||||
;; - It is bad to have two emacses both running shadowfile at the same
|
||||
;; - It is bad to have two Emacsen both running shadowfile at the same
|
||||
;; time. It tries to detect this condition, but is not always successful.
|
||||
;;
|
||||
;; - You have to be careful not to edit a file in two locations
|
||||
|
@ -64,19 +65,16 @@
|
|||
;;
|
||||
;; - It ought to check modification times of both files to make sure
|
||||
;; it is doing the right thing. This will have to wait until
|
||||
;; file-newer-than-file-p works between machines.
|
||||
;; `file-newer-than-file-p' works between machines.
|
||||
;;
|
||||
;; - It will not make directories for you, it just fails to copy files
|
||||
;; that belong in non-existent directories.
|
||||
;;
|
||||
;; Please report any bugs to me (boris@gnu.org). Also let me know
|
||||
;; if you have suggestions or would like to be informed of updates.
|
||||
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'ange-ftp)
|
||||
(require 'tramp)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Variables
|
||||
|
@ -107,35 +105,35 @@ files that have been changed and need to be copied to other systems."
|
|||
:type 'boolean
|
||||
:group 'shadow)
|
||||
|
||||
;; FIXME in a sense, this changed in 24.4 (addition of locate-user-emacs-file),
|
||||
;; but due to the weird way this variable is initialized to nil, it didn't
|
||||
;; literally change. Same for shadow-todo-file.
|
||||
(defcustom shadow-info-file nil
|
||||
(defcustom shadow-info-file (locate-user-emacs-file "shadows" ".shadows")
|
||||
"File to keep shadow information in.
|
||||
The `shadow-info-file' should be shadowed to all your accounts to
|
||||
ensure consistency. Default: ~/.emacs.d/shadows"
|
||||
:type '(choice (const nil) file)
|
||||
:group 'shadow)
|
||||
:type 'file
|
||||
:group 'shadow
|
||||
:version "26.2")
|
||||
|
||||
(defcustom shadow-todo-file nil
|
||||
(defcustom shadow-todo-file
|
||||
(locate-user-emacs-file "shadow_todo" ".shadow_todo")
|
||||
"File to store the list of uncopied shadows in.
|
||||
This means that if a remote system is down, or for any reason you cannot or
|
||||
decide not to copy your shadow files at the end of one Emacs session, it will
|
||||
remember and ask you again in your next Emacs session.
|
||||
This file must NOT be shadowed to any other system, it is host-specific.
|
||||
Default: ~/.emacs.d/shadow_todo"
|
||||
:type '(choice (const nil) file)
|
||||
:group 'shadow)
|
||||
:type 'file
|
||||
:group 'shadow
|
||||
:version "26.2")
|
||||
|
||||
|
||||
;;; The following two variables should in most cases initialize themselves
|
||||
;;; correctly. They are provided as variables in case the defaults are wrong
|
||||
;;; on your machine (and for efficiency).
|
||||
|
||||
(defvar shadow-system-name (system-name)
|
||||
"The complete hostname of this machine.")
|
||||
(defvar shadow-system-name (concat "/" (system-name) ":")
|
||||
"The identification for local files on this machine.")
|
||||
|
||||
(defvar shadow-homedir nil
|
||||
(defvar shadow-homedir "~"
|
||||
"Your home directory on this machine.")
|
||||
|
||||
;;;
|
||||
|
@ -186,12 +184,12 @@ created by `shadow-define-regexp-group'.")
|
|||
(car list))
|
||||
|
||||
(defun shadow-regexp-superquote (string)
|
||||
"Like `regexp-quote', but includes the ^ and $.
|
||||
"Like `regexp-quote', but includes the \\` and \\'.
|
||||
This makes sure regexp matches nothing but STRING."
|
||||
(concat "^" (regexp-quote string) "$"))
|
||||
(concat "\\`" (regexp-quote string) "\\'"))
|
||||
|
||||
(defun shadow-suffix (prefix string)
|
||||
"If PREFIX begins STRING, return the rest.
|
||||
"If PREFIX begins with STRING, return the rest.
|
||||
Return value is non-nil if PREFIX and STRING are `string=' up to the length of
|
||||
PREFIX."
|
||||
(let ((lp (length prefix))
|
||||
|
@ -204,70 +202,66 @@ PREFIX."
|
|||
;;; Clusters and sites
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;; I use the term `site' to refer to a string which may be the name of a
|
||||
;;; cluster or a literal hostname. All user-level commands should accept
|
||||
;;; either.
|
||||
;;; I use the term `site' to refer to a string which may be the
|
||||
;;; cluster identification "/name:", a remote identification
|
||||
;;; "/method:user@host:", or "/system-name:' (the value of
|
||||
;;; `shadow-system-name') for the location of local files. All
|
||||
;;; user-level commands should accept either.
|
||||
|
||||
(defun shadow-make-cluster (name primary regexp)
|
||||
"Create a shadow cluster.
|
||||
It is called NAME, uses the PRIMARY hostname and REGEXP matching all
|
||||
hosts in the cluster. The variable `shadow-clusters' associates the
|
||||
names of clusters to these structures. This function is for program
|
||||
use: to create clusters interactively, use `shadow-define-cluster'
|
||||
instead."
|
||||
(list name primary regexp))
|
||||
|
||||
(defmacro shadow-cluster-name (cluster)
|
||||
"Return the name of the CLUSTER."
|
||||
(list 'elt cluster 0))
|
||||
|
||||
(defmacro shadow-cluster-primary (cluster)
|
||||
"Return the primary hostname of a CLUSTER."
|
||||
(list 'elt cluster 1))
|
||||
|
||||
(defmacro shadow-cluster-regexp (cluster)
|
||||
"Return the regexp matching hosts in a CLUSTER."
|
||||
(list 'elt cluster 2))
|
||||
(cl-defstruct (shadow-cluster (:type list) :named) name primary regexp)
|
||||
|
||||
(defun shadow-set-cluster (name primary regexp)
|
||||
"Put cluster NAME on the list of clusters.
|
||||
Replace old definition, if any. PRIMARY and REGEXP are the
|
||||
information defining the cluster. For interactive use, call
|
||||
`shadow-define-cluster' instead."
|
||||
(let ((rest (cl-remove-if (lambda (x) (equal name (car x)))
|
||||
(let ((rest (cl-remove-if (lambda (x) (equal name (shadow-cluster-name x)))
|
||||
shadow-clusters)))
|
||||
(setq shadow-clusters
|
||||
(cons (shadow-make-cluster name primary regexp)
|
||||
(cons (make-shadow-cluster :name name :primary primary :regexp regexp)
|
||||
rest))))
|
||||
|
||||
(defmacro shadow-get-cluster (name)
|
||||
(defun shadow-get-cluster (name)
|
||||
"Return cluster named NAME, or nil."
|
||||
(list 'assoc name 'shadow-clusters))
|
||||
|
||||
(defun shadow-site-primary (site)
|
||||
"If SITE is a cluster, return primary host, otherwise return SITE."
|
||||
(let ((c (shadow-get-cluster site)))
|
||||
(if c
|
||||
(shadow-cluster-primary c)
|
||||
site)))
|
||||
(shadow-find
|
||||
(lambda (x) (string-equal (shadow-cluster-name x) name))
|
||||
shadow-clusters))
|
||||
|
||||
;;; SITES
|
||||
|
||||
(defun shadow-site-name (site)
|
||||
"Return name if SITE has the form \"/name:\", otherwise SITE."
|
||||
(if (string-match "\\`/\\(\\w+\\):\\'" site)
|
||||
(match-string 1 site) site))
|
||||
|
||||
(defun shadow-name-site (name)
|
||||
"Return \"/name:\" if NAME has word syntax, otherwise NAME."
|
||||
(if (string-match "\\`\\w+\\'" name)
|
||||
(format "/%s:"name) name))
|
||||
|
||||
(defun shadow-site-primary (site)
|
||||
"If SITE is a cluster, return primary identification, otherwise return SITE."
|
||||
(let ((cluster (shadow-get-cluster (shadow-site-name site))))
|
||||
(if cluster
|
||||
(shadow-cluster-primary cluster)
|
||||
site)))
|
||||
|
||||
(defun shadow-site-cluster (site)
|
||||
"Given a SITE (hostname or cluster name), return cluster it is in, or nil."
|
||||
(or (assoc site shadow-clusters)
|
||||
"Given a SITE, return cluster it is in, or nil."
|
||||
(or (shadow-get-cluster (shadow-site-name site))
|
||||
(shadow-find
|
||||
(function (lambda (x)
|
||||
(string-match (shadow-cluster-regexp x)
|
||||
site)))
|
||||
(lambda (x)
|
||||
(string-match (shadow-cluster-regexp x) (shadow-name-site site)))
|
||||
shadow-clusters)))
|
||||
|
||||
(defun shadow-read-site ()
|
||||
"Read a cluster name or hostname from the minibuffer."
|
||||
(let ((ans (completing-read "Host or cluster name [RET when done]: "
|
||||
"Read a cluster name or host identification from the minibuffer."
|
||||
(let ((ans (completing-read "Host identification or cluster name: "
|
||||
shadow-clusters)))
|
||||
(if (equal "" ans)
|
||||
nil
|
||||
(when (or (shadow-get-cluster (shadow-site-name ans))
|
||||
(string-equal ans shadow-system-name)
|
||||
(string-equal ans (shadow-site-name shadow-system-name))
|
||||
(setq ans (file-remote-p ans)))
|
||||
ans)))
|
||||
|
||||
(defun shadow-site-match (site1 site2)
|
||||
|
@ -281,63 +275,95 @@ be matched against the primary of SITE2."
|
|||
(string-match (shadow-cluster-regexp cluster1) primary2)
|
||||
(string-equal site1 primary2)))))
|
||||
|
||||
(defun shadow-get-user (site)
|
||||
"Return the default username for a SITE."
|
||||
(ange-ftp-get-user (shadow-site-primary site)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Filename manipulation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun shadow-parse-fullname (fullname)
|
||||
"Parse FULLNAME into (site user path) list.
|
||||
Leave it alone if it already is one. Return nil if the argument is
|
||||
not a full ange-ftp pathname."
|
||||
(if (listp fullname)
|
||||
fullname
|
||||
(ange-ftp-ftp-name fullname)))
|
||||
|
||||
(defun shadow-parse-name (name)
|
||||
"Parse any NAME into (site user name) list.
|
||||
Argument can be a simple name, full ange-ftp name, or already a hup list."
|
||||
(or (shadow-parse-fullname name)
|
||||
(list shadow-system-name
|
||||
(user-login-name)
|
||||
name)))
|
||||
"Parse any NAME into a `tramp-file-name' structure.
|
||||
Argument can be a simple name, remote file name, or already a
|
||||
`tramp-file-name' structure."
|
||||
(cond
|
||||
((null name) nil)
|
||||
((tramp-file-name-p name) name)
|
||||
((file-remote-p name) (tramp-dissect-file-name name))
|
||||
((shadow-local-file name)
|
||||
(make-tramp-file-name
|
||||
:host (shadow-site-name shadow-system-name)
|
||||
:localname (shadow-local-file name)))
|
||||
;; Cluster name.
|
||||
((string-match "^/\\([^:/]+\\):\\([^:]*\\)$" name)
|
||||
(let ((name (match-string 1 name))
|
||||
(file (match-string 2 name)))
|
||||
(when (shadow-get-cluster name)
|
||||
(make-tramp-file-name :host name :localname file))))))
|
||||
|
||||
(defsubst shadow-make-fullname (host user name)
|
||||
"Make an ange-ftp style fullname out of HOST, USER (optional), and NAME.
|
||||
This is probably not as general as it ought to be."
|
||||
(concat "/"
|
||||
(if user (concat user "@"))
|
||||
host ":"
|
||||
name))
|
||||
(defsubst shadow-make-fullname (hup &optional host name)
|
||||
"Make a Tramp style fullname out of HUP, a `tramp-file-name' structure.
|
||||
Replace HOST, and NAME when non-nil."
|
||||
(let ((hup (copy-tramp-file-name hup)))
|
||||
(when host (setf (tramp-file-name-host hup) host))
|
||||
(when name (setf (tramp-file-name-localname hup) name))
|
||||
(if (null (tramp-file-name-method hup))
|
||||
(format
|
||||
"/%s:%s" (tramp-file-name-host hup) (tramp-file-name-localname hup))
|
||||
(tramp-make-tramp-file-name
|
||||
(tramp-file-name-method hup)
|
||||
(tramp-file-name-user hup)
|
||||
(tramp-file-name-domain hup)
|
||||
(tramp-file-name-host hup)
|
||||
(tramp-file-name-port hup)
|
||||
(tramp-file-name-localname hup)
|
||||
(tramp-file-name-hop hup)))))
|
||||
|
||||
(defun shadow-replace-name-component (fullname newname)
|
||||
"Return FULLNAME with the name component changed to NEWNAME."
|
||||
(let ((hup (shadow-parse-fullname fullname)))
|
||||
(shadow-make-fullname (nth 0 hup) (nth 1 hup) newname)))
|
||||
(concat (file-remote-p fullname) newname))
|
||||
|
||||
(defun shadow-local-file (file)
|
||||
"If FILE is at this site, remove /user@host part.
|
||||
If refers to a different system or a different user on this system,
|
||||
return nil."
|
||||
(let ((hup (shadow-parse-fullname file)))
|
||||
(cond ((null hup) file)
|
||||
((and (shadow-site-match (nth 0 hup) shadow-system-name)
|
||||
(string-equal (nth 1 hup) (user-login-name)))
|
||||
(nth 2 hup))
|
||||
(t nil))))
|
||||
"If FILE is not remote, return it.
|
||||
If it refers to a different system, return nil."
|
||||
(cond
|
||||
((null file) nil)
|
||||
;; `tramp-file-name' structure.
|
||||
((and (tramp-file-name-p file) (null (tramp-file-name-method file)))
|
||||
(tramp-file-name-localname file))
|
||||
((tramp-file-name-p file) nil)
|
||||
;; Local host name.
|
||||
((string-match
|
||||
(format "^%s\\([^:]*\\)$" (regexp-quote shadow-system-name)) file)
|
||||
(match-string 1 file))
|
||||
;; Cluster name.
|
||||
((and (string-match "^/\\([^:/]+\\):\\([^:]*\\)$" file)
|
||||
(shadow-get-cluster (match-string 1 file)))
|
||||
(let ((file (match-string 2 file))
|
||||
(primary
|
||||
(shadow-cluster-primary
|
||||
(shadow-get-cluster (match-string 1 file)))))
|
||||
(when (string-equal primary shadow-system-name) (setq primary nil))
|
||||
(shadow-local-file (concat primary file))))
|
||||
;; Local name.
|
||||
((null (file-remote-p file)) file)))
|
||||
|
||||
(defun shadow-expand-cluster-in-file-name (file)
|
||||
"If hostname part of FILE is a cluster, expand it to cluster's primary hostname.
|
||||
Will return the name bare if it is a local file."
|
||||
(let ((hup (shadow-parse-name file)))
|
||||
(cond ((null hup) file)
|
||||
((shadow-local-file hup))
|
||||
((shadow-make-fullname (shadow-site-primary (nth 0 hup))
|
||||
(nth 1 hup)
|
||||
(nth 2 hup))))))
|
||||
(when (stringp file)
|
||||
(cond
|
||||
;; Local file.
|
||||
((shadow-local-file file))
|
||||
;; Cluster name.
|
||||
((string-match "^\\(/[^:/]+:\\)[^:]*$" file)
|
||||
(let ((primary
|
||||
(save-match-data
|
||||
(shadow-cluster-primary
|
||||
(shadow-get-cluster
|
||||
(shadow-site-name (match-string 1 file)))))))
|
||||
(if (not primary)
|
||||
file
|
||||
(setq file (replace-match primary nil nil file 1))
|
||||
(or (shadow-local-file file) file))))
|
||||
(t file))))
|
||||
|
||||
(defun shadow-expand-file-name (file &optional default)
|
||||
"Expand file name and get FILE's true name."
|
||||
|
@ -352,46 +378,50 @@ true."
|
|||
(homedir (if (shadow-local-file hup)
|
||||
shadow-homedir
|
||||
(file-name-as-directory
|
||||
(nth 2 (shadow-parse-fullname
|
||||
(expand-file-name
|
||||
(shadow-make-fullname
|
||||
(nth 0 hup) (nth 1 hup) "~")))))))
|
||||
(suffix (shadow-suffix homedir (nth 2 hup)))
|
||||
(cluster (shadow-site-cluster (nth 0 hup))))
|
||||
(file-local-name
|
||||
(expand-file-name (shadow-make-fullname hup nil "~"))))))
|
||||
(suffix (shadow-suffix homedir (tramp-file-name-localname hup)))
|
||||
(cluster (shadow-site-cluster (shadow-make-fullname hup nil ""))))
|
||||
(when cluster
|
||||
(setf (tramp-file-name-method hup) nil
|
||||
(tramp-file-name-host hup) (shadow-cluster-name cluster)))
|
||||
(shadow-make-fullname
|
||||
(if cluster
|
||||
(shadow-cluster-name cluster)
|
||||
(nth 0 hup))
|
||||
(nth 1 hup)
|
||||
hup nil
|
||||
(if suffix
|
||||
(concat "~/" suffix)
|
||||
(nth 2 hup)))))
|
||||
(concat "~/" suffix)
|
||||
(tramp-file-name-localname hup)))))
|
||||
|
||||
(defun shadow-same-site (pattern file)
|
||||
"True if the site of PATTERN and of FILE are on the same site.
|
||||
If usernames are supplied, they must also match exactly. PATTERN and FILE may
|
||||
be lists of host, user, name, or ange-ftp file names. FILE may also be just a
|
||||
local filename."
|
||||
(let ((pattern-sup (shadow-parse-fullname pattern))
|
||||
PATTERN and FILE may be Tramp vectors, or remote file names.
|
||||
FILE may also be just a local filename."
|
||||
(let ((pattern-sup (shadow-parse-name pattern))
|
||||
(file-sup (shadow-parse-name file)))
|
||||
(and
|
||||
(shadow-site-match (nth 0 pattern-sup) (nth 0 file-sup))
|
||||
(or (null (nth 1 pattern-sup))
|
||||
(string-equal (nth 1 pattern-sup) (nth 1 file-sup))))))
|
||||
(shadow-site-match
|
||||
(tramp-file-name-host pattern-sup) (tramp-file-name-host file-sup))
|
||||
(or (null (tramp-file-name-user pattern-sup))
|
||||
(string-equal
|
||||
(tramp-file-name-user pattern-sup)
|
||||
(tramp-file-name-user file-sup))))))
|
||||
|
||||
(defun shadow-file-match (pattern file &optional regexp)
|
||||
"Return t if PATTERN matches FILE.
|
||||
If REGEXP is supplied and non-nil, the file part of the pattern is a regular
|
||||
expression, otherwise it must match exactly. The sites and usernames must
|
||||
match---see `shadow-same-site'. The pattern must be in full ange-ftp format,
|
||||
expression, otherwise it must match exactly. The sites must
|
||||
match---see `shadow-same-site'. The pattern must be in full Tramp format,
|
||||
but the file can be any valid filename. This function does not do any
|
||||
filename expansion or contraction, you must do that yourself first."
|
||||
(let* ((pattern-sup (shadow-parse-fullname pattern))
|
||||
(let* ((pattern-sup (shadow-parse-name pattern))
|
||||
(file-sup (shadow-parse-name file)))
|
||||
(and (shadow-same-site pattern-sup file-sup)
|
||||
(if regexp
|
||||
(string-match (nth 2 pattern-sup) (nth 2 file-sup))
|
||||
(string-equal (nth 2 pattern-sup) (nth 2 file-sup))))))
|
||||
(string-match
|
||||
(tramp-file-name-localname pattern-sup)
|
||||
(tramp-file-name-localname file-sup))
|
||||
(string-equal
|
||||
(tramp-file-name-localname pattern-sup)
|
||||
(tramp-file-name-localname file-sup))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; User-level Commands
|
||||
|
@ -405,30 +435,34 @@ one of them is sufficient to update the file on all of them. Clusters are
|
|||
defined by a name, the network address of a primary host (the one we copy
|
||||
files to), and a regular expression that matches the hostnames of all the
|
||||
sites in the cluster."
|
||||
(interactive (list (completing-read "Cluster name: " shadow-clusters () ())))
|
||||
(interactive (list (completing-read "Cluster name: " shadow-clusters)))
|
||||
(let* ((old (shadow-get-cluster name))
|
||||
(primary (read-string "Primary host: "
|
||||
(if old (shadow-cluster-primary old)
|
||||
name)))
|
||||
(regexp (let (try-regexp)
|
||||
(while (not
|
||||
(string-match
|
||||
(setq try-regexp
|
||||
(primary (let (try-primary)
|
||||
(while (not
|
||||
(or
|
||||
(string-equal
|
||||
(setq try-primary
|
||||
(read-string
|
||||
"Regexp matching all host names: "
|
||||
(if old (shadow-cluster-regexp old)
|
||||
(shadow-regexp-superquote primary))))
|
||||
primary))
|
||||
(message "Regexp doesn't include the primary host!")
|
||||
(sit-for 2))
|
||||
try-regexp))
|
||||
; (username (read-no-blanks-input
|
||||
; (format "Username (default %s): "
|
||||
; (shadow-get-user primary))
|
||||
; (if old (or (shadow-cluster-username old) "")
|
||||
; (user-login-name))))
|
||||
)
|
||||
; (if (string-equal "" username) (setq username nil))
|
||||
"Primary host: "
|
||||
(if old (shadow-cluster-primary old)
|
||||
name)))
|
||||
shadow-system-name)
|
||||
(file-remote-p try-primary)))
|
||||
(message "Not a valid primary!")
|
||||
(sit-for 2))
|
||||
try-primary))
|
||||
(regexp (let (try-regexp)
|
||||
(while (not
|
||||
(string-match
|
||||
(setq try-regexp
|
||||
(read-string
|
||||
"Regexp matching all host names: "
|
||||
(if old (shadow-cluster-regexp old)
|
||||
(shadow-regexp-superquote primary))))
|
||||
primary))
|
||||
(message "Regexp doesn't include the primary host!")
|
||||
(sit-for 2))
|
||||
try-regexp)))
|
||||
(shadow-set-cluster name primary regexp)))
|
||||
|
||||
;;;###autoload
|
||||
|
@ -438,20 +472,14 @@ It may have different filenames on each site. When this file is edited, the
|
|||
new version will be copied to each of the other locations. Sites can be
|
||||
specific hostnames, or names of clusters (see `shadow-define-cluster')."
|
||||
(interactive)
|
||||
(let* ((hup (shadow-parse-fullname
|
||||
(let* ((hup (shadow-parse-name
|
||||
(shadow-contract-file-name (buffer-file-name))))
|
||||
(name (nth 2 hup))
|
||||
user site group)
|
||||
(name (tramp-file-name-localname hup))
|
||||
site group)
|
||||
(while (setq site (shadow-read-site))
|
||||
(setq user (read-string (format "Username (default %s): "
|
||||
(shadow-get-user site)))
|
||||
name (read-string "Filename: " name))
|
||||
(setq group (cons (shadow-make-fullname site
|
||||
(if (string-equal "" user)
|
||||
(shadow-get-user site)
|
||||
user)
|
||||
name)
|
||||
group)))
|
||||
(setq name (read-string "Filename: " name)
|
||||
hup (shadow-parse-name (shadow-contract-file-name name))
|
||||
group (cons (shadow-make-fullname hup site) group)))
|
||||
(setq shadow-literal-groups (cons group shadow-literal-groups)))
|
||||
(shadow-write-info-file))
|
||||
|
||||
|
@ -468,19 +496,12 @@ function). Each site can be either a hostname or the name of a cluster (see
|
|||
"Filename regexp: "
|
||||
(if (buffer-file-name)
|
||||
(shadow-regexp-superquote
|
||||
(nth 2
|
||||
(shadow-parse-name
|
||||
(shadow-contract-file-name
|
||||
(buffer-file-name))))))))
|
||||
site sites usernames)
|
||||
(file-local-name (buffer-file-name))))))
|
||||
site sites)
|
||||
(while (setq site (shadow-read-site))
|
||||
(setq sites (cons site sites))
|
||||
(setq usernames
|
||||
(cons (read-string (format "Username for %s: " site)
|
||||
(shadow-get-user site))
|
||||
usernames)))
|
||||
(setq sites (cons site sites)))
|
||||
(setq shadow-regexp-groups
|
||||
(cons (shadow-make-group regexp sites usernames)
|
||||
(cons (shadow-make-group regexp sites)
|
||||
shadow-regexp-groups))
|
||||
(shadow-write-info-file)))
|
||||
|
||||
|
@ -537,14 +558,14 @@ permanently, remove the group from `shadow-literal-groups' or
|
|||
;;; Internal functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun shadow-make-group (regexp sites usernames)
|
||||
(defun shadow-make-group (regexp sites)
|
||||
"Make a description of a file group---
|
||||
actually a list of regexp ange-ftp file names---from REGEXP (name of file to
|
||||
be shadowed), list of SITES, and corresponding list of USERNAMES for each
|
||||
site."
|
||||
actually a list of regexp Tramp file names---from REGEXP (name of file to
|
||||
be shadowed), and list of SITES"
|
||||
(if sites
|
||||
(cons (shadow-make-fullname (car sites) (car usernames) regexp)
|
||||
(shadow-make-group regexp (cdr sites) (cdr usernames)))
|
||||
(cons (shadow-make-fullname
|
||||
(shadow-parse-name (shadow-site-primary (car sites))) nil regexp)
|
||||
(shadow-make-group regexp (cdr sites)))
|
||||
nil))
|
||||
|
||||
(defun shadow-copy-file (s)
|
||||
|
@ -601,7 +622,9 @@ Consider them as regular expressions if third arg REGEXP is true."
|
|||
(car groups))))
|
||||
(append (cond ((equal nonmatching (car groups)) nil)
|
||||
(regexp
|
||||
(let ((realname (nth 2 (shadow-parse-fullname file))))
|
||||
(let ((realname
|
||||
(tramp-file-name-localname
|
||||
(shadow-parse-name file))))
|
||||
(mapcar
|
||||
(function
|
||||
(lambda (x)
|
||||
|
@ -636,9 +659,8 @@ PAIR must be `eq' to one of the elements of that list."
|
|||
Thus restores shadowfile's state from your last Emacs session.
|
||||
Return t unless files were locked; then return nil."
|
||||
(interactive)
|
||||
(if (and (fboundp 'file-locked-p)
|
||||
(or (stringp (file-locked-p shadow-info-file))
|
||||
(stringp (file-locked-p shadow-todo-file))))
|
||||
(if (or (stringp (file-locked-p shadow-info-file))
|
||||
(stringp (file-locked-p shadow-todo-file)))
|
||||
(progn
|
||||
(message "Shadowfile is running in another Emacs; can't have two.")
|
||||
(beep)
|
||||
|
@ -647,7 +669,7 @@ Return t unless files were locked; then return nil."
|
|||
(save-current-buffer
|
||||
(when shadow-info-file
|
||||
(set-buffer (setq shadow-info-buffer
|
||||
(find-file-noselect shadow-info-file)))
|
||||
(find-file-noselect shadow-info-file 'nowarn)))
|
||||
(when (and (not (buffer-modified-p))
|
||||
(file-newer-than-file-p (make-auto-save-file-name)
|
||||
shadow-info-file))
|
||||
|
@ -680,6 +702,7 @@ defined, the old hashtable info is invalid."
|
|||
(if (not shadow-info-buffer)
|
||||
(setq shadow-info-buffer (find-file-noselect shadow-info-file)))
|
||||
(set-buffer shadow-info-buffer)
|
||||
(setq buffer-read-only nil)
|
||||
(delete-region (point-min) (point-max))
|
||||
(shadow-insert-var 'shadow-clusters)
|
||||
(shadow-insert-var 'shadow-literal-groups)
|
||||
|
@ -692,6 +715,7 @@ With non-nil argument also saves the buffer."
|
|||
(if (not shadow-todo-buffer)
|
||||
(setq shadow-todo-buffer (find-file-noselect shadow-todo-file)))
|
||||
(set-buffer shadow-todo-buffer)
|
||||
(setq buffer-read-only nil)
|
||||
(delete-region (point-min) (point-max))
|
||||
(shadow-insert-var 'shadow-files-to-copy)
|
||||
(if save (shadow-save-todo-file))))
|
||||
|
@ -764,24 +788,6 @@ look for files that have been changed and need to be copied to other systems."
|
|||
(yes-or-no-p "Active processes exist; kill them and exit anyway? "))))
|
||||
(kill-emacs)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Lucid Emacs compatibility
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; This is on hold until someone tells me about a working version of
|
||||
;; map-ynp for Lucid Emacs.
|
||||
|
||||
;(when (string-match "Lucid" emacs-version)
|
||||
; (require 'symlink-fix)
|
||||
; (require 'ange-ftp)
|
||||
; (require 'map-ynp)
|
||||
; (if (not (fboundp 'file-truename))
|
||||
; (fset 'shadow-expand-file-name
|
||||
; (symbol-function 'symlink-expand-file-name)))
|
||||
; (if (not (fboundp 'ange-ftp-ftp-name))
|
||||
; (fset 'ange-ftp-ftp-name
|
||||
; (symbol-function 'ange-ftp-ftp-name))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; Hook us up
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -790,18 +796,10 @@ look for files that have been changed and need to be copied to other systems."
|
|||
(defun shadow-initialize ()
|
||||
"Set up file shadowing."
|
||||
(interactive)
|
||||
(if (null shadow-homedir)
|
||||
(setq shadow-homedir
|
||||
(file-name-as-directory (shadow-expand-file-name "~"))))
|
||||
(if (null shadow-info-file)
|
||||
(setq shadow-info-file
|
||||
;; FIXME: Move defaults to their defcustom.
|
||||
(shadow-expand-file-name
|
||||
(locate-user-emacs-file "shadows" ".shadows"))))
|
||||
(if (null shadow-todo-file)
|
||||
(setq shadow-todo-file
|
||||
(shadow-expand-file-name
|
||||
(locate-user-emacs-file "shadow_todo" ".shadow_todo"))))
|
||||
(setq shadow-homedir
|
||||
(file-name-as-directory (shadow-expand-file-name shadow-homedir))
|
||||
shadow-info-file (shadow-expand-file-name shadow-info-file)
|
||||
shadow-todo-file (shadow-expand-file-name shadow-todo-file))
|
||||
(if (not (shadow-read-files))
|
||||
(progn
|
||||
(message "Shadowfile information files not found - aborting")
|
||||
|
|
876
test/lisp/shadowfile-tests.el
Normal file
876
test/lisp/shadowfile-tests.el
Normal file
|
@ -0,0 +1,876 @@
|
|||
;;; shadowfile-tests.el --- Tests of shadowfile
|
||||
|
||||
;; Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Michael Albinus <michael.albinus@gmx.de>
|
||||
|
||||
;; This program is free software: you can redistribute it and/or
|
||||
;; modify it under the terms of the GNU General Public License as
|
||||
;; published by the Free Software Foundation, either version 3 of the
|
||||
;; License, or (at your option) any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful, but
|
||||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;; General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; A whole test run can be performed calling the command `shadowfile-test-all'.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'shadowfile)
|
||||
(require 'tramp)
|
||||
|
||||
;; There is no default value on w32 systems, which could work out of the box.
|
||||
(defconst shadow-test-remote-temporary-file-directory
|
||||
(cond
|
||||
((getenv "REMOTE_TEMPORARY_FILE_DIRECTORY"))
|
||||
((eq system-type 'windows-nt) null-device)
|
||||
(t (add-to-list
|
||||
'tramp-methods
|
||||
'("mock"
|
||||
(tramp-login-program "sh")
|
||||
(tramp-login-args (("-i")))
|
||||
(tramp-remote-shell "/bin/sh")
|
||||
(tramp-remote-shell-args ("-c"))
|
||||
(tramp-connection-timeout 10)))
|
||||
(add-to-list
|
||||
'tramp-default-host-alist
|
||||
`("\\`mock\\'" nil ,(system-name)))
|
||||
;; Emacs' Makefile sets $HOME to a nonexistent value. Needed in
|
||||
;; batch mode only, therefore. It cannot be
|
||||
;; `temporary-directory', because the tests with "~" would fail.
|
||||
(unless (and (null noninteractive) (file-directory-p "~/"))
|
||||
(setenv "HOME" invocation-directory))
|
||||
(format "/mock::%s" temporary-file-directory)))
|
||||
"Temporary directory for Tramp tests.")
|
||||
|
||||
(defconst shadow-test-info-file
|
||||
(expand-file-name "shadows_test" temporary-file-directory)
|
||||
"File to keep shadow information in during tests.")
|
||||
|
||||
(defconst shadow-test-todo-file
|
||||
(expand-file-name "shadow_todo_test" temporary-file-directory)
|
||||
"File to store the list of uncopied shadows in during tests.")
|
||||
|
||||
(ert-deftest shadow-test00-clusters ()
|
||||
"Check cluster definitions.
|
||||
Per definition, all files are identical on the different hosts of
|
||||
a cluster (or site). This is not tested here; it must be
|
||||
guaranteed by the originator of a cluster definition."
|
||||
(skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
|
||||
|
||||
(let ((text-quoting-style 'grave) ;; We inspect the *Messages* buffer!
|
||||
(inhibit-message t)
|
||||
(shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters
|
||||
cluster primary regexp mocked-input)
|
||||
(unwind-protect
|
||||
;; We must mock `read-from-minibuffer' and `read-string', in
|
||||
;; order to avoid interactive arguments.
|
||||
(cl-letf* (((symbol-function 'read-from-minibuffer)
|
||||
(lambda (&rest args) (pop mocked-input)))
|
||||
((symbol-function 'read-string)
|
||||
(lambda (&rest args) (pop mocked-input))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define a cluster.
|
||||
(setq cluster "cluster"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary)
|
||||
mocked-input `(,cluster ,primary ,regexp))
|
||||
(call-interactively 'shadow-define-cluster)
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-name (shadow-get-cluster cluster)) cluster))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-primary (shadow-get-cluster cluster)) primary))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
|
||||
(should-not (shadow-get-cluster "non-existent-cluster-name"))
|
||||
|
||||
;; Test `shadow-set-cluster' and `make-shadow-cluster'.
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
(should
|
||||
(equal (shadow-get-cluster cluster)
|
||||
(make-shadow-cluster
|
||||
:name cluster :primary primary :regexp regexp)))
|
||||
|
||||
;; The primary must be either `shadow-system-name', or a remote file.
|
||||
(setq ;; The second "cluster" is wrong.
|
||||
mocked-input `(,cluster ,cluster ,primary ,regexp))
|
||||
(with-current-buffer (messages-buffer)
|
||||
(narrow-to-region (point-max) (point-max)))
|
||||
(call-interactively 'shadow-define-cluster)
|
||||
(should
|
||||
(string-match
|
||||
(regexp-quote "Not a valid primary!")
|
||||
(with-current-buffer (messages-buffer) (buffer-string))))
|
||||
;; The first cluster definition is still valid.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-name (shadow-get-cluster cluster)) cluster))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-primary (shadow-get-cluster cluster)) primary))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
|
||||
|
||||
;; The regexp must match the primary name.
|
||||
(setq ;; The second "cluster" is wrong.
|
||||
mocked-input `(,cluster ,primary ,cluster ,regexp))
|
||||
(with-current-buffer (messages-buffer)
|
||||
(narrow-to-region (point-max) (point-max)))
|
||||
(call-interactively 'shadow-define-cluster)
|
||||
(should
|
||||
(string-match
|
||||
(regexp-quote "Regexp doesn't include the primary host!")
|
||||
(with-current-buffer (messages-buffer) (buffer-string))))
|
||||
;; The first cluster definition is still valid.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-name (shadow-get-cluster cluster)) cluster))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-primary (shadow-get-cluster cluster)) primary))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
|
||||
|
||||
;; Redefine the cluster.
|
||||
(setq primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary)
|
||||
mocked-input `(,cluster ,primary ,regexp))
|
||||
(call-interactively 'shadow-define-cluster)
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-name (shadow-get-cluster cluster)) cluster))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-primary (shadow-get-cluster cluster)) primary))
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-cluster-regexp (shadow-get-cluster cluster)) regexp))
|
||||
|
||||
;; Test `shadow-set-cluster' and `make-shadow-cluster'.
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
(should
|
||||
(equal (shadow-get-cluster cluster)
|
||||
(make-shadow-cluster
|
||||
:name cluster :primary primary :regexp regexp))))
|
||||
|
||||
;; Cleanup.
|
||||
(with-current-buffer (messages-buffer) (widen))
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test01-sites ()
|
||||
"Check site definitions.
|
||||
Per definition, all files are identical on the different hosts of
|
||||
a cluster (or site). This is not tested here; it must be
|
||||
guaranteed by the originator of a cluster definition."
|
||||
(skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
|
||||
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters
|
||||
cluster1 cluster2 primary1 primary2 regexp1 regexp2 mocked-input)
|
||||
(unwind-protect
|
||||
;; We must mock `read-from-minibuffer' and `read-string', in
|
||||
;; order to avoid interactive arguments.
|
||||
(cl-letf* (((symbol-function 'read-from-minibuffer)
|
||||
(lambda (&rest args) (pop mocked-input)))
|
||||
((symbol-function 'read-string)
|
||||
(lambda (&rest args) (pop mocked-input))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define a cluster.
|
||||
(setq cluster1 "cluster1"
|
||||
primary1 shadow-system-name
|
||||
regexp1 (shadow-regexp-superquote primary1))
|
||||
(shadow-set-cluster cluster1 primary1 regexp1)
|
||||
|
||||
;; A site is either a cluster identification, or a primary host.
|
||||
(should (string-equal cluster1 (shadow-site-name cluster1)))
|
||||
(should (string-equal primary1 (shadow-name-site primary1)))
|
||||
(should
|
||||
(string-equal (format "/%s:" cluster1) (shadow-name-site cluster1)))
|
||||
(should (string-equal (system-name) (shadow-site-name primary1)))
|
||||
(should
|
||||
(string-equal
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
(shadow-name-site
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory))))
|
||||
(should
|
||||
(string-equal
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
(shadow-site-name
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory))))
|
||||
|
||||
(should (equal (shadow-site-cluster cluster1)
|
||||
(shadow-get-cluster cluster1)))
|
||||
(should (equal (shadow-site-cluster (shadow-name-site cluster1))
|
||||
(shadow-get-cluster cluster1)))
|
||||
(should (equal (shadow-site-cluster primary1)
|
||||
(shadow-get-cluster cluster1)))
|
||||
(should (equal (shadow-site-cluster (shadow-site-name primary1))
|
||||
(shadow-get-cluster cluster1)))
|
||||
(should (string-equal (shadow-site-primary cluster1) primary1))
|
||||
(should (string-equal (shadow-site-primary primary1) primary1))
|
||||
|
||||
;; `shadow-read-site' accepts "cluster", "/cluster:",
|
||||
;; "system", "/system:". It shall reject bad site names.
|
||||
(setq mocked-input
|
||||
`(,cluster1 ,(shadow-name-site cluster1)
|
||||
,primary1 ,(shadow-site-name primary1)
|
||||
,shadow-system-name "" "bad" "/bad:"))
|
||||
(should (string-equal (shadow-read-site) cluster1))
|
||||
(should (string-equal (shadow-read-site) (shadow-name-site cluster1)))
|
||||
(should (string-equal (shadow-read-site) primary1))
|
||||
(should (string-equal (shadow-read-site) (shadow-site-name primary1)))
|
||||
(should (string-equal (shadow-read-site) shadow-system-name))
|
||||
(should-not (shadow-read-site)) ; ""
|
||||
(should-not (shadow-read-site)) ; "bad"
|
||||
(should-not (shadow-read-site)) ; "/bad:"
|
||||
(should-error (shadow-read-site)) ; no input at all
|
||||
|
||||
;; Define a second cluster.
|
||||
(setq cluster2 "cluster2"
|
||||
primary2
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp2 (format "^\\(%s\\|%s\\)$" shadow-system-name primary2))
|
||||
(shadow-set-cluster cluster2 primary2 regexp2)
|
||||
|
||||
;; `shadow-site-match' shall know all different kind of site names.
|
||||
(should (shadow-site-match cluster1 cluster1))
|
||||
(should (shadow-site-match primary1 primary1))
|
||||
(should (shadow-site-match cluster1 primary1))
|
||||
(should (shadow-site-match primary1 cluster1))
|
||||
(should (shadow-site-match cluster2 cluster2))
|
||||
(should (shadow-site-match primary2 primary2))
|
||||
(should (shadow-site-match cluster2 primary2))
|
||||
(should (shadow-site-match primary2 cluster2))
|
||||
|
||||
;; The regexp of `cluster2' matches the primary of
|
||||
;; `cluster1'. Not vice versa.
|
||||
(should (shadow-site-match cluster2 cluster1))
|
||||
(should-not (shadow-site-match cluster1 cluster2))
|
||||
|
||||
;; If we use the primaries of a cluster, it doesn't match.
|
||||
(should-not
|
||||
(shadow-site-match (shadow-site-primary cluster2) cluster1))
|
||||
(should-not
|
||||
(shadow-site-match (shadow-site-primary cluster1) cluster2)))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test02-files ()
|
||||
"Check file manipulation functions."
|
||||
(skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
|
||||
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters
|
||||
cluster primary regexp file hup)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define a cluster.
|
||||
(setq cluster "cluster"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary)
|
||||
file (make-temp-name
|
||||
(expand-file-name
|
||||
"shadowfile-tests" temporary-file-directory)))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
;; The constant structure to compare with.
|
||||
(setq hup (make-tramp-file-name :host (system-name) :localname file))
|
||||
|
||||
;; The structure a local file is transformed in.
|
||||
(should (equal (shadow-parse-name file) hup))
|
||||
(should (equal (shadow-parse-name (concat "/" cluster ":" file)) hup))
|
||||
(should (equal (shadow-parse-name (concat primary file)) hup))
|
||||
|
||||
;; A local file name is kept.
|
||||
(should
|
||||
(string-equal (shadow-local-file file) file))
|
||||
;; A file on this cluster is also local.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-local-file (concat "/" cluster ":" file)) file))
|
||||
;; A file on the primary host is also local.
|
||||
(should
|
||||
(string-equal (shadow-local-file (concat primary file)) file))
|
||||
|
||||
;; Redefine the cluster.
|
||||
(setq primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
;; The structure of the local file is still the same.
|
||||
(should (equal (shadow-parse-name file) hup))
|
||||
;; The cluster name must be used.
|
||||
(setf (tramp-file-name-host hup) cluster)
|
||||
(should (equal (shadow-parse-name (concat "/" cluster ":" file)) hup))
|
||||
;; The structure of a remote file is different.
|
||||
(should
|
||||
(equal (shadow-parse-name (concat primary file))
|
||||
(tramp-dissect-file-name (concat primary file))))
|
||||
|
||||
;; A local file is still local.
|
||||
(should (shadow-local-file file))
|
||||
;; A file on this cluster is not local.
|
||||
(should-not (shadow-local-file (concat "/" cluster ":" file)))
|
||||
;; A file on the primary host is not local.
|
||||
(should-not (shadow-local-file (concat primary file)))
|
||||
;; There's no error on wrong FILE.
|
||||
(should-not (shadow-local-file nil)))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test03-expand-cluster-in-file-name ()
|
||||
"Check canonical file name of a cluster or site."
|
||||
(skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
|
||||
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters
|
||||
cluster primary regexp file1 file2)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define a cluster.
|
||||
(setq cluster "cluster"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
(setq file1
|
||||
(make-temp-name
|
||||
(expand-file-name "shadowfile-tests" temporary-file-directory))
|
||||
file2
|
||||
(make-temp-name
|
||||
(expand-file-name
|
||||
"shadowfile-tests"
|
||||
shadow-test-remote-temporary-file-directory)))
|
||||
|
||||
;; A local file name is kept.
|
||||
(should
|
||||
(string-equal (shadow-expand-cluster-in-file-name file1) file1))
|
||||
;; A remote file is kept.
|
||||
(should
|
||||
(string-equal (shadow-expand-cluster-in-file-name file2) file2))
|
||||
;; A cluster name is expanded to the primary name.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-expand-cluster-in-file-name (format "/%s:%s" cluster file1))
|
||||
(shadow-expand-cluster-in-file-name (concat primary file1))))
|
||||
;; A primary name is expanded if it is a local file name.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-expand-cluster-in-file-name (concat primary file1)) file1))
|
||||
|
||||
;; Redefine the cluster.
|
||||
(setq primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
;; A cluster name is expanded to the primary name.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-expand-cluster-in-file-name (format "/%s:%s" cluster file1))
|
||||
(shadow-expand-cluster-in-file-name (concat primary file1))))
|
||||
;; A primary name is not expanded if it isn't is a local file name.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-expand-cluster-in-file-name (concat primary file1))
|
||||
(concat primary file1))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test04-contract-file-name ()
|
||||
"Check canonical file name of a cluster or site."
|
||||
(skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
|
||||
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters
|
||||
cluster primary regexp file)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define a cluster.
|
||||
(setq cluster "cluster"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary)
|
||||
file (make-temp-name
|
||||
(expand-file-name
|
||||
"shadowfile-tests" temporary-file-directory)))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
;; The cluster name is prepended for local files.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-contract-file-name file) (concat "/cluster:" file)))
|
||||
;; A cluster file name is preserved.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-contract-file-name (concat "/cluster:" file))
|
||||
(concat "/cluster:" file)))
|
||||
;; `shadow-system-name' is mapped to the cluster.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-contract-file-name (concat shadow-system-name file))
|
||||
(concat "/cluster:" file)))
|
||||
|
||||
;; Redefine the cluster.
|
||||
(setq primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
;; A remote file name is mapped to the cluster.
|
||||
(should
|
||||
(string-equal
|
||||
(shadow-contract-file-name
|
||||
(concat
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory) file))
|
||||
(concat "/cluster:" file))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test05-file-match ()
|
||||
"Check `shadow-same-site' and `shadow-file-match'."
|
||||
(skip-unless (file-remote-p shadow-test-remote-temporary-file-directory))
|
||||
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters
|
||||
cluster primary regexp file)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define a cluster.
|
||||
(setq cluster "cluster"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary)
|
||||
file (make-temp-name
|
||||
(expand-file-name
|
||||
"shadowfile-tests" temporary-file-directory)))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
(should (shadow-same-site (shadow-parse-name "/cluster:") file))
|
||||
(should
|
||||
(shadow-same-site (shadow-parse-name shadow-system-name) file))
|
||||
(should (shadow-same-site (shadow-parse-name file) file))
|
||||
|
||||
(should
|
||||
(shadow-file-match
|
||||
(shadow-parse-name (concat "/cluster:" file)) file))
|
||||
(should
|
||||
(shadow-file-match
|
||||
(shadow-parse-name (concat shadow-system-name file)) file))
|
||||
(should (shadow-file-match (shadow-parse-name file) file))
|
||||
|
||||
;; Redefine the cluster.
|
||||
(setq primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster primary regexp)
|
||||
|
||||
(should
|
||||
(shadow-file-match
|
||||
(shadow-parse-name
|
||||
(concat
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
file))
|
||||
file)))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test06-literal-groups ()
|
||||
"Check literal group definitions."
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters shadow-literal-groups
|
||||
cluster1 cluster2 primary regexp file1 file2 mocked-input)
|
||||
(unwind-protect
|
||||
;; We must mock `read-from-minibuffer' and `read-string', in
|
||||
;; order to avoid interactive arguments.
|
||||
(cl-letf* (((symbol-function 'read-from-minibuffer)
|
||||
(lambda (&rest args) (pop mocked-input)))
|
||||
((symbol-function 'read-string)
|
||||
(lambda (&rest args) (pop mocked-input))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define clusters.
|
||||
(setq cluster1 "cluster1"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster1 primary regexp)
|
||||
|
||||
(setq cluster2 "cluster2"
|
||||
primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (format "^\\(%s\\|%s\\)$" shadow-system-name primary))
|
||||
(shadow-set-cluster cluster2 primary regexp)
|
||||
|
||||
;; Define a literal group.
|
||||
(setq file1
|
||||
(make-temp-name
|
||||
(expand-file-name "shadowfile-tests" temporary-file-directory))
|
||||
file2
|
||||
(make-temp-name
|
||||
(expand-file-name
|
||||
"shadowfile-tests"
|
||||
shadow-test-remote-temporary-file-directory))
|
||||
mocked-input `(,cluster1 ,file1 ,cluster2 ,file2 ,(kbd "RET")))
|
||||
(with-temp-buffer
|
||||
(setq-local buffer-file-name file1)
|
||||
(call-interactively 'shadow-define-literal-group))
|
||||
|
||||
;; `shadow-literal-groups' is a list of lists.
|
||||
(should (consp shadow-literal-groups))
|
||||
(should (consp (car shadow-literal-groups)))
|
||||
(should-not (cdr shadow-literal-groups))
|
||||
|
||||
(should (member (format "/%s:%s" cluster1 (file-local-name file1))
|
||||
(car shadow-literal-groups)))
|
||||
(should (member (format "/%s:%s" cluster2 (file-local-name file2))
|
||||
(car shadow-literal-groups))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test07-regexp-groups ()
|
||||
"Check regexp group definitions."
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
shadow-clusters shadow-regexp-groups
|
||||
cluster1 cluster2 primary regexp file mocked-input)
|
||||
(unwind-protect
|
||||
;; We must mock `read-from-minibuffer' and `read-string', in
|
||||
;; order to avoid interactive arguments.
|
||||
(cl-letf* (((symbol-function 'read-from-minibuffer)
|
||||
(lambda (&rest args) (pop mocked-input)))
|
||||
((symbol-function 'read-string)
|
||||
(lambda (&rest args) (pop mocked-input))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define clusters.
|
||||
(setq cluster1 "cluster1"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster1 primary regexp)
|
||||
|
||||
(setq cluster2 "cluster2"
|
||||
primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (format "^\\(%s\\|%s\\)$" shadow-system-name primary))
|
||||
(shadow-set-cluster cluster2 primary regexp)
|
||||
|
||||
;; Define a regexp group.
|
||||
(setq file
|
||||
(make-temp-name
|
||||
(expand-file-name "shadowfile-tests" temporary-file-directory))
|
||||
mocked-input `(,(shadow-regexp-superquote file)
|
||||
,cluster1 ,cluster2 ,(kbd "RET")))
|
||||
(with-temp-buffer
|
||||
(setq-local buffer-file-name nil)
|
||||
(call-interactively 'shadow-define-regexp-group))
|
||||
|
||||
;; `shadow-regexp-groups' is a list of lists.
|
||||
(should (consp shadow-regexp-groups))
|
||||
(should (consp (car shadow-regexp-groups)))
|
||||
(should-not (cdr shadow-regexp-groups))
|
||||
|
||||
(should
|
||||
(member
|
||||
(concat
|
||||
(shadow-site-primary cluster1) (shadow-regexp-superquote file))
|
||||
(car shadow-regexp-groups)))
|
||||
(should
|
||||
(member
|
||||
(concat
|
||||
(shadow-site-primary cluster2) (shadow-regexp-superquote file))
|
||||
(car shadow-regexp-groups))))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file)))))
|
||||
|
||||
(ert-deftest shadow-test08-shadow-todo ()
|
||||
"Check that needed shadows are added to todo."
|
||||
(let ((backup-inhibited t)
|
||||
(shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
(shadow-inhibit-message t)
|
||||
shadow-clusters shadow-literal-groups shadow-regexp-groups
|
||||
shadow-files-to-copy
|
||||
cluster1 cluster2 primary regexp file)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
|
||||
;; Define clusters.
|
||||
(setq cluster1 "cluster1"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster1 primary regexp)
|
||||
|
||||
(setq cluster2 "cluster2"
|
||||
primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster2 primary regexp)
|
||||
|
||||
;; Define a literal group.
|
||||
(setq file
|
||||
(make-temp-name
|
||||
(expand-file-name "shadowfile-tests" temporary-file-directory))
|
||||
shadow-literal-groups
|
||||
`((,(concat "/cluster1:" file) ,(concat "/cluster2:" file))))
|
||||
|
||||
;; Save file from "cluster1" definition.
|
||||
(with-temp-buffer
|
||||
(setq buffer-file-name file)
|
||||
(insert "foo")
|
||||
(save-buffer))
|
||||
(should
|
||||
(member
|
||||
(cons file (shadow-contract-file-name (concat "/cluster2:" file)))
|
||||
shadow-files-to-copy))
|
||||
|
||||
;; Save file from "cluster2" definition.
|
||||
(with-temp-buffer
|
||||
(setq buffer-file-name (concat (shadow-site-primary cluster2) file))
|
||||
(insert "foo")
|
||||
(save-buffer))
|
||||
(should
|
||||
(member
|
||||
(cons
|
||||
(concat (shadow-site-primary cluster2) file)
|
||||
(shadow-contract-file-name (concat "/cluster1:" file)))
|
||||
shadow-files-to-copy))
|
||||
|
||||
;; Define a regexp group.
|
||||
(setq shadow-files-to-copy nil
|
||||
shadow-regexp-groups
|
||||
`((,(concat (shadow-site-primary cluster1)
|
||||
(shadow-regexp-superquote file))
|
||||
,(concat (shadow-site-primary cluster2)
|
||||
(shadow-regexp-superquote file)))))
|
||||
|
||||
;; Save file from "cluster1" definition.
|
||||
(with-temp-buffer
|
||||
(setq buffer-file-name file)
|
||||
(insert "foo")
|
||||
(save-buffer))
|
||||
(should
|
||||
(member
|
||||
(cons file (shadow-contract-file-name (concat "/cluster2:" file)))
|
||||
shadow-files-to-copy))
|
||||
|
||||
;; Save file from "cluster2" definition.
|
||||
(with-temp-buffer
|
||||
(setq buffer-file-name (concat (shadow-site-primary cluster2) file))
|
||||
(insert "foo")
|
||||
(save-buffer))
|
||||
(should
|
||||
(member
|
||||
(cons
|
||||
(concat (shadow-site-primary cluster2) file)
|
||||
(shadow-contract-file-name (concat "/cluster1:" file)))
|
||||
shadow-files-to-copy)))
|
||||
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
(when (file-exists-p file)
|
||||
(delete-file file))
|
||||
(when (file-exists-p (concat (shadow-site-primary cluster2) file))
|
||||
(delete-file (concat (shadow-site-primary cluster2) file))))))
|
||||
|
||||
(ert-deftest shadow-test09-shadow-copy-files ()
|
||||
"Check that needed shadow files are copied."
|
||||
(let ((backup-inhibited t)
|
||||
(shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file)
|
||||
(shadow-inhibit-message t)
|
||||
(shadow-noquery t)
|
||||
shadow-clusters shadow-files-to-copy
|
||||
cluster1 cluster2 primary regexp file mocked-input)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Cleanup.
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
(when (buffer-live-p shadow-todo-buffer)
|
||||
(with-current-buffer shadow-todo-buffer (erase-buffer)))
|
||||
|
||||
;; Define clusters.
|
||||
(setq cluster1 "cluster1"
|
||||
primary shadow-system-name
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster1 primary regexp)
|
||||
|
||||
(setq cluster2 "cluster2"
|
||||
primary
|
||||
(file-remote-p shadow-test-remote-temporary-file-directory)
|
||||
regexp (shadow-regexp-superquote primary))
|
||||
(shadow-set-cluster cluster2 primary regexp)
|
||||
|
||||
;; Define files to copy.
|
||||
(setq file
|
||||
(make-temp-name
|
||||
(expand-file-name "shadowfile-tests" temporary-file-directory))
|
||||
shadow-literal-groups
|
||||
`((,(concat "/cluster1:" file) ,(concat "/cluster2:" file)))
|
||||
shadow-regexp-groups
|
||||
`((,(concat (shadow-site-primary cluster1)
|
||||
(shadow-regexp-superquote file))
|
||||
,(concat (shadow-site-primary cluster2)
|
||||
(shadow-regexp-superquote file))))
|
||||
mocked-input `(,(concat (shadow-site-primary cluster2) file)
|
||||
,file))
|
||||
|
||||
;; Save files.
|
||||
(with-temp-buffer
|
||||
(setq buffer-file-name file)
|
||||
(insert "foo")
|
||||
(save-buffer))
|
||||
(with-temp-buffer
|
||||
(setq buffer-file-name (concat (shadow-site-primary cluster2) file))
|
||||
(insert "foo")
|
||||
(save-buffer))
|
||||
|
||||
;; We must mock `write-region', in order to check proper
|
||||
;; action.
|
||||
(add-function
|
||||
:before (symbol-function 'write-region)
|
||||
(lambda (&rest args)
|
||||
(when (and (buffer-file-name) mocked-input)
|
||||
(should (equal (buffer-file-name) (pop mocked-input)))))
|
||||
'((name . "write-region-mock")))
|
||||
|
||||
;; Copy the files.
|
||||
(shadow-copy-files 'noquery)
|
||||
(should-not shadow-files-to-copy)
|
||||
(with-current-buffer shadow-todo-buffer
|
||||
(goto-char (point-min))
|
||||
(should
|
||||
(looking-at (regexp-quote "(setq shadow-files-to-copy nil)")))))
|
||||
|
||||
;; Cleanup.
|
||||
(remove-function (symbol-function 'write-region) "write-region-mock")
|
||||
(when (file-exists-p shadow-info-file)
|
||||
(delete-file shadow-info-file))
|
||||
(when (file-exists-p shadow-todo-file)
|
||||
(delete-file shadow-todo-file))
|
||||
(when (file-exists-p file)
|
||||
(delete-file file))
|
||||
(when (file-exists-p (concat (shadow-site-primary cluster2) file))
|
||||
(delete-file (concat (shadow-site-primary cluster2) file))))))
|
||||
|
||||
(defun shadowfile-test-all (&optional interactive)
|
||||
"Run all tests for \\[shadowfile]."
|
||||
(interactive "p")
|
||||
(if interactive
|
||||
(ert-run-tests-interactively "^shadowfile-")
|
||||
(ert-run-tests-batch "^shadowfile-")))
|
||||
|
||||
(let ((shadow-info-file shadow-test-info-file)
|
||||
(shadow-todo-file shadow-test-todo-file))
|
||||
(shadow-initialize))
|
||||
|
||||
(provide 'shadowfile-tests)
|
||||
;;; shadowfile-tests.el ends here
|
Loading…
Add table
Reference in a new issue