#lang racket/base (require rackunit racket/path racket/file racket/port "../private/coreutils.rkt" "../private/extended.rkt" "../private/dispatcher.rkt" "../private/help.rkt" "../private/racket-tools.rkt" "../private/editor.rkt" "../private/env-support.rkt") (define (capture-output procedure . args) (with-output-to-string (λ () (apply procedure args)))) (define (capture-filter input procedure . args) (parameterize ([current-input-port (open-input-string input)]) (apply capture-output procedure args))) (define test-root (make-temporary-file "rash-coreutils-~a" 'directory)) (dynamic-wind void (λ () (parameterize ([current-directory test-root]) (coreutils-mkdir "a") (check-true (directory-exists? "a")) (coreutils-mkdir "-p" "b/c") (check-true (directory-exists? "b/c")) (coreutils-touch "one.txt") (check-true (file-exists? "one.txt")) (call-with-output-file "one.txt" #:exists 'truncate (λ (out) (display "hello\n" out))) (check-equal? (capture-output coreutils-cat "one.txt") "hello\n") (coreutils-cp "one.txt" "two.txt") (check-true (file-exists? "two.txt")) (coreutils-mv "two.txt" "three.txt") (check-false (file-exists? "two.txt")) (check-true (file-exists? "three.txt")) (check-equal? (capture-output coreutils-echo "hello" "world") "hello world\n") (check-true (regexp-match? #rx"one[.]txt" (capture-output coreutils-ls))) (define long-listing (capture-output coreutils-ls "-l")) (check-true (regexp-match? #rx"[-] +[0-9]+ +one[.]txt" long-listing)) (coreutils-touch ".hidden") (define long-all-listing (capture-output coreutils-ls "-la")) (check-true (regexp-match? #rx"[-] +[0-9]+ +[.]hidden" long-all-listing)) (coreutils-touch "info1.rkt") (coreutils-touch "info22.rkt") (coreutils-touch "other.rkt") (define regexp-arguments (expand-coreutils-arguments (list #px"^info[0-9]+[.]rkt$"))) (check-equal? (sort (map (λ (path) (path->string (file-name-from-path path))) regexp-arguments) stringstring (first edit-call)) "one.txt") (check-true (second edit-call)) (set! edit-call #f) (parameterize ([current-coreutils-editor (λ (filename #:wait? [wait? #f]) (set! edit-call (list filename wait?)))]) (dispatch-coreutils-command 'edit "one.txt")) (check-equal? (path->string (first edit-call)) "one.txt") (check-false (second edit-call)) (check-exn exn:fail? (λ () (parameterize ([current-coreutils-editor (λ (filename #:wait? [wait? #f]) (void))]) (dispatch-coreutils-command 'edit "one.txt" "two.txt")))) (call-with-output-file "lines.txt" #:exists 'truncate (λ (out) (display "one\ntwo\nthree\n" out))) (check-equal? (capture-output coreutils-head "-n" "2" "lines.txt") "one\ntwo\n") (check-equal? (capture-output coreutils-tail "-n" "1" "lines.txt") "three\n") ;; A long physical line must not force head or tail to materialize that ;; line in memory. The 64 KiB internal block size is crossed many times. (define long-lines-path (build-path test-root "long-lines.txt")) (define long-chunk (make-bytes (* 64 1024) 120)) (call-with-output-file long-lines-path #:exists 'truncate (λ (out) (display "first\n" out) (for ([i (in-range 64)]) (write-bytes long-chunk out)) (display "\nlast-one\nlast-two\n" out))) (check-equal? (capture-output coreutils-head "-n" "1" long-lines-path) "first\n") (check-equal? (capture-output coreutils-tail "-n" "2" long-lines-path) "last-one\nlast-two\n") (call-with-input-file long-lines-path (λ (in) (parameterize ([current-input-port in]) (check-equal? (capture-output coreutils-tail "-n" "2") "last-one\nlast-two\n")))) (check-equal? (capture-output coreutils-wc "-l" "lines.txt") "3\n") (check-equal? (capture-output coreutils-basename "a/b/c.txt") "c.txt\n") (check-equal? (capture-filter "abcde\n" coreutils-tr "a-z" "A-Z") "ABCDE\n") (check-equal? (capture-filter "a1b2c3\n" coreutils-tr "-d" "0-9") "abc\n") (check-equal? (capture-filter "a b\n" coreutils-tr "-s" " ") "a b\n") (check-true (regexp-match? #rx"a[/\\]b" (capture-output coreutils-dirname "a/b/c.txt"))) (define env-output (parameterize ([current-coreutils-command-runner (λ (command) (and (equal? command '(printenv RASH_COREUTILS_VALUE)) (begin (coreutils-printenv 'RASH_COREUTILS_VALUE) #t)))]) (capture-output coreutils-env (environment-assignment "RASH_COREUTILS_VALUE" 42) 'printenv 'RASH_COREUTILS_VALUE))) (check-equal? env-output "42\n") (define test-environment (environment-variables-copy (current-environment-variables))) (environment-variables-set! test-environment #"RASH_COREUTILS_TEST" #"before") (parameterize ([current-environment-variables test-environment]) (check-equal? (capture-output coreutils-printenv "RASH_COREUTILS_TEST") "before\n")) (check-true (regexp-match? #px"[0-9]{4}-[0-9]{2}-[0-9]{2}T" (capture-output coreutils-date "--iso"))) ;; time writes measurements to stderr without contaminating command output. (define time-error (open-output-string)) (define timed-output (parameterize ([current-error-port time-error]) (capture-output dispatch-coreutils-command 'time 'echo 'timed))) (check-equal? timed-output "timed\n") (define time-output (get-output-string time-error)) (check-true (regexp-match? #px"^real\\t[0-9]+[.][0-9]{3}s\ncpu\\t[0-9]+[.][0-9]{3}s\ngc\\t[0-9]+[.][0-9]{3}s\n$" time-output)) (check-exn exn:fail? (λ () (expand-coreutils-arguments (list #px"^does-not-exist$")))) (coreutils-rm "three.txt") (check-false (file-exists? "three.txt")) (coreutils-rm "-r" "b") (check-false (directory-exists? "b")) (coreutils-rmdir "a") (check-false (directory-exists? "a")))) (λ () (when (directory-exists? test-root) (delete-directory/files test-root))))