#!/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 /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 []\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: