;;; esh-var-tests.el --- esh-var test suite -*- lexical-binding:t -*- ;; Copyright (C) 2022-2024 Free Software Foundation, Inc. ;; This file is part of GNU Emacs. ;; GNU Emacs 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 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs 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 GNU Emacs. If not, see . ;;; Commentary: ;; Tests for Eshell's variable handling. ;;; Code: (require 'tramp) (require 'ert) (require 'esh-mode) (require 'esh-var) (require 'eshell) (require 'eshell-tests-helpers (expand-file-name "eshell-tests-helpers" (file-name-directory (or load-file-name default-directory)))) (defvar eshell-test-value nil) ;;; Tests: ;; Variable interpolation (ert-deftest esh-var-test/interp-var () "Interpolate variable" (eshell-command-result-equal "echo $user-login-name" user-login-name)) (ert-deftest esh-var-test/interp-quoted-var () "Interpolate quoted variable" (eshell-command-result-equal "echo $'user-login-name'" user-login-name) (eshell-command-result-equal "echo $\"user-login-name\"" user-login-name)) (ert-deftest esh-var-test/interp-quoted-var-concat () "Interpolate and concat quoted variable" (eshell-command-result-equal "echo $'user-login-name'-foo" (concat user-login-name "-foo")) (eshell-command-result-equal "echo $\"user-login-name\"-foo" (concat user-login-name "-foo"))) (ert-deftest esh-var-test/interp-list-var () "Interpolate list variable" (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo $eshell-test-value" '(1 2 3)))) (ert-deftest esh-var-test/interp-list-var-concat () "Interpolate and concat list variable" (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo a$'eshell-test-value'z" '("a1" 2 "3z")))) (defun esh-var-test/interp-var-indices (function &optional range-function) "Test interpolation of an indexable value with indices. FUNCTION is a function that takes a list of elements and returns the object to test. RANGE-FUNCTION is a function that takes a list of elements and returns the expected result of an index range for the object; if nil, use FUNCTION instead." (let ((eshell-test-value (funcall function '("zero" "one" "two" "three" "four"))) (range-function (or range-function function))) ;; Positive indices (eshell-command-result-equal "echo $eshell-test-value[0]" "zero") (eshell-command-result-equal "echo $eshell-test-value[0 2]" '("zero" "two")) (eshell-command-result-equal "echo $eshell-test-value[0 2 4]" '("zero" "two" "four")) ;; Negative indices (eshell-command-result-equal "echo $eshell-test-value[-1]" "four") (eshell-command-result-equal "echo $eshell-test-value[-1 -3]" '("four" "two")) ;; Index ranges (eshell-command-result-equal "echo $eshell-test-value[1..4]" (funcall range-function '("one" "two" "three"))) (eshell-command-result-equal "echo $eshell-test-value[..2]" (funcall range-function '("zero" "one"))) (eshell-command-result-equal "echo $eshell-test-value[-2..]" (funcall range-function '("three" "four"))) (eshell-command-result-equal "echo $eshell-test-value[..]" (funcall range-function '("zero" "one" "two" "three" "four"))) (eshell-command-result-equal "echo $eshell-test-value[1..4 -2..]" (list (funcall range-function '("one" "two" "three")) (funcall range-function '("three" "four")))))) (ert-deftest esh-var-test/interp-var-indices/list () "Interpolate list variable with indices." (esh-var-test/interp-var-indices #'identity)) (ert-deftest esh-var-test/interp-var-indices/vector () "Interpolate vector variable with indices." (esh-var-test/interp-var-indices #'vconcat)) (ert-deftest esh-var-test/interp-var-indices/ring () "Interpolate ring variable with indices." (esh-var-test/interp-var-indices #'ring-convert-sequence-to-ring)) (ert-deftest esh-var-test/interp-var-indices/split () "Interpolate string variable with indices." (esh-var-test/interp-var-indices (lambda (values) (string-join values " ")) #'identity)) (ert-deftest esh-var-test/interp-var-string-split-indices () "Interpolate string variable with string splitter and indices." ;; Test using punctuation as a delimiter. (let ((eshell-test-value "zero:one:two:three:four")) (eshell-command-result-equal "echo $eshell-test-value[: 0]" "zero") (eshell-command-result-equal "echo $eshell-test-value[: 0 2]" '("zero" "two"))) ;; Test using a letter as a delimiter. (let ((eshell-test-value "zeroXoneXtwoXthreeXfour")) (eshell-command-result-equal "echo $eshell-test-value[X 0]" "zero") (eshell-command-result-equal "echo $eshell-test-value[X 0 2]" '("zero" "two"))) ;; Test using a number as a delimiter. (let ((eshell-test-value "zero0one0two0three0four")) (eshell-command-result-equal "echo $eshell-test-value[\"0\" 0]" "zero") (eshell-command-result-equal "echo $eshell-test-value[\"0\" 0 2]" '("zero" "two")))) (ert-deftest esh-var-test/interp-var-regexp-split-indices () "Interpolate string variable with regexp splitter and indices." ;; Test using a regexp as a delimiter. (let ((eshell-test-value "zero:one!two:three!four")) (eshell-command-result-equal "echo $eshell-test-value['[:!]' 0]" "zero") (eshell-command-result-equal "echo $eshell-test-value['[:!]' 0 2]" '("zero" "two")) (eshell-command-result-equal "echo $eshell-test-value[\"[:!]\" 0]" "zero") (eshell-command-result-equal "echo $eshell-test-value[\"[:!]\" 0 2]" '("zero" "two"))) ;; Test using a regexp that looks like range syntax as a delimiter. (let ((eshell-test-value "zero0..0one0..0two0..0three0..0four")) (eshell-command-result-equal "echo $eshell-test-value[\"0..0\" 0]" "zero") (eshell-command-result-equal "echo $eshell-test-value[\"0..0\" 0 2]" '("zero" "two")))) (ert-deftest esh-var-test/interp-var-assoc () "Interpolate alist variable with index." (let ((eshell-test-value '(("foo" . 1) (bar . 2) ("3" . "three")))) (eshell-command-result-equal "echo $eshell-test-value[foo]" 1) (eshell-command-result-equal "echo $eshell-test-value[#'bar]" 2) (eshell-command-result-equal "echo $eshell-test-value[\"3\"]" "three"))) (ert-deftest esh-var-test/interp-var-indices-subcommand () "Interpolate list variable with subcommand expansion for indices." (skip-unless (executable-find "echo")) (let ((eshell-test-value '("zero" "one" "two" "three" "four"))) (eshell-command-result-equal "echo $eshell-test-value[${*echo 0}]" "zero") (eshell-command-result-equal "echo $eshell-test-value[${*echo 0} ${*echo 2}]" '("zero" "two")))) (ert-deftest esh-var-test/interp-var-length-list () "Interpolate length of list variable." (let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9))))) (eshell-command-result-equal "echo $#eshell-test-value" 3) (eshell-command-result-equal "echo $#eshell-test-value[1]" 1) (eshell-command-result-equal "echo $#eshell-test-value[2][1]" 4))) (ert-deftest esh-var-test/interp-var-length-string () "Interpolate length of string variable" (let ((eshell-test-value "foobar")) (eshell-command-result-equal "echo $#eshell-test-value" 6))) (ert-deftest esh-var-test/interp-var-length-alist () "Interpolate length of alist variable." (let ((eshell-test-value '(("foo" . (1 2 3))))) (eshell-command-result-equal "echo $#eshell-test-value" 1) (eshell-command-result-equal "echo $#eshell-test-value[foo]" 3))) (ert-deftest esh-var-test/interp-var-splice () "Splice-interpolate list variable." (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo a $@eshell-test-value z" '("a" 1 2 3 "z")))) (ert-deftest esh-var-test/interp-var-splice-concat () "Splice-interpolate and concat list variable." (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo it is a$@'eshell-test-value'z" '("it" "is" "a1" 2 "3z")) ;; This is a tricky case. We're concatenating a spliced list and ;; a non-spliced list. The general rule is that splicing should ;; work as though the user typed "$X[0] $X[1] ... $X[N]". That ;; means that the last value of our splice should get concatenated ;; into the first value of the non-spliced list. (eshell-command-result-equal "echo it is $@'eshell-test-value'$eshell-test-value" '("it" "is" 1 2 (31 2 3))))) (ert-deftest esh-var-test/interp-lisp () "Interpolate Lisp form evaluation." (eshell-command-result-equal "+ $(+ 1 2) 3" 6)) (ert-deftest esh-var-test/interp-lisp-indices () "Interpolate Lisp form evaluation with index." (eshell-command-result-equal "+ $(list 1 2)[1] 3" 5)) (ert-deftest esh-var-test/interp-cmd () "Interpolate command result." (eshell-command-result-equal "+ ${+ 1 2} 3" 6)) (ert-deftest esh-var-test/interp-cmd-indices () "Interpolate command result with index." (eshell-command-result-equal "+ ${listify 1 2}[1] 3" 5)) (ert-deftest esh-var-test/interp-cmd-external () "Interpolate command result from external command." (skip-unless (executable-find "echo")) (with-temp-eshell (eshell-match-command-output "echo ${*echo hi}" "hi\n"))) (ert-deftest esh-var-test/interp-cmd-external-indices () "Interpolate command result from external command with index." (skip-unless (executable-find "echo")) (with-temp-eshell (eshell-match-command-output "echo ${*echo \"hi\nbye\"}[1]" "bye\n"))) (ert-deftest esh-var-test/interp-temp-cmd () "Interpolate command result redirected to temp file." (eshell-command-result-equal "cat $" "hi")) (ert-deftest esh-var-test/interp-concat-lisp () "Interpolate and concat Lisp form." (eshell-command-result-equal "+ $(+ 1 2)3 3" 36)) (ert-deftest esh-var-test/interp-concat-lisp2 () "Interpolate and concat two Lisp forms." (eshell-command-result-equal "+ $(+ 1 2)$(+ 1 2) 3" 36)) (ert-deftest esh-var-test/interp-concat-cmd () "Interpolate and concat command with literal." (eshell-command-result-equal "+ ${+ 1 2}3 3" 36) (eshell-command-result-equal "echo ${*echo \"foo\nbar\"}-baz" '("foo" "bar-baz")) ;; Concatenating to a number in a list should produce a number... (eshell-command-result-equal "echo ${*echo \"1\n2\"}3" '(1 23)) ;; ... but concatenating to a string that looks like a number in a list ;; should produce a string. (eshell-command-result-equal "echo ${*echo \"hi\n2\"}3" '("hi" "23"))) (ert-deftest esh-var-test/interp-concat-cmd2 () "Interpolate and concat two commands." (eshell-command-result-equal "+ ${+ 1 2}${+ 1 2} 3" 36)) (ert-deftest esh-var-test/interp-concat-cmd-external () "Interpolate command result from external command with concatenation." (skip-unless (executable-find "echo")) (with-temp-eshell (eshell-match-command-output "echo ${echo hi}-${*echo there}" "hi-there\n"))) ;; Quoted variable interpolation (ert-deftest esh-var-test/quoted-interp-var () "Interpolate variable inside double-quotes." (eshell-command-result-equal "echo \"$user-login-name\"" user-login-name)) (ert-deftest esh-var-test/quoted-interp-quoted-var () "Interpolate quoted variable inside double-quotes" (eshell-command-result-equal "echo \"hi, $'user-login-name'\"" (concat "hi, " user-login-name)) (eshell-command-result-equal "echo \"hi, $\\\"user-login-name\\\"\"" (concat "hi, " user-login-name))) (ert-deftest esh-var-test/quoted-interp-list-var () "Interpolate list variable inside double-quotes." (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo \"$eshell-test-value\"" "(1 2 3)"))) (ert-deftest esh-var-test/quoted-interp-list-var-concat () "Interpolate and concat list variable inside double-quotes" (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo \"a$'eshell-test-value'z\"" "a(1 2 3)z"))) (ert-deftest esh-var-test/quoted-interp-var-indices () "Interpolate string variable with indices inside double-quotes." (let ((eshell-test-value '("zero" "one" "two" "three" "four"))) (eshell-command-result-equal "echo \"$eshell-test-value[0]\"" "zero") ;; FIXME: These tests would use the 0th index like the other tests ;; here, but evaluating the command just above adds an `escaped' ;; property to the string "zero". This results in the output ;; printing the string properties, which is probably the wrong ;; behavior. See bug#54486. (eshell-command-result-equal "echo \"$eshell-test-value[1 2]\"" "(\"one\" \"two\")") (eshell-command-result-equal "echo \"$eshell-test-value[1 2 4]\"" "(\"one\" \"two\" \"four\")"))) (ert-deftest esh-var-test/quote-interp-var-indices-subcommand () "Interpolate list variable with subcommand expansion for indices inside double-quotes." (skip-unless (executable-find "echo")) (let ((eshell-test-value '("zero" "one" "two" "three" "four"))) (eshell-command-result-equal "echo \"$eshell-test-value[${*echo 0}]\"" "zero") ;; FIXME: These tests would use the 0th index like the other tests ;; here, but see above. (eshell-command-result-equal "echo \"$eshell-test-value[${*echo 1} ${*echo 2}]\"" "(\"one\" \"two\")"))) (ert-deftest esh-var-test/quoted-interp-var-split-indices () "Interpolate string variable with indices inside double-quotes." (let ((eshell-test-value "zero one two three four")) (eshell-command-result-equal "echo \"$eshell-test-value[0]\"" "zero") (eshell-command-result-equal "echo \"$eshell-test-value[0 2]\"" "(\"zero\" \"two\")"))) (ert-deftest esh-var-test/quoted-interp-var-string-split-indices () "Interpolate string variable with string splitter and indices inside double-quotes." (let ((eshell-test-value "zero:one:two:three:four")) (eshell-command-result-equal "echo \"$eshell-test-value[: 0]\"" "zero") (eshell-command-result-equal "echo \"$eshell-test-value[: 0 2]\"" "(\"zero\" \"two\")")) (let ((eshell-test-value "zeroXoneXtwoXthreeXfour")) (eshell-command-result-equal "echo \"$eshell-test-value[X 0]\"" "zero") (eshell-command-result-equal "echo \"$eshell-test-value[X 0 2]\"" "(\"zero\" \"two\")"))) (ert-deftest esh-var-test/quoted-interp-var-regexp-split-indices () "Interpolate string variable with regexp splitter and indices." (let ((eshell-test-value "zero:one!two:three!four")) (eshell-command-result-equal "echo \"$eshell-test-value['[:!]' 0]\"" "zero") (eshell-command-result-equal "echo \"$eshell-test-value['[:!]' 0 2]\"" "(\"zero\" \"two\")") (eshell-command-result-equal "echo \"$eshell-test-value[\\\"[:!]\\\" 0]\"" "zero") (eshell-command-result-equal "echo \"$eshell-test-value[\\\"[:!]\\\" 0 2]\"" "(\"zero\" \"two\")"))) (ert-deftest esh-var-test/quoted-interp-var-assoc () "Interpolate alist variable with index inside double-quotes." (let ((eshell-test-value '(("foo" . 1) (bar . 2)))) (eshell-command-result-equal "echo \"$eshell-test-value[foo]\"" "1") (eshell-command-result-equal "echo \"$eshell-test-value[#'bar]\"" "2"))) (ert-deftest esh-var-test/quoted-interp-var-length-list () "Interpolate length of list variable inside double-quotes." (let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9))))) (eshell-command-result-equal "echo \"$#eshell-test-value\"" "3") (eshell-command-result-equal "echo \"$#eshell-test-value[1]\"" "1") (eshell-command-result-equal "echo \"$#eshell-test-value[2][1]\"" "4"))) (ert-deftest esh-var-test/quoted-interp-var-length-string () "Interpolate length of string variable inside double-quotes." (let ((eshell-test-value "foobar")) (eshell-command-result-equal "echo \"$#eshell-test-value\"" "6"))) (ert-deftest esh-var-test/quoted-interp-var-length-alist () "Interpolate length of alist variable inside double-quotes." (let ((eshell-test-value '(("foo" . (1 2 3))))) (eshell-command-result-equal "echo \"$#eshell-test-value\"" "1") (eshell-command-result-equal "echo \"$#eshell-test-value[foo]\"" "3"))) (ert-deftest esh-var-test/quoted-interp-var-splice () "Splice-interpolate list variable inside double-quotes." (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo a \"$@eshell-test-value\" z" '("a" "1 2 3" "z")))) (ert-deftest esh-var-test/quoted-interp-var-splice-concat () "Splice-interpolate and concat list variable inside double-quotes" (let ((eshell-test-value '(1 2 3))) (eshell-command-result-equal "echo \"a$@'eshell-test-value'z\"" "a1 2 3z"))) (ert-deftest esh-var-test/quoted-interp-lisp () "Interpolate Lisp form evaluation inside double-quotes." (eshell-command-result-equal "echo \"hi $(concat \\\"the\\\" \\\"re\\\")\"" "hi there")) (ert-deftest esh-var-test/quoted-interp-lisp-indices () "Interpolate Lisp form evaluation with index." (eshell-command-result-equal "funcall concat \"$(list 1 2)[1]\" cool" "2cool")) (ert-deftest esh-var-test/quoted-interp-cmd () "Interpolate command result inside double-quotes." (eshell-command-result-equal "echo \"hi ${echo \\\"there\\\"}\"" "hi there")) (ert-deftest esh-var-test/quoted-interp-cmd-indices () "Interpolate command result with index inside double-quotes." (eshell-command-result-equal "funcall concat \"${listify 1 2}[1]\" cool" "2cool")) (ert-deftest esh-var-test/quoted-interp-temp-cmd () "Interpolate command result redirected to temp file inside double-quotes." (let ((temporary-file-directory (file-name-as-directory (make-temp-file "esh-vars-tests" t)))) (unwind-protect (eshell-command-result-equal "cat \"$\"" "hi") (delete-directory temporary-file-directory t)))) (ert-deftest esh-var-test/quoted-interp-concat-cmd () "Interpolate and concat command with literal." (eshell-command-result-equal "echo \"${echo \\\"foo\nbar\\\"} baz\"" "foo\nbar baz")) ;; Interpolating commands (ert-deftest esh-var-test/command-interp () "Interpolate a variable as a command name." (let ((eshell-test-value "printnl")) (eshell-command-result-equal "$eshell-test-value hello there" "hello\nthere\n"))) (ert-deftest esh-var-test/command-interp-splice () "Interpolate a splice variable as a command name with arguments." (let ((eshell-test-value '("printnl" "hello" "there"))) (eshell-command-result-equal "$@eshell-test-value" "hello\nthere\n"))) ;; Interpolated variable conversion (ert-deftest esh-var-test/interp-convert-var-number () "Interpolate numeric variable." (let ((eshell-test-value 123)) (eshell-command-result-equal "type-of $eshell-test-value" 'integer))) (ert-deftest esh-var-test/interp-convert-var-split-indices () "Interpolate and convert string variable with indices." ;; Check that numeric forms are converted to numbers. (let ((eshell-test-value "000 010 020 030 040")) (eshell-command-result-equal "echo $eshell-test-value[0]" 0) (eshell-command-result-equal "echo $eshell-test-value[0 2]" '(0 20))) ;; Check that multiline forms are preserved as-is. (let ((eshell-test-value "foo\nbar:baz\n")) (eshell-command-result-equal "echo $eshell-test-value[: 0]" "foo\nbar") (eshell-command-result-equal "echo $eshell-test-value[: 1]" "baz\n"))) (ert-deftest esh-var-test/interp-convert-quoted-var-number () "Interpolate numeric quoted numeric variable." (let ((eshell-test-value 123)) (eshell-command-result-equal "funcall type-of $'eshell-test-value'" 'integer) (eshell-command-result-equal "funcall type-of $\"eshell-test-value\"" 'integer))) (ert-deftest esh-var-test/interp-convert-quoted-var-split-indices () "Interpolate and convert quoted string variable with indices." (let ((eshell-test-value "000 010 020 030 040")) (eshell-command-result-equal "echo $'eshell-test-value'[0]" 0) (eshell-command-result-equal "echo $'eshell-test-value'[0 2]" '(0 20)))) (ert-deftest esh-var-test/interp-convert-cmd-string-newline () "Interpolate trailing-newline command result." (eshell-command-result-equal "echo ${echo \"foo\n\"}" "foo")) (ert-deftest esh-var-test/interp-convert-cmd-multiline () "Interpolate multi-line command result." (eshell-command-result-equal "echo ${echo \"foo\nbar\"}" '("foo" "bar")) ;; Numeric output should be converted to numbers... (eshell-command-result-equal "echo ${echo \"01\n02\n03\"}" '(1 2 3)) ;; ... but only if every line is numeric. (eshell-command-result-equal "echo ${echo \"01\n02\nhi\"}" '("01" "02" "hi"))) (ert-deftest esh-var-test/interp-convert-cmd-number () "Interpolate numeric command result." (eshell-command-result-equal "echo ${echo \"1\"}" 1)) (ert-deftest esh-var-test/interp-convert-cmd-split-indices () "Interpolate command result with indices." (eshell-command-result-equal "echo ${echo \"000 010 020\"}[0]" 0) (eshell-command-result-equal "echo ${echo \"000 010 020\"}[0 2]" '(0 20))) (ert-deftest esh-var-test/quoted-interp-convert-var-number () "Interpolate numeric variable inside double-quotes." (let ((eshell-test-value 123)) (eshell-command-result-equal "funcall type-of \"$eshell-test-value\"" 'string))) (ert-deftest esh-var-test/quoted-interp-convert-var-split-indices () "Interpolate string variable with indices inside double-quotes." (let ((eshell-test-value "000 010 020 030 040")) (eshell-command-result-equal "echo \"$eshell-test-value[0]\"" "000") (eshell-command-result-equal "echo \"$eshell-test-value[0 2]\"" "(\"000\" \"020\")"))) (ert-deftest esh-var-test/quoted-interp-convert-quoted-var-number () "Interpolate numeric quoted variable inside double-quotes." (let ((eshell-test-value 123)) (eshell-command-result-equal "funcall type-of \"$'eshell-test-value'\"" 'string) (eshell-command-result-equal "funcall type-of \"$\\\"eshell-test-value\\\"\"" 'string))) (ert-deftest esh-var-test/quoted-interp-convert-quoted-var-split-indices () "Interpolate quoted string variable with indices inside double-quotes." (let ((eshell-test-value "000 010 020 030 040")) (eshell-command-result-equal "echo \"$eshell-test-value[0]\"" "000") (eshell-command-result-equal "echo \"$eshell-test-value[0 2]\"" "(\"000\" \"020\")"))) (ert-deftest esh-var-test/quoted-interp-convert-cmd-string-newline () "Interpolate trailing-newline command result inside double-quotes." (eshell-command-result-equal "echo \"${echo \\\"foo\n\\\"}\"" "foo") (eshell-command-result-equal "echo \"${echo \\\"foo\n\n\\\"}\"" "foo")) (ert-deftest esh-var-test/quoted-interp-convert-cmd-multiline () "Interpolate multi-line command result inside double-quotes." (eshell-command-result-equal "echo \"${echo \\\"foo\nbar\\\"}\"" "foo\nbar")) (ert-deftest esh-var-test/quoted-interp-convert-cmd-number () "Interpolate numeric command result inside double-quotes." (eshell-command-result-equal "echo \"${echo \\\"1\\\"}\"" "1")) (ert-deftest esh-var-test/quoted-interp-convert-cmd-split-indices () "Interpolate command result with indices inside double-quotes." (eshell-command-result-equal "echo \"${echo \\\"000 010 020\\\"}[0]\"" "000")) ;; Variable-related commands (ert-deftest esh-var-test/set/env-var () "Test that `set' with a string variable name sets an environment variable." (with-temp-eshell (eshell-match-command-output "set VAR hello" "hello\n") (should (equal (getenv "VAR") "hello"))) (should-not (equal (getenv "VAR") "hello"))) (ert-deftest esh-var-test/set/symbol () "Test that `set' with a symbol variable name sets a Lisp variable." (let (eshell-test-value) (eshell-command-result-equal "set #'eshell-test-value hello" "hello") (should (equal eshell-test-value "hello")))) (ert-deftest esh-var-test/unset/env-var () "Test that `unset' with a string variable name unsets an env var." (let ((process-environment (cons "VAR=value" process-environment))) (with-temp-eshell (eshell-match-command-output "unset VAR" "\\`\\'") (should (equal (getenv "VAR") nil))) (should (equal (getenv "VAR") "value")))) (ert-deftest esh-var-test/unset/symbol () "Test that `unset' with a symbol variable name unsets a Lisp variable." (let ((eshell-test-value "value")) (eshell-command-result-equal "unset #'eshell-test-value" nil) (should (equal eshell-test-value nil)))) (ert-deftest esh-var-test/setq () "Test that `setq' sets Lisp variables." (let (eshell-test-value) (eshell-command-result-equal "setq eshell-test-value hello" "hello") (should (equal eshell-test-value "hello")))) (ert-deftest esh-var-test/export () "Test that `export' sets environment variables." (with-temp-eshell (eshell-match-command-output "export VAR=hello" "\\`\\'") (should (equal (getenv "VAR") "hello")))) (ert-deftest esh-var-test/local-variables () "Test that \"VAR=value command\" temporarily sets variables." (with-temp-eshell (push "VAR=value" process-environment) (eshell-match-command-output "VAR=hello env" "VAR=hello\n") (should (equal (getenv "VAR") "value")))) (ert-deftest esh-var-test/local-variables/skip-nil () "Test that Eshell skips leading nil arguments after local variable setting." (with-temp-eshell (push "VAR=value" process-environment) (eshell-match-command-output "VAR=hello $eshell-test-value env" "VAR=hello\n") (should (equal (getenv "VAR") "value")))) (ert-deftest esh-var-test/local-variables/cd () "Test that \"VAR=value cd DIR\" properly changes the directory." (let ((parent-directory (file-name-directory (directory-file-name default-directory)))) (with-temp-eshell (eshell-insert-command "VAR=hello cd ..") (should (equal default-directory parent-directory))))) (ert-deftest esh-var-test/local-variables/env () "Test that \"env VAR=value command\" temporarily sets variables." (with-temp-eshell (push "VAR=value" process-environment) (eshell-match-command-output "env VAR=hello env" "VAR=hello\n") (should (equal (getenv "VAR") "value")))) ;; Variable aliases (ert-deftest esh-var-test/alias/function () "Test using a variable alias defined as a function." (let ((text-quoting-style 'grave)) (with-temp-eshell (push `("ALIAS" ,(lambda () "value") nil t) eshell-variable-aliases-list) (eshell-match-command-output "echo $ALIAS" "value\n") (eshell-match-command-output "set ALIAS hello" "Variable `ALIAS' is not settable\n" nil t)))) (ert-deftest esh-var-test/alias/function-pair () "Test using a variable alias defined as a pair of getter/setter functions." (with-temp-eshell (let ((eshell-test-value "value")) (push `("ALIAS" (,(lambda () eshell-test-value) . (lambda (_ value) (setq eshell-test-value (upcase value)))) nil t) eshell-variable-aliases-list) (eshell-match-command-output "echo $ALIAS" "value\n") (eshell-match-command-output "set ALIAS hello" "HELLO\n") (should (equal eshell-test-value "HELLO"))))) (ert-deftest esh-var-test/alias/string () "Test using a variable alias defined as a string. This should get/set the aliased environment variable." (with-temp-eshell (let ((eshell-test-value "lisp-value")) (push "eshell-test-value=env-value" process-environment) (push `("ALIAS" "eshell-test-value") eshell-variable-aliases-list) (eshell-match-command-output "echo $ALIAS" "env-value\n") (eshell-match-command-output "set ALIAS hello" "hello\n") (should (equal (getenv "eshell-test-value") "hello")) (should (equal eshell-test-value "lisp-value"))))) (ert-deftest esh-var-test/alias/string/prefer-lisp () "Test using a variable alias defined as a string. This sets `eshell-prefer-lisp-variables' to t and should get/set the aliased Lisp variable." (with-temp-eshell (let ((eshell-test-value "lisp-value") (eshell-prefer-lisp-variables t)) (push "eshell-test-value=env-value" process-environment) (push `("ALIAS" "eshell-test-value") eshell-variable-aliases-list) (eshell-match-command-output "echo $ALIAS" "lisp-value\n") (eshell-match-command-output "set ALIAS hello" "hello\n") (should (equal (car process-environment) "eshell-test-value=env-value")) (should (equal eshell-test-value "hello"))))) (ert-deftest esh-var-test/alias/symbol () "Test using a variable alias defined as a symbol. This should get/set the value bound to the symbol." (with-temp-eshell (let ((eshell-test-value "value")) (push '("ALIAS" eshell-test-value) eshell-variable-aliases-list) (eshell-match-command-output "echo $ALIAS" "value\n") (eshell-match-command-output "set ALIAS hello" "hello\n") (should (equal eshell-test-value "hello"))))) (ert-deftest esh-var-test/alias/symbol-pair () "Test using a variable alias defined as a pair of symbols. This should get the value bound to the symbol, but fail to set it, since the setter is nil." (with-temp-eshell (let ((eshell-test-value "value") (text-quoting-style 'grave)) (push '("ALIAS" (eshell-test-value . nil)) eshell-variable-aliases-list) (eshell-match-command-output "echo $ALIAS" "value\n") (eshell-match-command-output "set ALIAS hello" "Variable `ALIAS' is not settable\n" nil t)))) (ert-deftest esh-var-test/alias/export () "Test that `export' properly sets variable aliases." (with-temp-eshell (let ((eshell-test-value "value")) (push `("ALIAS" (,(lambda () eshell-test-value) . (lambda (_ value) (setq eshell-test-value value))) nil t) eshell-variable-aliases-list) (eshell-match-command-output "export ALIAS=hello" "\\`\\'") (should (equal eshell-test-value "hello"))))) (ert-deftest esh-var-test/alias/local-variables () "Test that \"VAR=value cmd\" temporarily sets read-only variable aliases." (with-temp-eshell (let ((eshell-test-value "value")) (push `("ALIAS" ,(lambda () eshell-test-value) t t) eshell-variable-aliases-list) (eshell-match-command-output "ALIAS=hello env" "ALIAS=hello\n") (should (equal eshell-test-value "value"))))) ;; Built-in variables (ert-deftest esh-var-test/lines-var () "$LINES should equal (window-body-height nil 'remap)" (eshell-command-result-equal "echo $LINES" (window-body-height nil 'remap))) (ert-deftest esh-var-test/columns-var () "$COLUMNS should equal (window-body-width nil 'remap)." (eshell-command-result-equal "echo $COLUMNS" (window-body-width nil 'remap))) (ert-deftest esh-var-test/inside-emacs-var () "Test presence of \"INSIDE_EMACS\" in subprocesses." (with-temp-eshell (eshell-match-command-output "env" (format "INSIDE_EMACS=%s,eshell" emacs-version)))) (ert-deftest esh-var-test/inside-emacs-var-split-indices () "Test using \"INSIDE_EMACS\" with split indices." (with-temp-eshell (eshell-match-command-output "echo $INSIDE_EMACS[, 1]" "eshell"))) (ert-deftest esh-var-test/pager-var/default () "Test that retrieving the default value of $PAGER works. This should be the value of `comint-pager' if non-nil, otherwise the value of the $PAGER env var." (let ((comint-pager nil) (process-environment (cons "PAGER=cat" process-environment))) (eshell-command-result-equal "echo $PAGER" "cat") (setq comint-pager "less") (eshell-command-result-equal "echo $PAGER" "less"))) (ert-deftest esh-var-test/pager-var/set () "Test that setting $PAGER in Eshell overrides the default value." (let ((comint-pager nil) (process-environment (cons "PAGER=cat" process-environment))) (with-temp-eshell (eshell-match-command-output "set PAGER bat" "bat") (eshell-match-command-output "echo $PAGER" "bat")) (setq comint-pager "less") (with-temp-eshell (eshell-match-command-output "set PAGER bat" "bat") (eshell-match-command-output "echo $PAGER" "bat")))) (ert-deftest esh-var-test/pager-var/unset () "Test that unsetting $PAGER in Eshell overrides the default value." (let ((comint-pager nil) (process-environment (cons "PAGER=cat" process-environment))) (with-temp-eshell (eshell-insert-command "unset PAGER") (eshell-match-command-output "echo $PAGER" "\\`\\'")) (setq comint-pager "less") (with-temp-eshell (eshell-insert-command "unset PAGER") (eshell-match-command-output "echo $PAGER" "\\`\\'")))) (ert-deftest esh-var-test/pager-var/set-locally () "Test setting $PAGER temporarily for a single command." (let ((comint-pager nil) (process-environment (cons "PAGER=cat" process-environment))) (with-temp-eshell (eshell-match-command-output "PAGER=bat env" "PAGER=bat\n") (eshell-match-command-output "echo $PAGER" "cat")) (setq comint-pager "less") (with-temp-eshell (eshell-match-command-output "PAGER=bat env" "PAGER=bat\n") (eshell-match-command-output "echo $PAGER" "less")))) (ert-deftest esh-var-test/path-var/local-directory () "Test using $PATH in a local directory." (let ((expected-path (string-join (eshell-get-path t) (path-separator)))) (with-temp-eshell (eshell-match-command-output "echo $PATH" (regexp-quote expected-path))))) (ert-deftest esh-var-test/path-var/remote-directory () "Test using $PATH in a remote directory." (skip-unless (eshell-tests-remote-accessible-p)) (let* ((default-directory ert-remote-temporary-file-directory) (expected-path (string-join (eshell-get-path t) (path-separator)))) (with-temp-eshell (eshell-match-command-output "echo $PATH" (regexp-quote expected-path))))) (ert-deftest esh-var-test/path-var/set () "Test setting $PATH." (let* ((path-to-set-list '("/some/path" "/other/path")) (path-to-set (string-join path-to-set-list (path-separator)))) (with-temp-eshell ;; Quote PATH value, because on Windows path-separator is ';'. (eshell-match-command-output (concat "set PATH " (eshell-quote-argument path-to-set) "") (concat path-to-set "\n")) (eshell-match-command-output "echo $PATH" (concat path-to-set "\n")) (should (equal (eshell-get-path t) path-to-set-list))))) (ert-deftest esh-var-test/path-var/set-locally () "Test setting $PATH temporarily for a single command." (let* ((path-to-set-list '("/some/path" "/other/path")) (path-to-set (string-join path-to-set-list (path-separator)))) (with-temp-eshell ;; As above, quote PATH value. (eshell-match-command-output (concat "set PATH " (eshell-quote-argument path-to-set) "") (concat path-to-set "\n")) (eshell-match-command-output "PATH=/local/path env" "PATH=/local/path\n") ;; After the last command, the previous $PATH value should be restored. (eshell-match-command-output "echo $PATH" (concat path-to-set "\n")) (should (equal (eshell-get-path t) path-to-set-list))))) (ert-deftest esh-var-test/path-var/preserve-across-hosts () "Test that $PATH can be set independently on multiple hosts." (skip-unless (not (eq system-type 'windows-nt))) (let ((local-directory default-directory) local-path remote-path) (with-temp-eshell ;; Set the $PATH on localhost. (eshell-insert-command "set PATH /local/path") (setq local-path (eshell-last-output)) ;; `cd' to a remote host and set the $PATH there too. (eshell-insert-command (format "cd %s" ert-remote-temporary-file-directory)) (eshell-insert-command "set PATH /remote/path") (setq remote-path (eshell-last-output)) ;; Return to localhost and check that $PATH is the value we set ;; originally. (eshell-insert-command (format "cd %s" local-directory)) (eshell-match-command-output "echo $PATH" (regexp-quote local-path)) ;; ... and do the same for the remote host. (eshell-insert-command (format "cd %s" ert-remote-temporary-file-directory)) (eshell-match-command-output "echo $PATH" (regexp-quote remote-path))))) (ert-deftest esh-var-test/uid-var () "Test that $UID is equivalent to (user-uid) for local directories." (eshell-command-result-equal "echo $UID" (user-uid))) (ert-deftest esh-var-test/gid-var () "Test that $GID is equivalent to (group-gid) for local directories." (eshell-command-result-equal "echo $GID" (group-gid))) (ert-deftest esh-var-test/last-status-var-lisp-command () "Test using the \"last exit status\" ($?) variable with a Lisp command." (with-temp-eshell (eshell-match-command-output "funcall zerop 0; echo $?" "t\n0\n") (eshell-match-command-output "funcall zerop 1; echo $?" "0\n") (eshell-match-command-output "funcall zerop foo; echo $?" "1\n" nil t))) (ert-deftest esh-var-test/last-status-var-lisp-form () "Test using the \"last exit status\" ($?) variable with a Lisp form." (let ((eshell-lisp-form-nil-is-failure t)) (with-temp-eshell (eshell-match-command-output "(zerop 0); echo $?" "t\n0\n") (eshell-match-command-output "(zerop 1); echo $?" "2\n") (eshell-match-command-output "(zerop \"foo\"); echo $?" "1\n" nil t)))) (ert-deftest esh-var-test/last-status-var-lisp-form-2 () "Test using the \"last exit status\" ($?) variable with a Lisp form. This tests when `eshell-lisp-form-nil-is-failure' is nil." (let ((eshell-lisp-form-nil-is-failure nil)) (with-temp-eshell (eshell-match-command-output "(zerop 0); echo $?" "0\n") (eshell-match-command-output "(zerop 0); echo $?" "0\n") (eshell-match-command-output "(zerop \"foo\"); echo $?" "1\n" nil t)))) (ert-deftest esh-var-test/last-status-var-ext-cmd () "Test using the \"last exit status\" ($?) variable with an external command." (skip-unless (executable-find "[")) (with-temp-eshell (eshell-match-command-output "[ foo = foo ]; echo $?" "0\n") (eshell-match-command-output "[ foo = bar ]; echo $?" "1\n"))) (ert-deftest esh-var-test/last-result-var () "Test using the \"last result\" ($$) variable." (with-temp-eshell (eshell-match-command-output "+ 1 2; + $$ 2" "3\n5\n"))) (ert-deftest esh-var-test/last-result-var-twice () "Test using the \"last result\" ($$) variable twice." (with-temp-eshell (eshell-match-command-output "+ 1 2; + $$ $$" "3\n6\n"))) (ert-deftest esh-var-test/last-result-var-ext-cmd () "Test using the \"last result\" ($$) variable with an external command." (skip-unless (executable-find "[")) (with-temp-eshell ;; MS-DOS/MS-Windows have an external command 'format', which we ;; don't want here. (let ((eshell-prefer-lisp-functions t)) (eshell-match-command-output "[ foo = foo ]; format \"%s\" $$" "t\n") (eshell-match-command-output "[ foo = bar ]; format \"%s\" $$" "nil\n")))) (ert-deftest esh-var-test/last-result-var-split-indices () "Test using the \"last result\" ($$) variable with split indices." (with-temp-eshell (eshell-match-command-output "funcall string-join (list \"01\" \"02\") :; + $$[: 1] 3" "01:02\n5\n") (eshell-match-command-output "funcall string-join (list \"01\" \"02\") :; echo \"$$[: 1]\"" "01:02\n02\n"))) (ert-deftest esh-var-test/last-arg-var () "Test using the \"last arg\" ($_) variable." (with-temp-eshell (eshell-match-command-output "+ 1 2; + $_ 4" "3\n6\n"))) (ert-deftest esh-var-test/last-arg-var-indices () "Test using the \"last arg\" ($_) variable with indices." (with-temp-eshell (eshell-match-command-output "+ 1 2; + $_[0] 4" "3\n5\n") (eshell-match-command-output "+ 1 2; + $_[1] 4" "3\n6\n"))) (ert-deftest esh-var-test/last-arg-var-split-indices () "Test using the \"last arg\" ($_) variable with split indices." (with-temp-eshell (eshell-match-command-output "funcall concat 01:02 03:04; + $_[1][: 1] 5" "01:0203:04\n7\n") (eshell-match-command-output "funcall concat 01:02 03:04; echo \"$_[1][: 1]\"" "01:0203:04\n02\n"))) ;; esh-var-tests.el ends here