;;; file-system-tests.lisp ;;; ;;; Copyright (C) 2005 Peter Graves ;;; ;;; 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 2 ;;; 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, write to the Free Software ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #+sbcl (require '#:sb-posix) (in-package #:abcl.test.lisp) (defparameter *this-file* (merge-pathnames (make-pathname :type "lisp") (if (find :asdf2 *features*) (merge-pathnames (make-pathname :name (pathname-name *load-truename*)) (asdf:system-relative-pathname :abcl-test-lisp "test/lisp/abcl/")) *load-truename*))) (defparameter *this-directory* (if (find :asdf2 *features*) (asdf:system-relative-pathname :abcl-test-lisp "test/lisp/abcl/") (make-pathname :host (pathname-host *load-truename*) :device (pathname-device *load-truename*) :directory (pathname-directory *load-truename*)))) (defun pathnames-equal-p (pathname1 pathname2) #-(or allegro clisp cmu lispworks) (equal pathname1 pathname2) #+(or allegro clisp cmu) (and (pathnamep pathname1) (pathnamep pathname2) (equal (pathname-host pathname1) (pathname-host pathname2)) (equal (pathname-device pathname1) (pathname-device pathname2)) (equal (pathname-directory pathname1) (pathname-directory pathname2)) (equal (pathname-name pathname1) (pathname-name pathname2)) (equal (pathname-type pathname1) (pathname-type pathname2)) (or (equal (pathname-version pathname1) (pathname-version pathname2)) (and (member (pathname-version pathname1) '(:newest nil)) (member (pathname-version pathname2) '(:newest nil)) t))) #+lispworks (string= (namestring pathname1) (namestring pathname2))) #+abcl (defun run-shell-command (command &key directory (output *standard-output*)) (ext:run-shell-command command :directory directory :output output)) #+allegro (defun run-shell-command (command &key directory (output *standard-output*)) (excl:run-shell-command command :directory directory :input nil :output output)) #+clisp (defun run-shell-command (command &key directory (output *standard-output*)) (declare (ignore output)) ;; FIXME (let (status old-directory) (when directory (setf old-directory (ext:cd)) (ext:cd directory)) (unwind-protect (setf status (ext:run-shell-command command)) (when old-directory (ext:cd old-directory))) (cond ((numberp status) status) ((eq status t) 0) (t -1)))) #+cmu (defun run-shell-command (command &key directory (output *standard-output*)) (when directory (setf command (concatenate 'string "\\cd \"" (namestring (pathname directory)) "\" && " command))) (ext:process-exit-code (ext:run-program "/bin/sh" (list "-c" command) :input nil :output output))) #+sbcl (defun run-shell-command (command &key directory (output *standard-output*)) (when directory (setf command (concatenate 'string "\\cd \"" (namestring (pathname directory)) "\" && " command))) (sb-ext:process-exit-code (sb-ext:run-program "/bin/sh" (list "-c" command) :input nil :output output))) #+lispworks (defun run-shell-command (command &key directory (output *standard-output*)) (when directory #+unix (setf command (concatenate 'string "\\cd \"" (namestring (pathname directory)) "\" && " command))) (system:call-system-showing-output command :shell-type "/bin/sh" :output-stream output)) (defun copy-file (from to) (let* ((from-namestring (namestring (pathname from))) (to-namestring (namestring (pathname to))) (command (concatenate 'string "cp " from-namestring " " to-namestring))) (zerop (run-shell-command command)))) (defun make-symbolic-link (from to) (let* ((from-namestring (namestring (pathname from))) (to-namestring (namestring (pathname to))) (command (concatenate 'string "ln -s " from-namestring " " to-namestring))) (zerop (run-shell-command command)))) (defun probe-directory (pathname) #+abcl (ext:probe-directory pathname) #+allegro (excl:probe-directory pathname) #+clisp (ignore-errors (ext:probe-directory pathname)) #+cmu (probe-file pathname) ; FIXME #+sbcl (probe-file pathname) ; FIXME #+lispworks (probe-file pathname) ) (defun file-directory-p (pathname) #+abcl (ext:file-directory-p pathname) #+allegro (excl:file-directory-p pathname) #-(or abcl allegro) (let* ((namestring (namestring pathname)) (len (length namestring)) (last-char (and (> len 0) (char namestring (1- len))))) (eql last-char #+windows #\\ #-windows #\/))) (defun make-directory (pathname) #+allegro (excl:make-directory pathname) #-allegro (and (ensure-directories-exist pathname) t)) (defun delete-directory (pathname) #+abcl (delete-file pathname) #+allegro (excl:delete-directory pathname) #+clisp (ext:delete-dir (namestring pathname)) #+cmu (unix:unix-rmdir (namestring pathname)) #+sbcl (zerop (sb-posix:rmdir (namestring pathname))) #+lispworks (lw:delete-directory pathname) ) ;; This approach is race-prone, but it should be adequate for our limited ;; purposes here. (defun make-temporary-filename (directory) (unless (probe-directory directory) (error "The directory ~S does not exist." directory)) (dotimes (i 10) (let ((pathname (merge-pathnames (make-pathname :name (symbol-name (gensym)) :type nil) directory))) (unless (probe-file pathname) (return-from make-temporary-filename pathname)))) (error "Unable to create a temporary filename in ~S" directory)) (defun touch (filespec) (with-open-file (stream filespec :direction :output :if-exists :error))) (defun make-temporary-directory (parent-directory) (let* ((tmp (make-temporary-filename parent-directory)) (directory-namestring (concatenate 'string (namestring tmp) "/")) (directory-pathname (pathname directory-namestring))) (make-directory directory-pathname) directory-pathname)) (defun delete-directory-and-files (pathspec &key (quiet t) (dry-run nil)) (let* ((namestring (namestring pathspec)) (len (length namestring)) (last-char (and (> len 0) (char namestring (1- len))))) (unless (eql last-char #+windows #\\ #-windows #\/) (setf namestring (concatenate 'string namestring #+windows "\\" #-windows "/"))) (let ((pathname (pathname namestring))) (unless (probe-directory pathname) (error "Directory does not exist: ~S" pathname)) (unless quiet (format t "~&processing directory ~S~%" pathname)) (let ((list (directory (make-pathname :name :wild :type #-clisp :wild #+clisp nil :defaults pathname)))) (dolist (x list) (cond ((file-directory-p x) (delete-directory-and-files x :quiet quiet)) (t (unless quiet (format t "~&deleting file ~S~%" x)) (unless dry-run (delete-file x))))) (unless quiet (format t "~&deleting directory ~S~%" pathname)) (unless dry-run (delete-directory pathname)))))) #-(or allegro clisp lispworks windows) (deftest run-shell-command.1 (let* ((raw-string (with-output-to-string (s) (run-shell-command "pwd" :directory *this-directory* :output s))) (string (string-right-trim '(#\newline #\return) raw-string)) (length (length string))) (when (> length 0) (unless (eql (char string (1- length)) #\/) (setf string (concatenate 'string string (string #\/))))) (string= string (directory-namestring *this-directory*))) t) #-(or allegro clisp lispworks windows) (deftest run-shell-command.2 (let* ((directory (probe-file (merge-pathnames "../" *this-directory*))) (raw-string (with-output-to-string (s) (run-shell-command "pwd" :directory directory :output s))) (string (string-right-trim '(#\newline #\return) raw-string)) (length (length string))) (when (> length 0) (unless (eql (char string (1- length)) #\/) (setf string (concatenate 'string string (string #\/))))) (string= string (directory-namestring directory))) t) (deftest probe-file.1 (pathnames-equal-p (probe-file *this-file*) *this-file*) t) (deftest probe-file.2 (let ((pathname #p".")) #-clisp (pathnames-equal-p (probe-file pathname) (truename pathname)) #+clisp ;; "." names a directory, not a file. (signals-error (probe-file pathname) 'file-error)) t) #+(and clisp windows) (pushnew 'probe-file.2 *expected-failures*) (deftest probe-file.3 (let ((pathname #p"./")) #-clisp (pathnames-equal-p (probe-file pathname) *this-directory*) #+clisp ;; "no file name given" (signals-error (probe-file pathname) 'file-error)) t) (deftest probe-file.4 (let ((pathname #p"..")) #-clisp (pathnames-equal-p (probe-file pathname) (truename pathname)) #+clisp ;; ".." names a directory, not a file. (signals-error (probe-file pathname) 'file-error)) t) #+(and clisp windows) (pushnew 'probe-file.4 *expected-failures*) (deftest probe-file.5 (or ;; It might not exist. That's OK. (null (probe-directory #p"/home/")) (pathnames-equal-p (probe-file #p"/home") (probe-file #p"/home/"))) t) #+(or allegro cmu clisp) (pushnew 'probe-file.5 *expected-failures*) (deftest truename.1 (pathnames-equal-p (truename *this-file*) *this-file*) t) (deftest truename.2 (pathnames-equal-p (truename #p"./") *this-directory*) t) (deftest directory.1 (let ((list (directory *this-file*))) (and (= (length list) 1) (pathnames-equal-p (car list) *this-file*))) t) ;; Verify that DIRECTORY returns nil if the directory is empty. (deftest directory.2 (let ((directory-pathname (make-temporary-directory *this-directory*))) (unwind-protect (directory (make-pathname :name :wild :defaults directory-pathname)) (delete-directory-and-files directory-pathname))) nil) ;; A directory with a one file named "foo". (deftest directory.3 (let ((directory-pathname (make-temporary-directory *this-directory*))) (unwind-protect (let ((file-pathname (make-pathname :name "foo" :defaults directory-pathname))) (touch file-pathname) (let ((directory (directory (make-pathname :name :wild :defaults directory-pathname)))) (and (listp directory) (= (length directory) 1) (pathnames-equal-p (car directory) file-pathname)))) (delete-directory-and-files directory-pathname))) t) ;; Same as DIRECTORY.3, but use :type :wild for the wildcard. (deftest directory.4 (let ((directory-pathname (make-temporary-directory *this-directory*))) (unwind-protect (let ((file-pathname (make-pathname :name "foo" :defaults directory-pathname))) (touch file-pathname) (let ((directory (directory (make-pathname :name :wild :type :wild :defaults directory-pathname)))) (and (listp directory) (= (length directory) 1) (pathnames-equal-p (truename (car directory)) (truename file-pathname))))) (delete-directory-and-files directory-pathname))) t) #+clisp ;; A pathname with type nil does not match a wildcard with type :WILD. (pushnew 'directory.4 *expected-failures*) #-windows (deftest symlink.1 (let* ((tmp1 (make-temporary-filename *this-directory*)) (tmp2 (make-temporary-filename *this-directory*))) (unwind-protect (values (unwind-protect (and ;; Copy this file to tmp1. (copy-file *this-file* tmp1) (pathnames-equal-p (probe-file tmp1) tmp1) ;; Create tmp2 as a symlink to tmp1. (make-symbolic-link tmp1 tmp2) ;; Verify that the symlink exists and points to the copy. (pathnames-equal-p (probe-file tmp2) tmp1) (pathnames-equal-p (truename tmp2) tmp1)) ;; Delete the symlink. (when (probe-file tmp2) (delete-file tmp2))) ;; Copy should still exist after symlink is deleted. (pathnames-equal-p (probe-file tmp1) tmp1)) (when (probe-file tmp1) (delete-file tmp1)))) t t) #+allegro ;; Allegro's PROBE-FILE doesn't follow the symlink. (pushnew 'symlink.1 *expected-failures*) #-windows (deftest symlink.2 (let* ((copy (make-temporary-filename *this-directory*)) (link (make-temporary-filename *this-directory*)) directory) (unwind-protect (and ;; Copy this file to copy. (copy-file *this-file* copy) ;; Verify that copy exists. (pathnames-equal-p (probe-file copy) copy) ;; Create link as a symlink to copy. (make-symbolic-link copy link) ;; Verify that the symlink appears in the directory listing. (setf directory (directory link)) (= (length directory) 1) ;; The directory listing should contain the truename of the symlink. (pathnames-equal-p (car directory) (truename link))) (progn ;; Clean up. (when (probe-file link) (delete-file link)) (when (probe-file copy) (delete-file copy))))) t) #+allegro (pushnew 'symlink.2 *expected-failures*) ;; user-homedir-pathname &optional host => pathname ;; "USER-HOMEDIR-PATHNAME returns a pathname without any name, type, or version ;; component (those components are all nil) for the user's home directory on ;; HOST. If it is impossible to determine the user's home directory on HOST, ;; then nil is returned. USER-HOMEDIR-PATHNAME never returns nil if HOST is not ;; supplied." (deftest user-homedir-pathname.1 (let ((pathname (user-homedir-pathname))) (values (pathnamep pathname) (pathname-name pathname) (pathname-type pathname) (pathname-version pathname))) t nil nil nil) #+allegro ;; Allegro's version component is :UNSPECIFIC. (pushnew 'user-homedir-pathname.1 *expected-failures*) (deftest file-system.directory-namestring.1 (let ((pathname (user-homedir-pathname))) (equal (namestring pathname) (directory-namestring pathname))) #-windows t #+windows ;; The drive prefix ("C:\\") is not part of the directory namestring. nil) #+clisp (pushnew 'file-system.directory-namestring.1 *expected-failures*) (deftest file.system.directory-namestring.2 (let ((pathname (user-homedir-pathname))) (equal (directory-namestring pathname) (namestring (make-pathname :directory (pathname-directory pathname))))) t) #+clisp (pushnew 'file-system.directory-namestring.2 *expected-failures*) (deftest ensure-directories-exist.1 (let* ((tmp (make-temporary-filename *this-directory*)) (directory-namestring (concatenate 'string (namestring tmp) "/")) (file-namestring (concatenate 'string directory-namestring "foo.bar"))) (multiple-value-bind (path created) (ensure-directories-exist file-namestring) (values ;; 1. "The primary value is the given pathspec..." #+(or allegro clisp) (eq path file-namestring) #-(or allegro clisp) (pathnames-equal-p (pathname path) (pathname file-namestring)) ;; 2. Verify that the directory was created. created ;; 3. Verify that the directory exists. #+clisp ;; CLISP's PROBE-DIRECTORY just returns T. (ext:probe-directory directory-namestring) ;; ABCL fills in DEVICE as :UNSPECIFIC when resolving ;; filesystem paths on non-M$DOG #+abcl (not (null (truename directory-namestring))) #-(or clisp abcl) (pathnames-equal-p (probe-file directory-namestring) (pathname directory-namestring)) ;; 4. Delete the directory. (when (probe-directory directory-namestring) (delete-directory directory-namestring)) ;; 5. Verify that the directory is no longer there. (probe-directory directory-namestring)) )) t t t t nil) ;; What happens if you call ENSURE-DIRECTORIES-EXIST with a pathname that has ;; no name, type, or version component? ;; Case 1: the directory in question already exists. (deftest ensure-directories-exist.2 (let ((pathname (make-pathname :host (pathname-host *this-directory*) :device (pathname-device *this-directory*) :directory (pathname-directory *this-directory*) :name nil :type nil :version nil))) (multiple-value-bind (path created) (ensure-directories-exist pathname) (values #+(or allegro clisp) (eq path pathname) #-(or allegro clisp) (pathnames-equal-p (pathname path) (pathname pathname)) created))) t nil) ;; Case 2: the directory in question does not exist. (deftest ensure-directories-exist.3 (let* ((tmp (make-temporary-filename *this-directory*)) (directory-namestring (concatenate 'string (namestring tmp) "/")) (pathname (pathname directory-namestring))) (multiple-value-bind (path created) (ensure-directories-exist pathname) (values #+(or allegro clisp) (eq path pathname) #-(or allegro clisp) (pathnames-equal-p (pathname path) (pathname pathname)) created (not (null (probe-directory directory-namestring))) (when (probe-directory directory-namestring) (delete-directory directory-namestring)) ))) t t t t)