#lang racket/base (require file/tar file/zip file/untar file/unzip racket/file racket/system racket/set tests/eli-tester) (define (make-file path) (with-output-to-file path (lambda () (for ([i (in-range (random 1000))]) (write-bytes (make-bytes (random 100) (+ 32 (random 96)))))))) (define (file-or-directory-permissions* path permissions) (file-or-directory-permissions path (for/fold ([n 0]) ([p '(["r" #o400] ["w" #o200] ["x" #o100])]) (if (regexp-match? (car p) permissions) (bitwise-ior n (cadr p)) n)))) (define (diff src dest check-attributes?) (define (compare-attributes p1 p2) (or (not check-attributes?) (and (or (and (eq? check-attributes? 'file) (directory-exists? p1)) (= (round-date (file-or-directory-modify-seconds p1)) (round-date (file-or-directory-modify-seconds p2))) (begin (printf "~s ~s ~s\n" p1 (file-or-directory-modify-seconds p1) (file-or-directory-modify-seconds p2)) #f)) (equal? (file-or-directory-permissions p1) (file-or-directory-permissions p2))))) (define (round-date s) (if (eq? check-attributes? 'file) ;; granularity of ".zip" file dates is 2 seconds(!) (if (even? s) s (add1 s)) ; round to future is the default s)) (cond [(link-exists? src) (and (link-exists? dest) (diff (resolve-path src) (resolve-path dest) check-attributes?))] [(file-exists? src) (and (file-exists? dest) (= (file-size src) (file-size dest)) (compare-attributes src dest) (equal? (file->bytes src) (file->bytes dest)))] [(directory-exists? src) (and (directory-exists? dest) (compare-attributes src dest) (let* ([sort-paths (λ (l) (sort l bytesbytes))] [srcs (sort-paths (for/list ([p (in-list (directory-list src))] #:unless (regexp-match? #rx"skip" p)) p))] [dests (sort-paths (directory-list dest))]) (and (equal? srcs dests) (for/and ([src-item (in-list srcs)] [dest-item (in-list dests)]) (diff (build-path src src-item) (build-path dest dest-item) check-attributes?)) ;; make dest writable to simplify clean-up: (begin (file-or-directory-permissions* dest "rwx") #t))))] [else #t])) (define (zip-tests zip unzip timestamps? #:dir-name [ex1 "ex1"] #:file-name [f2 "f2"] #:links? [links? #f] #:filter-path? [filter-path? #f]) (make-directory* ex1) (make-file (build-path ex1 "f1")) (make-file (build-path ex1 f2)) (make-file (build-path ex1 "f3")) (define more-dir (build-path ex1 "more")) (make-directory* more-dir) (make-file (build-path more-dir "f4")) (when links? (make-file-or-directory-link "f1" (build-path ex1 "f1-link")) (make-file-or-directory-link "more" (build-path ex1 "more-link")) (make-file-or-directory-link "no" (build-path ex1 "no-link"))) (when filter-path? (make-file (build-path ex1 "skip1")) (make-directory (build-path ex1 "skip2")) (make-file (build-path ex1 "skip2" "nope"))) (zip "a.zip" ex1) (when timestamps? (sleep 3)) ; at least 2 seconds, plus 1 to likely change parity (make-directory* "sub") (parameterize ([current-directory "sub"]) (unzip "../a.zip")) (unless (diff ex1 (build-path "sub" ex1) timestamps?) (eprintf "changed! ~s\n" zip)) (delete-directory/files "sub") (delete-file "a.zip") (zip "a.zip" #:path-prefix "inside" ex1) (make-directory* "sub") (parameterize ([current-directory "sub"]) (unzip "../a.zip")) (unless (diff ex1 (build-path "sub" "inside" ex1) timestamps?) (eprintf "changed! ~s\n" zip)) (delete-file "a.zip") (delete-directory/files "sub") (delete-directory/files ex1)) (define work-dir (make-temporary-file "packer~a" 'directory)) (define (make-zip utc?) (lambda (#:path-prefix [prefix #f] . args) (apply zip #:path-prefix prefix args #:utc-timestamps? utc?))) (define (make-unzip utc?) (lambda args (apply unzip #:preserve-timestamps? #t #:utc-timestamps? utc? args))) (parameterize ([current-directory work-dir]) (zip-tests zip unzip #f) (zip-tests (make-zip #f) (make-unzip #f) 'file) (zip-tests (make-zip #t) (make-unzip #t) 'file) (zip-tests tar untar #t #:links? (not (eq? 'windows (system-type)))) (zip-tests tar untar #t #:links? (not (eq? 'windows (system-type))) #:dir-name (make-string 64 #\d) #:file-name (make-string 64 #\f)) (zip-tests (lambda (#:path-prefix [prefix #f] . args) (apply tar args #:path-prefix prefix #:path-filter (lambda (p) (define-values (base name dir?) (split-path p)) (not (regexp-match? #rx"skip" name))))) untar #t #:filter-path? #t)) (delete-directory/files work-dir) 'ok