376 lines
14 KiB
Plaintext
Executable File
376 lines
14 KiB
Plaintext
Executable File
#!/bin/sh
|
|
exec scsh -o let-opt -o sort -o srfi-26 -o thread-fluids -e main -s "$0" "$@"
|
|
!#
|
|
|
|
;; Test a scsh package (either stand-alone or composed of two
|
|
;; archives) by checking that is conforms to the rules given in the
|
|
;; proposal.
|
|
|
|
;; $Id: scsh-test-pkg,v 1.1 2004/11/14 14:14:52 michel-schinz Exp $
|
|
|
|
; TODO have a way to give a log file
|
|
|
|
(define valid-extensions '(".tar" ".tar.gz" ".tar.bz2"))
|
|
|
|
;;; Utilities
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (string->version str)
|
|
(map string->number ((infix-splitter ".") str)))
|
|
|
|
(define (version->string v)
|
|
(string-join (map number->string v) "."))
|
|
|
|
(define (permissions->string perms)
|
|
(let ((decode (lambda (mask str)
|
|
(if (zero? (bitwise-and perms mask)) "-" str))))
|
|
(string-append (decode #o400 "r") (decode #o200 "w") (decode #o100 "x")
|
|
(decode #o040 "r") (decode #o020 "w") (decode #o010 "x")
|
|
(decode #o004 "r") (decode #o002 "w") (decode #o001 "x"))))
|
|
|
|
(define (paths->file-name . paths)
|
|
(path-list->file-name paths))
|
|
|
|
(define (delete-directory-and-contents dir)
|
|
(run (rm -rf ,(directory-as-file-name dir))))
|
|
|
|
(define (create-temp-directory)
|
|
(temp-file-iterate (lambda (dir) (create-directory dir) dir)
|
|
"/var/tmp/tmp.~a"))
|
|
|
|
(define (call-with-temp-directory proc)
|
|
(let ((temp-dir (create-temp-directory)))
|
|
(proc temp-dir)
|
|
(delete-directory-and-contents temp-dir)))
|
|
|
|
(define (with-temp-directory* thunk)
|
|
(call-with-temp-directory (lambda (dir) (with-cwd dir (thunk)))))
|
|
|
|
(define-syntax with-temp-directory
|
|
(syntax-rules ()
|
|
((with-temp-directory body ...)
|
|
(with-temp-directory* (lambda () body ...)))))
|
|
|
|
(define-syntax let-temp-directories
|
|
(syntax-rules ()
|
|
((let-temp-directories () body ...)
|
|
(begin body ...))
|
|
((let-temp-directories (i1 i2 ...) body ...)
|
|
(call-with-temp-directory
|
|
(lambda (i1) (let-temp-directories (i2 ...) body ...))))))
|
|
|
|
(define-syntax when
|
|
(syntax-rules ()
|
|
((when cond body1 ...)
|
|
(if cond (begin body1 ...)))))
|
|
|
|
(define-syntax unless
|
|
(syntax-rules ()
|
|
((unless cond body1 ...)
|
|
(if (not cond) (begin body1 ...)))))
|
|
|
|
(define (dir-for-each f dir)
|
|
(for-each (lambda (thing)
|
|
(f thing)
|
|
(if (file-directory? thing #f)
|
|
(dir-for-each f (absolute-file-name thing dir))))
|
|
(directory-files dir)))
|
|
|
|
(define (directory-contents dir)
|
|
(with-cwd dir
|
|
(append-map (lambda (path)
|
|
(if (file-directory? path #f)
|
|
(cons path
|
|
(map (cut absolute-file-name <> path)
|
|
(directory-contents path)))
|
|
(list path)))
|
|
(directory-files))))
|
|
|
|
(define (take-max lst len ellipsis)
|
|
(if (zero? len)
|
|
(if (null? lst) lst (list ellipsis))
|
|
(cons (car lst) (take-max (cdr lst) (- len 1) ellipsis))))
|
|
|
|
;; Package name parsing
|
|
|
|
(define pkg-name-rx
|
|
(rx (w/nocase alphabetic (* (| "-" alphanumeric)))))
|
|
|
|
(define pkg-version-rx
|
|
(rx (+ digit) (* "." (+ digit))))
|
|
|
|
(define pkg-full-name-rx
|
|
(rx (submatch ,pkg-name-rx) "-" (submatch ,pkg-version-rx)))
|
|
|
|
(define (parse-pkg-full-name full-name)
|
|
(let-match (regexp-search pkg-full-name-rx full-name) (#f pkg-name pkg-vers)
|
|
(list pkg-name pkg-vers)))
|
|
|
|
;; Interface with TAR
|
|
|
|
(define (tar-decompress-option archive)
|
|
(let ((archive-ext (string-downcase (file-name-extension archive))))
|
|
(cond ((string=? archive-ext ".gz") '(--gzip))
|
|
((string=? archive-ext ".bz2") '(--bzip2))
|
|
(else '()))))
|
|
|
|
(define (tar-unpack! archive)
|
|
(run (tar --extract ,@(tar-decompress-option archive) --file ,archive)))
|
|
|
|
(define (tar-file-list archive)
|
|
(run/strings (tar --list ,@(tar-decompress-option archive) --file ,archive)))
|
|
|
|
;; Directory comparison
|
|
|
|
(define (sorted-directory-files dir)
|
|
(sort-list (map (cut absolute-file-name <> dir) (directory-files dir))
|
|
string<?))
|
|
|
|
(define (compare-file-kind f1 i1 f2 i2 mismatch-handler)
|
|
(unless (eq? (file-info:type i1) (file-info:type i2))
|
|
(mismatch-handler f1 f2 'type)))
|
|
|
|
(define (compare-file-mode f1 i1 f2 i2 mismatch-handler)
|
|
(unless (= (file-info:mode i1) (file-info:mode i2))
|
|
(mismatch-handler f1 f2 'mode)))
|
|
|
|
(define (compare-file-contents f1 i1 f2 i2 mismatch-handler)
|
|
(unless (and (= (file-info:size i1) (file-info:size i2))
|
|
(zero? (run (diff -q ,f1 ,f2) (> /dev/null))))
|
|
(mismatch-handler f1 f2 'contents)))
|
|
|
|
(define (compare-directories dir1 dir2 mismatch-handler)
|
|
(for-each (lambda (f1 f2)
|
|
(unless (string=? (file-name-nondirectory f1)
|
|
(file-name-nondirectory f2))
|
|
(mismatch-handler f1 f2 'name))
|
|
(let ((i1 (file-info f1 #f)) (i2 (file-info f2 #f)))
|
|
(compare-file-kind f1 i1 f2 i2 mismatch-handler)
|
|
(cond ((file-info-regular? i1)
|
|
(compare-file-mode f1 i1 f2 i2 mismatch-handler)
|
|
(compare-file-contents f1 i1 f2 i2 mismatch-handler))
|
|
((file-info-directory? i1)
|
|
(compare-file-mode f1 i1 f2 i2 mismatch-handler)
|
|
(compare-directories f1 f2 mismatch-handler))
|
|
((file-info-symlink? i1)
|
|
)
|
|
(else
|
|
(mismatch-handler f1 f2 'unknown)))))
|
|
(sorted-directory-files dir1)
|
|
(sorted-directory-files dir2)))
|
|
|
|
(define (get-characteristic file characteristic)
|
|
(case characteristic
|
|
((name)
|
|
(string-append "name = " file))
|
|
((mode)
|
|
(string-append "mode = " (permissions->string (file-mode file))))
|
|
((type)
|
|
(string-append "type = " (file-type file)))
|
|
((contents)
|
|
"contents")
|
|
(else
|
|
"unknown characteristic")))
|
|
|
|
;;; Logging
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (display* . args)
|
|
(for-each (cut display <> (current-output-port)) args))
|
|
|
|
(define (log . args)
|
|
(apply display* args)
|
|
(newline))
|
|
|
|
(define log-info log)
|
|
|
|
(define error-count 0)
|
|
|
|
(define (log-error . args)
|
|
(apply log "Error: " args)
|
|
(set! error-count (+ 1 error-count)))
|
|
|
|
(define (log-check . args)
|
|
(apply log "Checking " args))
|
|
|
|
;;; Tests
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (pkg-full-name name version)
|
|
(string-append name "-" (version->string version)))
|
|
|
|
(define test-layout
|
|
'(lambda (pkg)
|
|
(let ((pkg-full-name (package-full-name pkg)))
|
|
`((base . ,pkg-full-name)
|
|
(misc-shared . ,pkg-full-name)
|
|
(scheme . ,(paths->file-name pkg-full-name "scheme"))
|
|
(lib . ,(paths->file-name pkg-full-name "lib"))
|
|
(doc . ,(paths->file-name pkg-full-name "doc"))
|
|
(active . ".")))))
|
|
|
|
(define (base-location pkg-dir) pkg-dir)
|
|
(define (doc-location pkg-dir) (absolute-file-name "doc" pkg-dir))
|
|
|
|
(define (layout->string layout)
|
|
(call-with-string-output-port (lambda (port) (write layout port))))
|
|
|
|
(define (valid-archive-names base-name)
|
|
(map (cut string-append base-name <>) valid-extensions))
|
|
|
|
(define (check-archive-name base-name archive-name)
|
|
(let ((valid-names (valid-archive-names base-name)))
|
|
(unless (member archive-name valid-names)
|
|
(log-error "invalid archive name "archive-name"\n"
|
|
" should be one of: "(string-join valid-names ", ")))))
|
|
|
|
(define (check-archive-contents name version archive)
|
|
(let ((top-dir-name (pkg-full-name name version))
|
|
(archive-files (map simplify-file-name (tar-file-list archive))))
|
|
(for-each (lambda (req-file)
|
|
(unless (member req-file archive-files)
|
|
(log-error "required file "req-file" not found "
|
|
"in archive")))
|
|
(map (cut absolute-file-name <> top-dir-name)
|
|
'("pkg-def.scm" "README" "COPYING")))
|
|
(for-each
|
|
(lambda (name)
|
|
(let ((dir-name (first (split-file-name name))))
|
|
(unless (string=? dir-name top-dir-name)
|
|
(log-error "archive file "name" not in correct directory"
|
|
"\n(should be: "top-dir-name")"))))
|
|
archive-files)))
|
|
|
|
(define (check-dir-file dir sub-dir file)
|
|
(let ((abs-sub-dir (absolute-file-name sub-dir dir)))
|
|
(when (and (file-exists? abs-sub-dir)
|
|
(file-not-exists? (absolute-file-name file abs-sub-dir)))
|
|
(log-error "directory "abs-sub-dir" exists, but it doesn't contain\n"
|
|
"required file "file))))
|
|
|
|
;; Check that the installed files have the appropriate names.
|
|
(define (check-installation-names pkg-name pkg-root)
|
|
(check-dir-file (base-location pkg-root) "." "load.scm")
|
|
(let ((doc (doc-location pkg-root)))
|
|
(check-dir-file doc "html" "index.html")
|
|
(check-dir-file doc "pdf" (string-append pkg-name ".pdf"))
|
|
(check-dir-file doc "ps" (string-append pkg-name ".ps"))))
|
|
|
|
(define (for-all-contents f dir)
|
|
(for-each (lambda (thing)
|
|
(f thing)
|
|
(if (file-directory? thing)
|
|
(for-all-contents f thing)))
|
|
(map (cut absolute-file-name <> dir) (directory-files dir))))
|
|
|
|
(define (check-installation-protections pkg-root)
|
|
(for-all-contents
|
|
(lambda (file)
|
|
(let* ((info (file-info file))
|
|
(fnd-perms (file-info:mode info))
|
|
(req-perms (cond ((file-info-regular? info) #o444)
|
|
((file-info-directory? info) #o555)
|
|
(else 0))))
|
|
(unless (= req-perms (bitwise-and req-perms fnd-perms))
|
|
(log-error "insufficient permissions for file "file"\n"
|
|
" required: "(permissions->string req-perms)"\n"
|
|
" found: "(permissions->string fnd-perms)))))
|
|
pkg-root))
|
|
|
|
(define (check-installation main-pkg-full-name prefix-dir)
|
|
(with-cwd prefix-dir
|
|
(let ((all-dirs (filter (cut file-directory? <> #f) (directory-files))))
|
|
(unless (member main-pkg-full-name all-dirs)
|
|
(log-error "expected at least one directory called "
|
|
main-pkg-full-name" to be installed\n"
|
|
" only found: "(string-join all-dirs ", ")))
|
|
(for-each (lambda (pkg-dir)
|
|
(let ((abs-pkg-dir (absolute-file-name pkg-dir))
|
|
(pkg-name (first (parse-pkg-full-name pkg-dir))))
|
|
(check-installation-names pkg-name abs-pkg-dir)
|
|
(check-installation-protections abs-pkg-dir)))
|
|
all-dirs))))
|
|
|
|
(define (install-pkg installer pkg-full-name archives prefix options)
|
|
(with-temp-directory
|
|
(for-each tar-unpack! archives)
|
|
(with-cwd pkg-full-name
|
|
(unless (zero? (run (,installer --no-user-defaults
|
|
--layout ,(layout->string test-layout)
|
|
--prefix ,prefix
|
|
,@options)
|
|
(> /dev/null)))
|
|
(log-error "unsuccessful installation")))))
|
|
|
|
(define (log-dest-dir-mismatch file-normal file-dest-dir characteristic)
|
|
(log-error "difference between file installed with and without --dest-dir\n"
|
|
" file: ~a\n"
|
|
" without --dest-dir: ~a\n"
|
|
" with --dest-dir: ~a\n"
|
|
file-normal
|
|
(get-characteristic file-normal characteristic)
|
|
(get-characteristic file-dest-dir characteristic)))
|
|
|
|
(define (test-pkg name version main-archive pkg-archive)
|
|
(let ((full-name (pkg-full-name name version))
|
|
(all-archives (delete #f (list main-archive pkg-archive)))
|
|
(sip (exec-path-search "scsh-install-pkg"
|
|
(thread-fluid exec-path-list))))
|
|
(unless (zero? (run (,sip --version) (> /dev/null)))
|
|
(error "need at least install-lib 1.2.0"))
|
|
(log-info "Using scsh-install-pkg executable: "sip"\n"
|
|
" ("(string-trim-right (run/string (,sip --version)))")")
|
|
(log-check "archive name")
|
|
(check-archive-name full-name (file-name-nondirectory main-archive))
|
|
(if pkg-archive
|
|
(check-archive-name (string-append "pkg_" full-name)
|
|
(file-name-nondirectory pkg-archive)))
|
|
(log-check "archive contents")
|
|
(for-each (cut check-archive-contents name version <>) all-archives)
|
|
(let-temp-directories (ref-dir tst-dir)
|
|
(log-check "basic installation")
|
|
(install-pkg sip full-name all-archives ref-dir '())
|
|
(check-installation full-name ref-dir)
|
|
(log-check "installation with --dest-dir")
|
|
(install-pkg sip full-name all-archives ref-dir `(--dest-dir ,tst-dir))
|
|
(compare-directories ref-dir tst-dir log-dest-dir-mismatch))
|
|
(log-check "installation with --dry-run")
|
|
(let-temp-directories (empty-dir)
|
|
(install-pkg sip full-name all-archives empty-dir `(--dry-run))
|
|
(let ((contents (directory-contents empty-dir)))
|
|
(unless (null? contents)
|
|
(log-error "no files should have been installed, but the "
|
|
"following were:\n "
|
|
(string-join (take-max contents 3 "...") ", ")))))))
|
|
|
|
; (log-check "installation with --non-shared-only")
|
|
; (do-test check-non-shared-installation main-archive pkg-archive))
|
|
|
|
|
|
(define (display-usage-then-exit prog)
|
|
(format #t
|
|
"Usage: ~a <pkg-name> <pkg-version> <main-archive> [<pkg-archive>]\n"
|
|
prog)
|
|
(exit 1))
|
|
|
|
(define (main cmd-line)
|
|
(let ((prog (car cmd-line))
|
|
(args (cdr cmd-line)))
|
|
(if (<= 3 (length args) 4)
|
|
(test-pkg (first args)
|
|
(string->version (second args))
|
|
(absolute-file-name (third args))
|
|
(and (> (length args) 3)
|
|
(absolute-file-name (fourth args))))
|
|
(display-usage-then-exit (file-name-nondirectory prog)))
|
|
(if (zero? error-count)
|
|
(display "Test passed.\n")
|
|
(begin
|
|
(display* "Test failed "
|
|
"("error-count" error"(if (= 1 error-count) "" "s")")\n")
|
|
(exit 1)))))
|
|
|
|
;;; Local Variables:
|
|
;;; mode:scheme
|
|
;;; End:
|