diff options
Diffstat (limited to 'test/lisp/eshell')
-rw-r--r-- | test/lisp/eshell/em-alias-tests.el | 87 | ||||
-rw-r--r-- | test/lisp/eshell/em-basic-tests.el | 71 | ||||
-rw-r--r-- | test/lisp/eshell/em-dirs-tests.el | 102 | ||||
-rw-r--r-- | test/lisp/eshell/em-extpipe-tests.el | 205 | ||||
-rw-r--r-- | test/lisp/eshell/em-glob-tests.el | 197 | ||||
-rw-r--r-- | test/lisp/eshell/em-hist-tests.el | 38 | ||||
-rw-r--r-- | test/lisp/eshell/em-ls-tests.el | 57 | ||||
-rw-r--r-- | test/lisp/eshell/em-pred-tests.el | 566 | ||||
-rw-r--r-- | test/lisp/eshell/em-script-tests.el | 62 | ||||
-rw-r--r-- | test/lisp/eshell/em-tramp-tests.el | 88 | ||||
-rw-r--r-- | test/lisp/eshell/esh-cmd-tests.el | 294 | ||||
-rw-r--r-- | test/lisp/eshell/esh-io-tests.el | 292 | ||||
-rw-r--r-- | test/lisp/eshell/esh-opt-tests.el | 289 | ||||
-rw-r--r-- | test/lisp/eshell/esh-proc-tests.el | 249 | ||||
-rw-r--r-- | test/lisp/eshell/esh-var-tests.el | 569 | ||||
-rw-r--r-- | test/lisp/eshell/eshell-tests-helpers.el | 140 | ||||
-rw-r--r-- | test/lisp/eshell/eshell-tests.el | 234 |
17 files changed, 3370 insertions, 170 deletions
diff --git a/test/lisp/eshell/em-alias-tests.el b/test/lisp/eshell/em-alias-tests.el new file mode 100644 index 00000000000..aca622220e3 --- /dev/null +++ b/test/lisp/eshell/em-alias-tests.el @@ -0,0 +1,87 @@ +;;; em-alias-tests.el --- em-alias test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's alias module. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(require 'eshell) +(require 'em-alias) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) +;;; Tests: + +(ert-deftest em-alias-test/simple-alias () + "Test a simple alias with no arguments" + (with-temp-eshell + (eshell-insert-command "alias say-hi 'echo hi'") + (eshell-match-command-output "say-hi" "hi\n") + (eshell-match-command-output "say-hi bye" "hi\n"))) + +(ert-deftest em-alias-test/alias-arg-vars () + "Test alias with $0, $1, ... variables" + (with-temp-eshell + (eshell-insert-command "alias show-args 'printnl $0 \"$1 $2\"'") + (eshell-match-command-output "show-args one two" "show-args\none two\n"))) + +(ert-deftest em-alias-test/alias-arg-vars-indices () + "Test alias with $1, $2, ... variables using indices" + (with-temp-eshell + (eshell-insert-command "alias funny-sum '+ $1[0] $2[1]'") + (eshell-match-command-output "funny-sum (list 1 2) (list 3 4)" + "5\n"))) + +(ert-deftest em-alias-test/alias-arg-vars-split-indices () + "Test alias with $0, $1, ... variables using split indices" + (with-temp-eshell + (eshell-insert-command "alias my-prefix 'echo $0[- 0]'") + (eshell-match-command-output "my-prefix" + "my\n") + (eshell-insert-command "alias funny-sum '+ $1[: 0] $2[: 1]'") + (eshell-match-command-output "funny-sum 1:2 3:4" + "5\n"))) + +(ert-deftest em-alias-test/alias-all-args-var () + "Test alias with the $* variable" + (with-temp-eshell + (eshell-insert-command "alias show-all-args 'printnl $*'") + (eshell-match-command-output "show-all-args" "\\`\\'") + (eshell-match-command-output "show-all-args a" "a\n") + (eshell-match-command-output "show-all-args a b c" "a\nb\nc\n"))) + +(ert-deftest em-alias-test/alias-all-args-var-indices () + "Test alias with the $* variable using indices" + (with-temp-eshell + (eshell-insert-command "alias add-pair '+ $*[0] $*[1]'") + (eshell-match-command-output "add-pair 1 2" "3\n"))) + +(ert-deftest em-alias-test/alias-all-args-var-split-indices () + "Test alias with the $* variable using split indices" + (with-temp-eshell + (eshell-insert-command "alias add-funny-pair '+ $*[0][: 0] $*[1][: 1]'") + (eshell-match-command-output "add-funny-pair 1:2 3:4" "5\n"))) + +;; em-alias-tests.el ends here diff --git a/test/lisp/eshell/em-basic-tests.el b/test/lisp/eshell/em-basic-tests.el new file mode 100644 index 00000000000..bc8baeaa035 --- /dev/null +++ b/test/lisp/eshell/em-basic-tests.el @@ -0,0 +1,71 @@ +;;; em-basic-tests.el --- em-basic test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for basic Eshell commands. + +;;; Code: + +(require 'ert) +(require 'em-basic) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) + +;;; Tests: + +(ert-deftest em-basic-test/umask-print-numeric () + "Test printing umask numerically." + (cl-letf (((symbol-function 'default-file-modes) (lambda () #o775))) + (eshell-command-result-equal "umask" "002\n")) + (cl-letf (((symbol-function 'default-file-modes) (lambda () #o654))) + (eshell-command-result-equal "umask" "123\n")) + ;; Make sure larger numbers don't cause problems. + (cl-letf (((symbol-function 'default-file-modes) (lambda () #o1775))) + (eshell-command-result-equal "umask" "002\n"))) + +(ert-deftest em-basic-test/umask-read-symbolic () + "Test printing umask symbolically." + (cl-letf (((symbol-function 'default-file-modes) (lambda () #o775))) + (eshell-command-result-equal "umask -S" + "u=rwx,g=rwx,o=rx\n")) + (cl-letf (((symbol-function 'default-file-modes) (lambda () #o654))) + (eshell-command-result-equal "umask -S" + "u=wx,g=rx,o=x\n")) + ;; Make sure larger numbers don't cause problems. + (cl-letf (((symbol-function 'default-file-modes) (lambda () #o1775))) + (eshell-command-result-equal "umask -S" + "u=rwx,g=rwx,o=rx\n"))) + +(ert-deftest em-basic-test/umask-set () + "Test setting umask." + (let ((file-modes 0)) + (cl-letf (((symbol-function 'set-default-file-modes) + (lambda (mode) (setq file-modes mode)))) + (eshell-test-command-result "umask 002") + (should (= file-modes #o775)) + (eshell-test-command-result "umask 123") + (should (= file-modes #o654)) + (eshell-test-command-result "umask $(identity #o222)") + (should (= file-modes #o555))))) + +;; em-basic-tests.el ends here diff --git a/test/lisp/eshell/em-dirs-tests.el b/test/lisp/eshell/em-dirs-tests.el new file mode 100644 index 00000000000..f72d708dcae --- /dev/null +++ b/test/lisp/eshell/em-dirs-tests.el @@ -0,0 +1,102 @@ +;;; em-dirs-tests.el --- em-dirs test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's dirs module. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(require 'eshell) +(require 'em-dirs) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) +;;; Tests: + +(ert-deftest em-dirs-test/pwd-var () + "Test using the $PWD variable." + (let ((default-directory "/some/path")) + (eshell-command-result-equal "echo $PWD" + (expand-file-name default-directory)))) + +(ert-deftest em-dirs-test/pwd-var-indices () + "Test using the $PWD variable with indices." + (let ((default-directory "/some/path/here")) + (eshell-command-result-equal "echo $PWD[/ 1]" + "some") + (eshell-command-result-equal "echo $PWD[/ 1 3]" + '("some" "here")))) + +(ert-deftest em-dirs-test/short-pwd-var () + "Test using the $+ (current directory) variable." + (let ((default-directory "/some/path")) + (eshell-command-result-equal "echo $+" + (expand-file-name default-directory)))) + +(ert-deftest em-dirs-test/oldpwd-var () + "Test using the $OLDPWD variable." + (let (eshell-last-dir-ring-file-name) + (with-temp-eshell + (eshell-match-command-output "echo $OLDPWD" + "\\`\\'") + (ring-insert eshell-last-dir-ring "/some/path") + (eshell-match-command-output "echo $OLDPWD" + "/some/path\n")))) + +(ert-deftest em-dirs-test/oldpwd-var-indices () + "Test using the $OLDPWD variable with indices." + (let (eshell-last-dir-ring-file-name) + (with-temp-eshell + (ring-insert eshell-last-dir-ring "/some/path/here") + (eshell-match-command-output "echo $OLDPWD[/ 1]" + "some\n") + (eshell-match-command-output "echo $OLDPWD[/ 1 3]" + "(\"some\" \"here\")\n")))) + +(ert-deftest em-dirs-test/directory-ring-var () + "Test using the $- (directory ring) variable." + (let (eshell-last-dir-ring-file-name) + (with-temp-eshell + (eshell-match-command-output "echo $-" + "\\`\\'") + (ring-insert eshell-last-dir-ring "/some/path") + (ring-insert eshell-last-dir-ring "/other/path") + (eshell-match-command-output "echo $-" + "/other/path\n") + (eshell-match-command-output "echo $-[0]" + "/other/path\n") + (eshell-match-command-output "echo $-[1]" + "/some/path\n")))) + +(ert-deftest em-dirs-test/directory-ring-var-indices () + "Test using the $- (directory ring) variable with multiple indices." + (let (eshell-last-dir-ring-file-name) + (with-temp-eshell + (ring-insert eshell-last-dir-ring "/some/path/here") + (eshell-match-command-output "echo $-[0][/ 1]" + "some\n") + (eshell-match-command-output "echo $-[1][/ 1 3]" + "(\"some\" \"here\")\n")))) + +;; em-dirs-tests.el ends here diff --git a/test/lisp/eshell/em-extpipe-tests.el b/test/lisp/eshell/em-extpipe-tests.el new file mode 100644 index 00000000000..04e78279427 --- /dev/null +++ b/test/lisp/eshell/em-extpipe-tests.el @@ -0,0 +1,205 @@ +;;; em-extpipe-tests.el --- em-extpipe test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author: Sean Whitton <spwhitton@spwhitton.name> + +;; 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + + +;;; Code: + +(require 'cl-lib) +(require 'ert) +(require 'ert-x) +(require 'em-extpipe) +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) + +(defmacro em-extpipe-tests--deftest (name input &rest body) + (declare (indent 2)) + `(ert-deftest ,name () + (cl-macrolet + ((should-parse (expected) + `(let ((shell-file-name "sh") + (shell-command-switch "-c")) + ;; Strip `eshell-trap-errors'. + (should (equal ,expected + (cadr (eshell-parse-command input)))))) + (with-substitute-for-temp (&rest body) + ;; Substitute name of an actual temporary file and/or + ;; buffer into `input'. The substitution logic is + ;; appropriate for only the use we put it to in this file. + `(ert-with-temp-file temp + (let ((temp-buffer (generate-new-buffer " *temp*" t))) + (unwind-protect + (let ((input + (replace-regexp-in-string + "temp\\([^>]\\|\\'\\)" temp + (string-replace "#<buffer temp>" + (buffer-name temp-buffer) + input)))) + ,@body) + (when (buffer-name temp-buffer) + (kill-buffer temp-buffer)))))) + (temp-should-string= (expected) + `(string= ,expected (string-trim-right + (with-temp-buffer + (insert-file-contents temp) + (buffer-string))))) + (temp-buffer-should-string= (expected) + `(string= ,expected (string-trim-right + (with-current-buffer temp-buffer + (buffer-string)))))) + (skip-unless shell-file-name) + (skip-unless shell-command-switch) + (skip-unless (executable-find shell-file-name)) + (let ((input ,input)) + (with-temp-eshell ,@body))))) + +(em-extpipe-tests--deftest em-extpipe-test-1 + "echo \"bar\" *| rev >temp" + (skip-unless (executable-find "rev")) + (should-parse '(eshell-named-command + "sh" (list "-c" "echo \"bar\" | rev >temp"))) + (with-substitute-for-temp + (eshell-match-command-output input "^$") + (temp-should-string= "rab"))) + +(em-extpipe-tests--deftest em-extpipe-test-2 + "echo \"bar\" | rev *>temp" + (skip-unless (executable-find "rev")) + (should-parse + '(eshell-execute-pipeline + '((eshell-named-command "echo" (list (eshell-escape-arg "bar"))) + (eshell-named-command "sh" (list "-c" "rev >temp"))))) + (with-substitute-for-temp + (eshell-match-command-output input "^$") + (temp-should-string= "rab"))) + +(em-extpipe-tests--deftest em-extpipe-test-3 "foo *| bar | baz -d" + (should-parse + '(eshell-execute-pipeline + '((eshell-named-command "sh" (list "-c" "foo | bar")) + (eshell-named-command "baz" (list "-d")))))) + +(em-extpipe-tests--deftest em-extpipe-test-4 + "echo \"bar\" *| rev >#<buffer temp>" + (skip-unless (executable-find "rev")) + (should-parse + '(progn + (ignore + (eshell-set-output-handle 1 'overwrite + (get-buffer-create "temp"))) + (eshell-named-command "sh" + (list "-c" "echo \"bar\" | rev")))) + (with-substitute-for-temp + (eshell-match-command-output input "^$") + (temp-buffer-should-string= "rab"))) + +(em-extpipe-tests--deftest em-extpipe-test-5 + "foo *| bar >#<buffer quux> baz" + (should-parse '(eshell-named-command + "sh" (list "-c" "foo | bar >#<buffer quux> baz")))) + +(em-extpipe-tests--deftest em-extpipe-test-6 + "foo >#<buffer quux> *| bar baz" + (should-parse '(eshell-named-command + "sh" (list "-c" "foo >#<buffer quux> | bar baz")))) + +(em-extpipe-tests--deftest em-extpipe-test-7 + "foo *| bar >#<buffer quux> >>#<process other>" + (should-parse + '(progn + (ignore + (eshell-set-output-handle 1 'overwrite + (get-buffer-create "quux"))) + (ignore + (eshell-set-output-handle 1 'append + (get-process "other"))) + (eshell-named-command "sh" + (list "-c" "foo | bar"))))) + +(em-extpipe-tests--deftest em-extpipe-test-8 + "foo *| bar >/dev/kill | baz" + (should-parse + '(eshell-execute-pipeline + '((progn + (ignore + (eshell-set-output-handle 1 'overwrite "/dev/kill")) + (eshell-named-command "sh" + (list "-c" "foo | bar"))) + (eshell-named-command "baz"))))) + +(em-extpipe-tests--deftest em-extpipe-test-9 "foo \\*| bar" + (should-parse + '(eshell-execute-pipeline + '((eshell-named-command "foo" + (list (eshell-escape-arg "*"))) + (eshell-named-command "bar"))))) + +(em-extpipe-tests--deftest em-extpipe-test-10 "foo \"*|\" *>bar" + (should-parse + '(eshell-named-command "sh" (list "-c" "foo \"*|\" >bar")))) + +(em-extpipe-tests--deftest em-extpipe-test-11 "foo '*|' bar" + (should-parse '(eshell-named-command + "foo" (list (eshell-escape-arg "*|") "bar")))) + +(em-extpipe-tests--deftest em-extpipe-test-12 ">foo bar *| baz" + (should-parse + '(eshell-named-command "sh" (list "-c" ">foo bar | baz")))) + +(em-extpipe-tests--deftest em-extpipe-test-13 "foo*|bar" + (should-parse '(eshell-execute-pipeline + '((eshell-named-command (eshell-concat nil "foo" "*")) + (eshell-named-command "bar"))))) + +(em-extpipe-tests--deftest em-extpipe-test-14 "tac *<temp" + (skip-unless (executable-find "tac")) + (should-parse '(eshell-named-command "sh" (list "-c" "tac <temp"))) + (with-substitute-for-temp + (with-temp-buffer (insert "bar\nbaz\n") (write-file temp)) + (eshell-match-command-output input "baz\nbar"))) + +(em-extpipe-tests--deftest em-extpipe-test-15 "echo \"bar\" *| cat" + (skip-unless (executable-find "cat")) + (should-parse + '(eshell-named-command "sh" (list "-c" "echo \"bar\" | cat"))) + (cl-letf (((symbol-function 'eshell/cat) + (lambda (&rest _args) (eshell-print "nonsense")))) + (eshell-match-command-output input "bar") + (eshell-match-command-output "echo \"bar\" | cat" "nonsense"))) + +(em-extpipe-tests--deftest em-extpipe-test-16 "echo \"bar\" *| rev" + (skip-unless (executable-find "rev")) + (should-parse + '(eshell-named-command "sh" (list "-c" "echo \"bar\" | rev"))) + (let ((eshell-prefer-lisp-functions t)) + (cl-letf (((symbol-function 'rev) + (lambda (&rest _args) (eshell-print "nonsense")))) + (eshell-match-command-output input "rab") + (eshell-match-command-output "echo \"bar\" | rev" "nonsense")))) + +;; Confirm we don't break input of sharp-quoted symbols (Bug#53518). +(em-extpipe-tests--deftest em-extpipe-test-17 "funcall #'upcase foo" + (eshell-match-command-output input "FOO")) + +;;; em-extpipe-tests.el ends here diff --git a/test/lisp/eshell/em-glob-tests.el b/test/lisp/eshell/em-glob-tests.el new file mode 100644 index 00000000000..b733be35d9a --- /dev/null +++ b/test/lisp/eshell/em-glob-tests.el @@ -0,0 +1,197 @@ +;;; em-glob-tests.el --- em-glob test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's glob expansion. + +;;; Code: + +(require 'ert) +(require 'em-glob) + +(defmacro with-fake-files (files &rest body) + "Evaluate BODY forms, pretending that FILES exist on the filesystem. +FILES is a list of file names that should be reported as +appropriate by `file-name-all-completions'. Any file name +component ending in \"symlink\" is treated as a symbolic link." + (declare (indent 1)) + `(cl-letf (((symbol-function 'file-name-all-completions) + (lambda (file directory) + (cl-assert (string= file "")) + (setq directory (expand-file-name directory)) + `("./" "../" + ,@(delete-dups + (remq nil + (mapcar + (lambda (file) + (setq file (expand-file-name file)) + (when (string-prefix-p directory file) + (replace-regexp-in-string + "/.*" "/" + (substring file (length directory))))) + ,files)))))) + ((symbol-function 'file-symlink-p) + (lambda (file) + (string-suffix-p "symlink" file)))) + ,@body)) + +;;; Tests: + +(ert-deftest em-glob-test/match-any-string () + "Test that \"*\" pattern matches any string." + (with-fake-files '("a.el" "b.el" "c.txt" "dir/a.el") + (should (equal (eshell-extended-glob "*.el") + '("a.el" "b.el"))))) + +(ert-deftest em-glob-test/match-any-directory () + "Test that \"*/\" pattern matches any directory." + (with-fake-files '("a.el" "b.el" "dir/a.el" "dir/sub/a.el" "symlink/") + (should (equal (eshell-extended-glob "*/") + '("dir/" "symlink/"))))) + +(ert-deftest em-glob-test/match-any-character () + "Test that \"?\" pattern matches any character." + (with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el") + (should (equal (eshell-extended-glob "?.el") + '("a.el" "b.el"))))) + +(ert-deftest em-glob-test/match-recursive () + "Test that \"**/\" recursively matches directories." + (with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el" "dir/sub/a.el" + "dir/symlink/a.el" "symlink/a.el" "symlink/sub/a.el") + (should (equal (eshell-extended-glob "**/a.el") + '("a.el" "dir/a.el" "dir/sub/a.el"))) + (should (equal (eshell-extended-glob "**/") + '("dir/" "dir/sub/"))))) + +(ert-deftest em-glob-test/match-recursive-follow-symlinks () + "Test that \"***/\" recursively matches directories, following symlinks." + (with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el" "dir/sub/a.el" + "dir/symlink/a.el" "symlink/a.el" "symlink/sub/a.el") + (should (equal (eshell-extended-glob "***/a.el") + '("a.el" "dir/a.el" "dir/sub/a.el" "dir/symlink/a.el" + "symlink/a.el" "symlink/sub/a.el"))) + (should (equal (eshell-extended-glob "***/") + '("dir/" "dir/sub/" "dir/symlink/" "symlink/" + "symlink/sub/"))))) + +(ert-deftest em-glob-test/match-recursive-mixed () + "Test combination of \"**/\" and \"***/\"." + (with-fake-files '("dir/a.el" "dir/sub/a.el" "dir/sub2/a.el" + "dir/symlink/a.el" "dir/sub/symlink/a.el" "symlink/a.el" + "symlink/sub/a.el" "symlink/sub/symlink/a.el") + (should (equal (eshell-extended-glob "**/sub/***/a.el") + '("dir/sub/a.el" "dir/sub/symlink/a.el"))) + (should (equal (eshell-extended-glob "***/sub/**/a.el") + '("dir/sub/a.el" "symlink/sub/a.el"))))) + +(ert-deftest em-glob-test/match-character-set-individual () + "Test \"[...]\" for individual characters." + (with-fake-files '("a.el" "b.el" "c.el" "d.el" "dir/a.el") + (should (equal (eshell-extended-glob "[ab].el") + '("a.el" "b.el"))) + (should (equal (eshell-extended-glob "[^ab].el") + '("c.el" "d.el"))))) + +(ert-deftest em-glob-test/match-character-set-range () + "Test \"[...]\" for character ranges." + (with-fake-files '("a.el" "b.el" "c.el" "d.el" "dir/a.el") + (should (equal (eshell-extended-glob "[a-c].el") + '("a.el" "b.el" "c.el"))) + (should (equal (eshell-extended-glob "[^a-c].el") + '("d.el"))))) + +(ert-deftest em-glob-test/match-character-set-class () + "Test \"[...]\" for character classes." + (with-fake-files '("1.el" "a.el" "b.el" "c.el" "dir/a.el") + (should (equal (eshell-extended-glob "[[:alpha:]].el") + '("a.el" "b.el" "c.el"))) + (should (equal (eshell-extended-glob "[^[:alpha:]].el") + '("1.el"))))) + +(ert-deftest em-glob-test/match-character-set-mixed () + "Test \"[...]\" with multiple kinds of members at once." + (with-fake-files '("1.el" "a.el" "b.el" "c.el" "d.el" "dir/a.el") + (should (equal (eshell-extended-glob "[ac-d[:digit:]].el") + '("1.el" "a.el" "c.el" "d.el"))) + (should (equal (eshell-extended-glob "[^ac-d[:digit:]].el") + '("b.el"))))) + +(ert-deftest em-glob-test/match-group-alternative () + "Test \"(x|y)\" matches either \"x\" or \"y\"." + (with-fake-files '("em-alias.el" "em-banner.el" "esh-arg.el" "misc.el" + "test/em-xtra.el") + (should (equal (eshell-extended-glob "e(m|sh)-*.el") + '("em-alias.el" "em-banner.el" "esh-arg.el"))))) + +(ert-deftest em-glob-test/match-n-or-more-characters () + "Test that \"x#\" and \"x#\" match zero or more instances of \"x\"." + (with-fake-files '("h.el" "ha.el" "hi.el" "hii.el" "dir/hi.el") + (should (equal (eshell-extended-glob "hi#.el") + '("h.el" "hi.el" "hii.el"))) + (should (equal (eshell-extended-glob "hi##.el") + '("hi.el" "hii.el"))))) + +(ert-deftest em-glob-test/match-n-or-more-groups () + "Test that \"(x)#\" and \"(x)#\" match zero or more instances of \"(x)\"." + (with-fake-files '("h.el" "ha.el" "hi.el" "hii.el" "dir/hi.el") + (should (equal (eshell-extended-glob "hi#.el") + '("h.el" "hi.el" "hii.el"))) + (should (equal (eshell-extended-glob "hi##.el") + '("hi.el" "hii.el"))))) + +(ert-deftest em-glob-test/match-n-or-more-character-sets () + "Test that \"[x]#\" and \"[x]#\" match zero or more instances of \"[x]\"." + (with-fake-files '("w.el" "wh.el" "wha.el" "whi.el" "whaha.el" "dir/wha.el") + (should (equal (eshell-extended-glob "w[ah]#.el") + '("w.el" "wh.el" "wha.el" "whaha.el"))) + (should (equal (eshell-extended-glob "w[ah]##.el") + '("wh.el" "wha.el" "whaha.el"))))) + +(ert-deftest em-glob-test/match-x-but-not-y () + "Test that \"x~y\" matches \"x\" but not \"y\"." + (with-fake-files '("1" "12" "123" "42" "dir/1") + (should (equal (eshell-extended-glob "[[:digit:]]##~4?") + '("1" "12" "123"))))) + +(ert-deftest em-glob-test/match-dot-files () + "Test that dot files are matched correctly." + (with-fake-files '("foo.el" ".emacs") + (should (equal (eshell-extended-glob ".*") + '("../" "./" ".emacs"))) + (let (eshell-glob-include-dot-dot) + (should (equal (eshell-extended-glob ".*") + '(".emacs")))) + (let ((eshell-glob-include-dot-files t)) + (should (equal (eshell-extended-glob "*") + '("../" "./" ".emacs" "foo.el"))) + (let (eshell-glob-include-dot-dot) + (should (equal (eshell-extended-glob "*") + '(".emacs" "foo.el"))))))) + +(ert-deftest em-glob-test/no-matches () + "Test behavior when a glob fails to match any files." + (with-fake-files '("foo.el" "bar.el") + (should (equal (eshell-extended-glob "*.txt") + "*.txt")) + (let ((eshell-error-if-no-glob t)) + (should-error (eshell-extended-glob "*.txt"))))) + +;; em-glob-tests.el ends here diff --git a/test/lisp/eshell/em-hist-tests.el b/test/lisp/eshell/em-hist-tests.el new file mode 100644 index 00000000000..634e9819839 --- /dev/null +++ b/test/lisp/eshell/em-hist-tests.el @@ -0,0 +1,38 @@ +;;; em-hist-tests.el --- em-hist test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2017-2022 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 <https://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'ert-x) +(require 'em-hist) + +(ert-deftest eshell-write-readonly-history () + "Test that having read-only strings in history is okay." + (ert-with-temp-file histfile + (let ((eshell-history-ring (make-ring 2))) + (ring-insert eshell-history-ring + (propertize "echo foo" 'read-only t)) + (ring-insert eshell-history-ring + (propertize "echo bar" 'read-only t)) + (eshell-write-history histfile)))) + +(provide 'em-hist-test) + +;;; em-hist-tests.el ends here diff --git a/test/lisp/eshell/em-ls-tests.el b/test/lisp/eshell/em-ls-tests.el index 35d6171400f..272280e81c7 100644 --- a/test/lisp/eshell/em-ls-tests.el +++ b/test/lisp/eshell/em-ls-tests.el @@ -1,6 +1,6 @@ -;;; tests/em-ls-tests.el --- em-ls test suite +;;; em-ls-tests.el --- em-ls test suite -*- lexical-binding:t -*- -;; Copyright (C) 2017 Free Software Foundation, Inc. +;; Copyright (C) 2017-2022 Free Software Foundation, Inc. ;; Author: Tino Calancha <tino.calancha@gmail.com> @@ -25,29 +25,30 @@ ;;; Code: (require 'ert) +(require 'ert-x) (require 'em-ls) +(require 'dired) (ert-deftest em-ls-test-bug27631 () "Test for https://debbugs.gnu.org/27631 ." - (let* ((dir (make-temp-file "bug27631" 'dir)) - (dir1 (expand-file-name "dir1" dir)) - (dir2 (expand-file-name "dir2" dir)) - (default-directory dir) - (orig eshell-ls-use-in-dired) - buf) - (unwind-protect - (progn - (customize-set-value 'eshell-ls-use-in-dired t) - (make-directory dir1) - (make-directory dir2) - (with-temp-file (expand-file-name "a.txt" dir1)) - (with-temp-file (expand-file-name "b.txt" dir2)) - (setq buf (dired (expand-file-name "dir*/*.txt" dir))) - (dired-toggle-marks) - (should (cdr (dired-get-marked-files)))) - (customize-set-variable 'eshell-ls-use-in-dired orig) - (delete-directory dir 'recursive) - (when (buffer-live-p buf) (kill-buffer buf))))) + (ert-with-temp-directory dir + (let* ((dir1 (expand-file-name "dir1" dir)) + (dir2 (expand-file-name "dir2" dir)) + (default-directory dir) + (orig eshell-ls-use-in-dired) + buf) + (unwind-protect + (progn + (customize-set-value 'eshell-ls-use-in-dired t) + (make-directory dir1) + (make-directory dir2) + (with-temp-file (expand-file-name "a.txt" dir1)) + (with-temp-file (expand-file-name "b.txt" dir2)) + (setq buf (dired (expand-file-name "dir*/*.txt" dir))) + (dired-toggle-marks) + (should (cdr (dired-get-marked-files)))) + (customize-set-variable 'eshell-ls-use-in-dired orig) + (when (buffer-live-p buf) (kill-buffer buf)))))) (ert-deftest em-ls-test-bug27817 () "Test for https://debbugs.gnu.org/27817 ." @@ -77,6 +78,11 @@ (ert-deftest em-ls-test-bug27844 () "Test for https://debbugs.gnu.org/27844 ." + ;; FIXME: it would be better to use something other than source-directory + ;; in this test. + (skip-unless (and source-directory + (file-exists-p + (expand-file-name "lisp/subr.el" source-directory)))) (let ((orig eshell-ls-use-in-dired) (dired-use-ls-dired 'unspecified) buf insert-directory-program) @@ -87,7 +93,14 @@ (dired-toggle-marks) (should (cdr (dired-get-marked-files))) (kill-buffer buf) - (setq buf (dired (expand-file-name "lisp/subr.el" source-directory))) + ;; Eshell's default format duplicates the year for non-recent files, + ;; eg "2015-05-06 2015", which doesn't make a lot of sense, + ;; and causes this portion of the test to fail if subr.el + ;; is non-recent (eg if building from a tarfile unpacked + ;; with a fixed early timestamp for reproducibility). Bug#33734. + (let ((eshell-ls-date-format "%b %e")) + (setq buf (dired (expand-file-name "lisp/subr.el" + source-directory)))) (should (looking-at "subr\\.el"))) (customize-set-variable 'eshell-ls-use-in-dired orig) (and (buffer-live-p buf) (kill-buffer))))) diff --git a/test/lisp/eshell/em-pred-tests.el b/test/lisp/eshell/em-pred-tests.el new file mode 100644 index 00000000000..0d6351ec826 --- /dev/null +++ b/test/lisp/eshell/em-pred-tests.el @@ -0,0 +1,566 @@ +;;; em-pred-tests.el --- em-pred test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's argument predicates/modifiers. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(require 'eshell) +(require 'em-glob) +(require 'em-pred) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) + +(defvar eshell-test-value nil) + +(defun eshell-eval-predicate (initial-value predicate) + "Evaluate PREDICATE on INITIAL-VALUE, returning the result. +PREDICATE is an Eshell argument predicate/modifier." + (let ((eshell-test-value initial-value)) + (ignore-errors + (eshell-test-command-result + (format "echo $eshell-test-value(%s)" predicate))))) + +(defun eshell-parse-file-name-attributes (file) + "Parse a fake FILE name to determine its attributes. +Fake file names are file names beginning with \"/fake/\". This +allows defining file names for fake files with various properties +to query via predicates. Attributes are written as a +comma-separate list of ATTR=VALUE pairs as the file's base name, +like: + + /fake/type=-,modes=0755.el + +The following attributes are recognized: + + * \"type\": A single character describing the file type; + accepts the same values as the first character of the file + modes in `ls -l'. + * \"modes\": The file's permission modes, in octal. + * \"links\": The number of links to this file. + * \"uid\": The UID of the file's owner. + * \"gid\": The UID of the file's group. + * \"atime\": The time the file was last accessed, in seconds + since the UNIX epoch. + * \"mtime\": As \"atime\", but for modification time. + * \"ctime\": As \"atime\", but for inode change time. + * \"size\": The file's size in bytes." + (mapcar (lambda (i) + (pcase (split-string i "=") + (`("modes" ,modes) + (cons 'modes (string-to-number modes 8))) + (`(,(and (or "links" "uid" "gid" "size") key) ,value) + (cons (intern key) (string-to-number value))) + (`(,(and (or "atime" "mtime" "ctime") key) ,value) + (cons (intern key) (time-convert (string-to-number value) t))) + (`(,key ,value) + (cons (intern key) value)) + (_ (error "invalid format %S" i)))) + (split-string (file-name-base file) ","))) + +(defmacro eshell-partial-let-func (overrides &rest body) + "Temporarily bind to FUNCTION-NAMEs and evaluate BODY. +This is roughly analogous to advising functions, but only does so +while BODY is executing, and only calls NEW-FUNCTION if its first +argument is a string beginning with \"/fake/\". + +This allows selectively overriding functions to test file +properties with fake files without altering the functions' +behavior for real files. + +\(fn ((FUNCTION-NAME NEW-FUNCTION) ...) BODY...)" + (declare (indent 1)) + `(cl-letf + ,(mapcar + (lambda (override) + `((symbol-function #',(car override)) + (let ((orig-function (symbol-function #',(car override)))) + (lambda (file &rest rest) + (apply + (if (and (stringp file) (string-prefix-p "/fake/" file)) + ,(cadr override) + orig-function) + file rest))))) + overrides) + ,@body)) + +(defmacro eshell-with-file-attributes-from-name (&rest body) + "Temporarily override file attribute functions and evaluate BODY." + (declare (indent 0)) + `(eshell-partial-let-func + ((file-attributes + (lambda (file &optional _id-format) + (let ((attrs (eshell-parse-file-name-attributes file))) + (list (equal (alist-get 'type attrs) "d") + (or (alist-get 'links attrs) 1) + (or (alist-get 'uid attrs) 0) + (or (alist-get 'gid attrs) 0) + (or (alist-get 'atime attrs) nil) + (or (alist-get 'mtime attrs) nil) + (or (alist-get 'ctime attrs) nil) + (or (alist-get 'size attrs) 0) + (format "%s---------" (or (alist-get 'type attrs) "-")) + nil 0 0)))) + (file-modes + (lambda (file _nofollow) + (let ((attrs (eshell-parse-file-name-attributes file))) + (or (alist-get 'modes attrs) 0)))) + (file-exists-p #'always) + (file-regular-p + (lambda (file) + (let ((attrs (eshell-parse-file-name-attributes file))) + (member (or (alist-get 'type attrs) "-") '("-" "l"))))) + (file-symlink-p + (lambda (file) + (let ((attrs (eshell-parse-file-name-attributes file))) + (equal (alist-get 'type attrs) "l")))) + (file-executable-p + (lambda (file) + (let ((attrs (eshell-parse-file-name-attributes file))) + ;; For simplicity, just return whether the file is + ;; world-executable. + (= (logand (or (alist-get 'modes attrs) 0) 1) 1))))) + ,@body)) + +;;; Tests: + + +;; Argument predicates + +(ert-deftest em-pred-test/predicate-file-types () + "Test file type predicates." + (eshell-with-file-attributes-from-name + (let ((files (mapcar (lambda (i) (format "/fake/type=%s" i)) + '("b" "c" "d/" "p" "s" "l" "-")))) + (should (equal (eshell-eval-predicate files "%") + '("/fake/type=b" "/fake/type=c"))) + (should (equal (eshell-eval-predicate files "%b") '("/fake/type=b"))) + (should (equal (eshell-eval-predicate files "%c") '("/fake/type=c"))) + (should (equal (eshell-eval-predicate files "/") '("/fake/type=d/"))) + (should (equal (eshell-eval-predicate files ".") '("/fake/type=-"))) + (should (equal (eshell-eval-predicate files "p") '("/fake/type=p"))) + (should (equal (eshell-eval-predicate files "=") '("/fake/type=s"))) + (should (equal (eshell-eval-predicate files "@") '("/fake/type=l")))))) + +(ert-deftest em-pred-test/predicate-executable () + "Test that \"*\" matches only regular, non-symlink executable files." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/modes=0777" "/fake/modes=0666" + "/fake/type=d,modes=0777" "/fake/type=l,modes=0777"))) + (should (equal (eshell-eval-predicate files "*") + '("/fake/modes=0777")))))) + +(defmacro em-pred-test--file-modes-deftest (name mode-template predicates + &optional docstring) + "Define NAME as a file-mode test. +MODE-TEMPLATE is a format string to convert an integer from 0 to +7 to an octal file mode. PREDICATES is a list of strings for the +read, write, and execute predicates to query the file's modes." + (declare (indent 4) (doc-string 4)) + `(ert-deftest ,name () + ,docstring + (eshell-with-file-attributes-from-name + (let ((file-template (concat "/fake/modes=" ,mode-template))) + (cl-flet ((make-files (perms) + (mapcar (lambda (i) (format file-template i)) + perms))) + (pcase-let ((files (make-files (number-sequence 0 7))) + (`(,read ,write ,exec) ,predicates)) + (should (equal (eshell-eval-predicate files read) + (make-files '(4 5 6 7)))) + (should (equal (eshell-eval-predicate files (concat "^" read)) + (make-files '(0 1 2 3)))) + (should (equal (eshell-eval-predicate files write) + (make-files '(2 3 6 7)))) + (should (equal (eshell-eval-predicate files (concat "^" write)) + (make-files '(0 1 4 5)))) + (should (equal (eshell-eval-predicate files exec) + (make-files '(1 3 5 7)))) + (should (equal (eshell-eval-predicate files (concat "^" exec)) + (make-files '(0 2 4 6)))))))))) + +(em-pred-test--file-modes-deftest em-pred-test/predicate-file-modes-owner + "0%o00" '("r" "w" "x") + "Test predicates for file permissions for the owner.") + +(em-pred-test--file-modes-deftest em-pred-test/predicate-file-modes-group + "00%o0" '("A" "I" "E") + "Test predicates for file permissions for the group.") + +(em-pred-test--file-modes-deftest em-pred-test/predicate-file-modes-world + "000%o" '("R" "W" "X") + "Test predicates for file permissions for the world.") + +(em-pred-test--file-modes-deftest em-pred-test/predicate-file-modes-flags + "%o000" '("s" "S" "t") + "Test predicates for \"s\" (setuid), \"S\" (setgid), and \"t\" (sticky).") + +(ert-deftest em-pred-test/predicate-effective-uid () + "Test that \"U\" matches files owned by the effective UID." + (eshell-with-file-attributes-from-name + (cl-letf (((symbol-function 'user-uid) (lambda () 1))) + (let ((files '("/fake/uid=1" "/fake/uid=2"))) + (should (equal (eshell-eval-predicate files "U") + '("/fake/uid=1"))))))) + +(ert-deftest em-pred-test/predicate-effective-gid () + "Test that \"G\" matches files owned by the effective GID." + (eshell-with-file-attributes-from-name + (cl-letf (((symbol-function 'group-gid) (lambda () 1))) + (let ((files '("/fake/gid=1" "/fake/gid=2"))) + (should (equal (eshell-eval-predicate files "G") + '("/fake/gid=1"))))))) + +(ert-deftest em-pred-test/predicate-links () + "Test that \"l\" filters by number of links." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/links=1" "/fake/links=2" "/fake/links=3"))) + (should (equal (eshell-eval-predicate files "l1") + '("/fake/links=1"))) + (should (equal (eshell-eval-predicate files "l+1") + '("/fake/links=2" "/fake/links=3"))) + (should (equal (eshell-eval-predicate files "l-3") + '("/fake/links=1" "/fake/links=2")))))) + +(ert-deftest em-pred-test/predicate-uid () + "Test that \"u\" filters by UID/user name." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/uid=1" "/fake/uid=2")) + (user-names '("root" "one" "two"))) + (should (equal (eshell-eval-predicate files "u1") + '("/fake/uid=1"))) + (cl-letf (((symbol-function 'eshell-user-id) + (lambda (name) (seq-position user-names name)))) + (should (equal (eshell-eval-predicate files "u'one'") + '("/fake/uid=1"))))))) + +(ert-deftest em-pred-test/predicate-gid () + "Test that \"g\" filters by GID/group name." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/gid=1" "/fake/gid=2")) + (group-names '("root" "one" "two"))) + (should (equal (eshell-eval-predicate files "g1") + '("/fake/gid=1"))) + (cl-letf (((symbol-function 'eshell-group-id) + (lambda (name) (seq-position group-names name)))) + (should (equal (eshell-eval-predicate files "g'one'") + '("/fake/gid=1"))))))) + +(defmacro em-pred-test--time-deftest (name file-attribute predicate + &optional docstring) + "Define NAME as a file-time test. +FILE-ATTRIBUTE is the file's attribute to set (e.g. \"atime\"). +PREDICATE is the predicate used to query that attribute." + (declare (indent 4) (doc-string 4)) + `(ert-deftest ,name () + ,docstring + (eshell-with-file-attributes-from-name + (cl-flet ((make-file (time) + (format "/fake/%s=%d" ,file-attribute time))) + (let* ((now (time-convert nil 'integer)) + (yesterday (- now 86400)) + (files (mapcar #'make-file (list now yesterday)))) + ;; Test comparison against a number of days. + (should (equal (eshell-eval-predicate + files (concat ,predicate "-1")) + (mapcar #'make-file (list now)))) + (should (equal (eshell-eval-predicate + files (concat ,predicate "+1")) + (mapcar #'make-file (list yesterday)))) + (should (equal (eshell-eval-predicate + files (concat ,predicate "+2")) + nil)) + ;; Test comparison against a number of hours. + (should (equal (eshell-eval-predicate + files (concat ,predicate "h-1")) + (mapcar #'make-file (list now)))) + (should (equal (eshell-eval-predicate + files (concat ,predicate "h+1")) + (mapcar #'make-file (list yesterday)))) + (should (equal (eshell-eval-predicate + files (concat ,predicate "+48")) + nil)) + ;; Test comparison against another file. + (should (equal (eshell-eval-predicate + files (format "%s-'%s'" ,predicate (make-file now))) + nil)) + (should (equal (eshell-eval-predicate + files (format "%s+'%s'" ,predicate (make-file now))) + (mapcar #'make-file (list yesterday))))))))) + +(em-pred-test--time-deftest em-pred-test/predicate-access-time + "atime" "a" + "Test that \"a\" filters by access time.") + +(em-pred-test--time-deftest em-pred-test/predicate-modification-time + "mtime" "m" + "Test that \"m\" filters by change time.") + +(em-pred-test--time-deftest em-pred-test/predicate-change-time + "ctime" "c" + "Test that \"c\" filters by change time.") + +(ert-deftest em-pred-test/predicate-size () + "Test that \"L\" filters by file size." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/size=0" + ;; 1 and 2 KiB. + "/fake/size=1024" "/fake/size=2048" + ;; 1 and 2 MiB. + "/fake/size=1048576" "/fake/size=2097152"))) + ;; Size in bytes. + (should (equal (eshell-eval-predicate files "L2048") + '("/fake/size=2048"))) + (should (equal (eshell-eval-predicate files "L+2048") + '("/fake/size=1048576" "/fake/size=2097152"))) + (should (equal (eshell-eval-predicate files "L-2048") + '("/fake/size=0" "/fake/size=1024"))) + ;; Size in blocks. + (should (equal (eshell-eval-predicate files "Lp4") + '("/fake/size=2048"))) + (should (equal (eshell-eval-predicate files "Lp+4") + '("/fake/size=1048576" "/fake/size=2097152"))) + (should (equal (eshell-eval-predicate files "Lp-4") + '("/fake/size=0" "/fake/size=1024"))) + ;; Size in KiB. + (should (equal (eshell-eval-predicate files "Lk2") + '("/fake/size=2048"))) + (should (equal (eshell-eval-predicate files "Lk+2") + '("/fake/size=1048576" "/fake/size=2097152"))) + (should (equal (eshell-eval-predicate files "Lk-2") + '("/fake/size=0" "/fake/size=1024"))) + ;; Size in MiB. + (should (equal (eshell-eval-predicate files "LM1") + '("/fake/size=1048576"))) + (should (equal (eshell-eval-predicate files "LM+1") + '("/fake/size=2097152"))) + (should (equal (eshell-eval-predicate files "LM-1") + '("/fake/size=0" "/fake/size=1024" "/fake/size=2048")))))) + + +;; Argument modifiers + +(ert-deftest em-pred-test/modifier-eval () + "Test that \":E\" re-evaluates the value." + (should (equal (eshell-eval-predicate "${echo hi}" ":E") "hi")) + (should (equal (eshell-eval-predicate + '("${echo hi}" "$(upcase \"bye\")") ":E") + '("hi" "BYE")))) + +(ert-deftest em-pred-test/modifier-downcase () + "Test that \":L\" downcases values." + (should (equal (eshell-eval-predicate "FOO" ":L") "foo")) + (should (equal (eshell-eval-predicate '("FOO" "BAR") ":L") + '("foo" "bar")))) + +(ert-deftest em-pred-test/modifier-upcase () + "Test that \":U\" upcases values." + (should (equal (eshell-eval-predicate "foo" ":U") "FOO")) + (should (equal (eshell-eval-predicate '("foo" "bar") ":U") + '("FOO" "BAR")))) + +(ert-deftest em-pred-test/modifier-capitalize () + "Test that \":C\" capitalizes values." + (should (equal (eshell-eval-predicate "foo bar" ":C") "Foo Bar")) + (should (equal (eshell-eval-predicate '("foo bar" "baz") ":C") + '("Foo Bar" "Baz")))) + +(ert-deftest em-pred-test/modifier-dirname () + "Test that \":h\" returns the dirname." + (should (equal (eshell-eval-predicate "/path/to/file.el" ":h") "/path/to/")) + (should (equal (eshell-eval-predicate + '("/path/to/file.el" "/other/path/") ":h") + '("/path/to/" "/other/path/")))) + +(ert-deftest em-pred-test/modifier-basename () + "Test that \":t\" returns the basename." + (should (equal (eshell-eval-predicate "/path/to/file.el" ":t") "file.el")) + (should (equal (eshell-eval-predicate + '("/path/to/file.el" "/other/path/") ":t") + '("file.el" "")))) + +(ert-deftest em-pred-test/modifier-extension () + "Test that \":e\" returns the extension." + (should (equal (eshell-eval-predicate "/path/to/file.el" ":e") "el")) + (should (equal (eshell-eval-predicate + '("/path/to/file.el" "/other/path/") ":e") + '("el" nil)))) + +(ert-deftest em-pred-test/modifier-sans-extension () + "Test that \":r\" returns the file name san extension." + (should (equal (eshell-eval-predicate "/path/to/file.el" ":r") + "/path/to/file")) + (should (equal (eshell-eval-predicate + '("/path/to/file.el" "/other/path/") ":r") + '("/path/to/file" "/other/path/")))) + +(ert-deftest em-pred-test/modifier-quote () + "Test that \":q\" quotes arguments." + (should (equal-including-properties + (eshell-eval-predicate '("foo" "bar") ":q") + (list (eshell-escape-arg "foo") (eshell-escape-arg "bar"))))) + +(ert-deftest em-pred-test/modifier-substitute () + "Test that \":s/PAT/REP/\" replaces PAT with REP once." + (should (equal (eshell-eval-predicate "bar" ":s/a/*/") "b*r")) + (should (equal (eshell-eval-predicate "bar" ":s|a|*|") "b*r")) + (should (equal (eshell-eval-predicate "bar" ":s{a}{*}") "b*r")) + (should (equal (eshell-eval-predicate "bar" ":s{a}'*'") "b*r")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":s/[ao]/*/") + '("f*o" "b*r" "b*z"))) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":s|[ao]|*|") + '("f*o" "b*r" "b*z")))) + +(ert-deftest em-pred-test/modifier-global-substitute () + "Test that \":s/PAT/REP/\" replaces PAT with REP for all occurrences." + (should (equal (eshell-eval-predicate "foo" ":gs/a/*/") "foo")) + (should (equal (eshell-eval-predicate "foo" ":gs|a|*|") "foo")) + (should (equal (eshell-eval-predicate "bar" ":gs/a/*/") "b*r")) + (should (equal (eshell-eval-predicate "bar" ":gs|a|*|") "b*r")) + (should (equal (eshell-eval-predicate "foo" ":gs/o/O/") "fOO")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":gs/[aeiou]/*/") + '("f**" "b*r" "b*z"))) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":gs|[aeiou]|*|") + '("f**" "b*r" "b*z")))) + +(ert-deftest em-pred-test/modifier-include () + "Test that \":i/PAT/\" filters elements to include only ones matching PAT." + (should (equal (eshell-eval-predicate "foo" ":i/a/") nil)) + (should (equal (eshell-eval-predicate "bar" ":i/a/") "bar")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":i/a/") + '("bar" "baz")))) + +(ert-deftest em-pred-test/modifier-exclude () + "Test that \":x/PAT/\" filters elements to exclude any matching PAT." + (should (equal (eshell-eval-predicate "foo" ":x/a/") "foo")) + (should (equal (eshell-eval-predicate "bar" ":x/a/") nil)) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":x/a/") + '("foo")))) + +(ert-deftest em-pred-test/modifier-split () + "Test that \":S\" and \":S/PAT/\" split elements by spaces (or PAT)." + (should (equal (eshell-eval-predicate "foo bar baz" ":S") + '("foo" "bar" "baz"))) + (should (equal (eshell-eval-predicate '("foo bar" "baz") ":S") + '(("foo" "bar") ("baz")))) + (should (equal (eshell-eval-predicate "foo-bar-baz" ":S/-/") + '("foo" "bar" "baz"))) + (should (equal (eshell-eval-predicate '("foo-bar" "baz") ":S/-/") + '(("foo" "bar") ("baz"))))) + +(ert-deftest em-pred-test/modifier-join () + "Test that \":j\" and \":j/DELIM/\" join elements by spaces (or DELIM)." + (should (equal (eshell-eval-predicate "foo" ":j") "foo")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":j") + "foo bar baz")) + (should (equal (eshell-eval-predicate "foo" ":j/-/") "foo")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":j/-/") + "foo-bar-baz"))) + +(ert-deftest em-pred-test/modifier-sort () + "Test that \":o\" sorts elements in lexicographic order." + (should (equal (eshell-eval-predicate "foo" ":o") "foo")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":o") + '("bar" "baz" "foo")))) + +(ert-deftest em-pred-test/modifier-sort-reverse () + "Test that \":o\" sorts elements in reverse lexicographic order." + (should (equal (eshell-eval-predicate "foo" ":O") "foo")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":O") + '("foo" "baz" "bar")))) + +(ert-deftest em-pred-test/modifier-unique () + "Test that \":u\" filters out duplicate elements." + (should (equal (eshell-eval-predicate "foo" ":u") "foo")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":u") + '("foo" "bar" "baz"))) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz" "foo") ":u") + '("foo" "bar" "baz")))) + +(ert-deftest em-pred-test/modifier-reverse () + "Test that \":r\" reverses the order of elements." + (should (equal (eshell-eval-predicate "foo" ":R") "foo")) + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":R") + '("baz" "bar" "foo")))) + + +;; Miscellaneous + +(ert-deftest em-pred-test/combine-predicate-and-modifier () + "Test combination of predicates and modifiers." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/type=-.el" "/fake/type=-.txt" "/fake/type=s.el" + "/fake/subdir/type=-.el"))) + (should (equal (eshell-eval-predicate files ".:e:u") + '("el" "txt")))))) + +(ert-deftest em-pred-test/predicate-delimiters () + "Test various delimiter pairs with predicates and modifiers." + (dolist (delims eshell-pred-delimiter-pairs) + (eshell-with-file-attributes-from-name + (let ((files '("/fake/uid=1" "/fake/uid=2")) + (user-names '("root" "one" "two"))) + (cl-letf (((symbol-function 'eshell-user-id) + (lambda (name) (seq-position user-names name)))) + (should (equal (eshell-eval-predicate + files (format "u%cone%c" (car delims) (cdr delims))) + '("/fake/uid=1")))))) + (should (equal (eshell-eval-predicate + '("foo" "bar" "baz") + (format ":j%c-%c" (car delims) (cdr delims))) + "foo-bar-baz")))) + +(ert-deftest em-pred-test/predicate-escaping () + "Test string escaping in predicate and modifier parameters." + ;; Escaping the delimiter should remove the backslash. + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":j'\\''") + "foo'bar'baz")) + ;; Escaping a backlash should remove the first backslash. + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":j'\\\\'") + "foo\\bar\\baz")) + ;; Escaping a different character should keep the backslash. + (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":j'\\\"'") + "foo\\\"bar\\\"baz"))) + +(ert-deftest em-pred-test/no-matches () + "Test behavior when a predicate fails to match any files." + (eshell-with-file-attributes-from-name + (let ((files '("/fake/modes=0666" "/fake/type=d,modes=0777" + "/fake/type=l,modes=0777"))) + (should (equal (eshell-eval-predicate files "*") nil)) + (let ((eshell-error-if-no-glob t)) + ;; Don't signal an error if the original list is empty. + (should (equal (eshell-eval-predicate nil "*") nil)) + ;; Ensure this signals an error. This test case is a bit + ;; clumsy, since `eshell-do-eval' makes it hard to catch + ;; errors otherwise. + (let ((modifiers (with-temp-eshell + (eshell-with-temp-command "*" + (eshell-parse-modifiers))))) + (should-error (eshell-apply-modifiers files (car modifiers) + (cdr modifiers) "*"))))))) + +;; em-pred-tests.el ends here diff --git a/test/lisp/eshell/em-script-tests.el b/test/lisp/eshell/em-script-tests.el new file mode 100644 index 00000000000..b837d464ccd --- /dev/null +++ b/test/lisp/eshell/em-script-tests.el @@ -0,0 +1,62 @@ +;;; em-script-tests.el --- em-script test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's script module. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(require 'eshell) +(require 'em-script) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) +;;; Tests: + +(ert-deftest em-script-test/source-script () + "Test sourcing script with no argumentss" + (ert-with-temp-file temp-file :text "echo hi" + (with-temp-eshell + (eshell-match-command-output (format "source %s" temp-file) + "hi\n")))) + +(ert-deftest em-script-test/source-script-arg-vars () + "Test sourcing script with $0, $1, ... variables" + (ert-with-temp-file temp-file :text "printnl $0 \"$1 $2\"" + (with-temp-eshell + (eshell-match-command-output (format "source %s one two" temp-file) + (format "%s\none two\n" temp-file))))) + +(ert-deftest em-script-test/source-script-all-args-var () + "Test sourcing script with the $* variable" + (ert-with-temp-file temp-file :text "printnl $*" + (with-temp-eshell + (eshell-match-command-output (format "source %s" temp-file) + "\\`\\'") + (eshell-match-command-output (format "source %s a" temp-file) + "a\n") + (eshell-match-command-output (format "source %s a b c" temp-file) + "a\nb\nc\n")))) + +;; em-script-tests.el ends here diff --git a/test/lisp/eshell/em-tramp-tests.el b/test/lisp/eshell/em-tramp-tests.el new file mode 100644 index 00000000000..8969c1e2294 --- /dev/null +++ b/test/lisp/eshell/em-tramp-tests.el @@ -0,0 +1,88 @@ +;;; em-tramp-tests.el --- em-tramp test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'em-tramp) +(require 'tramp) + +(ert-deftest em-tramp-test/su-default () + "Test Eshell `su' command with no arguments." + (should (equal + (catch 'eshell-replace-command (eshell/su)) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/su:root@%s:%s" + tramp-default-host default-directory))))))) + +(ert-deftest em-tramp-test/su-user () + "Test Eshell `su' command with USER argument." + (should (equal + (catch 'eshell-replace-command (eshell/su "USER")) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/su:USER@%s:%s" + tramp-default-host default-directory))))))) + +(ert-deftest em-tramp-test/su-login () + "Test Eshell `su' command with -/-l/--login option." + (dolist (args '(("--login") + ("-l") + ("-"))) + (should (equal + (catch 'eshell-replace-command (apply #'eshell/su args)) + `(eshell-trap-errors + (eshell-named-command + "cd" + (list ,(format "/su:root@%s:~/" tramp-default-host)))))))) + +(defun mock-eshell-named-command (&rest args) + "Dummy function to test Eshell `sudo' command rewriting." + (list default-directory args)) + +(ert-deftest em-tramp-test/sudo-basic () + "Test Eshell `sudo' command with default user." + (cl-letf (((symbol-function 'eshell-named-command) + #'mock-eshell-named-command)) + (should (equal + (catch 'eshell-external (eshell/sudo "echo" "hi")) + `(,(format "/sudo:root@%s:%s" tramp-default-host default-directory) + ("echo" ("hi"))))) + (should (equal + (catch 'eshell-external (eshell/sudo "echo" "-u" "hi")) + `(,(format "/sudo:root@%s:%s" tramp-default-host default-directory) + ("echo" ("-u" "hi"))))))) + +(ert-deftest em-tramp-test/sudo-user () + "Test Eshell `sudo' command with specified user." + (cl-letf (((symbol-function 'eshell-named-command) + #'mock-eshell-named-command)) + (should (equal + (catch 'eshell-external (eshell/sudo "-u" "USER" "echo" "hi")) + `(,(format "/sudo:USER@%s:%s" tramp-default-host default-directory) + ("echo" ("hi"))))) + (should (equal + (catch 'eshell-external (eshell/sudo "-u" "USER" "echo" "-u" "hi")) + `(,(format "/sudo:USER@%s:%s" tramp-default-host default-directory) + ("echo" ("-u" "hi"))))))) + +;;; em-tramp-tests.el ends here diff --git a/test/lisp/eshell/esh-cmd-tests.el b/test/lisp/eshell/esh-cmd-tests.el new file mode 100644 index 00000000000..92d785d7fdf --- /dev/null +++ b/test/lisp/eshell/esh-cmd-tests.el @@ -0,0 +1,294 @@ +;;; esh-cmd-tests.el --- esh-cmd test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's command invocation. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(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: + + +;; Command invocation + +(ert-deftest esh-cmd-test/simple-command-result () + "Test invocation with a simple command." + (eshell-command-result-equal "+ 1 2" 3)) + +(ert-deftest esh-cmd-test/lisp-command () + "Test invocation with an elisp command." + (eshell-command-result-equal "(+ 1 2)" 3)) + +(ert-deftest esh-cmd-test/lisp-command-with-quote () + "Test invocation with an elisp command containing a quote." + (eshell-command-result-equal "(eq 'foo nil)" nil)) + +(ert-deftest esh-cmd-test/lisp-command-args () + "Test invocation with elisp and trailing args. +Test that trailing arguments outside the S-expression are +ignored. e.g. \"(+ 1 2) 3\" => 3" + (eshell-command-result-equal "(+ 1 2) 3" 3)) + +(ert-deftest esh-cmd-test/subcommand () + "Test invocation with a simple subcommand." + (eshell-command-result-equal "{+ 1 2}" 3)) + +(ert-deftest esh-cmd-test/subcommand-args () + "Test invocation with a subcommand and trailing args. +Test that trailing arguments outside the subcommand are ignored. +e.g. \"{+ 1 2} 3\" => 3" + (eshell-command-result-equal "{+ 1 2} 3" 3)) + +(ert-deftest esh-cmd-test/subcommand-lisp () + "Test invocation with an elisp subcommand and trailing args. +Test that trailing arguments outside the subcommand are ignored. +e.g. \"{(+ 1 2)} 3\" => 3" + (eshell-command-result-equal "{(+ 1 2)} 3" 3)) + + +;; Lisp forms + +(ert-deftest esh-cmd-test/quoted-lisp-form () + "Test parsing of a quoted Lisp form." + (eshell-command-result-equal "echo #'(1 2)" '(1 2))) + +(ert-deftest esh-cmd-test/backquoted-lisp-form () + "Test parsing of a backquoted Lisp form." + (let ((eshell-test-value 42)) + (eshell-command-result-equal "echo `(answer ,eshell-test-value)" + '(answer 42)))) + +(ert-deftest esh-cmd-test/backquoted-lisp-form/splice () + "Test parsing of a backquoted Lisp form using splicing." + (let ((eshell-test-value '(2 3))) + (eshell-command-result-equal "echo `(1 ,@eshell-test-value)" + '(1 2 3)))) + + +;; Logical operators + +(ert-deftest esh-cmd-test/and-operator () + "Test logical && operator." + (skip-unless (executable-find "[")) + (with-temp-eshell + (eshell-match-command-output "[ foo = foo ] && echo hi" + "hi\n") + (eshell-match-command-output "[ foo = bar ] && echo hi" + "\\`\\'"))) + +(ert-deftest esh-cmd-test/or-operator () + "Test logical || operator." + (skip-unless (executable-find "[")) + (with-temp-eshell + (eshell-match-command-output "[ foo = foo ] || echo hi" + "\\`\\'") + (eshell-match-command-output "[ foo = bar ] || echo hi" + "hi\n"))) + + +;; Control flow statements + +(ert-deftest esh-cmd-test/for-loop () + "Test invocation of a for loop." + (with-temp-eshell + (eshell-match-command-output "for i in 5 { echo $i }" + "5\n"))) + +(ert-deftest esh-cmd-test/for-loop-list () + "Test invocation of a for loop iterating over a list." + (with-temp-eshell + (eshell-match-command-output "for i in (list 1 2 (list 3 4)) { echo $i }" + "1\n2\n(3 4)\n"))) + +(ert-deftest esh-cmd-test/for-loop-multiple-args () + "Test invocation of a for loop iterating over multiple arguments." + (with-temp-eshell + (eshell-match-command-output "for i in 1 2 (list 3 4) { echo $i }" + "1\n2\n3\n4\n"))) + +(ert-deftest esh-cmd-test/for-name-loop () ; bug#15231 + "Test invocation of a for loop using `name'." + (let ((process-environment (cons "name" process-environment))) + (eshell-command-result-equal "for name in 3 { echo $name }" + 3))) + +(ert-deftest esh-cmd-test/for-name-shadow-loop () ; bug#15372 + "Test invocation of a for loop using an env-var." + (let ((process-environment (cons "name=env-value" process-environment))) + (with-temp-eshell + (eshell-match-command-output + "echo $name; for name in 3 { echo $name }; echo $name" + "env-value\n3\nenv-value\n")))) + +(ert-deftest esh-cmd-test/while-loop () + "Test invocation of a while loop." + (with-temp-eshell + (let ((eshell-test-value '(0 1 2))) + (eshell-match-command-output + (concat "while $eshell-test-value " + "{ setq eshell-test-value (cdr eshell-test-value) }") + "(1 2)\n(2)\n")))) + +(ert-deftest esh-cmd-test/while-loop-lisp-form () + "Test invocation of a while loop using a Lisp form." + (with-temp-eshell + (let ((eshell-test-value 0)) + (eshell-match-command-output + (concat "while (/= eshell-test-value 3) " + "{ setq eshell-test-value (1+ eshell-test-value) }") + "1\n2\n3\n")))) + +(ert-deftest esh-cmd-test/while-loop-ext-cmd () + "Test invocation of a while loop using an external command." + (skip-unless (executable-find "[")) + (with-temp-eshell + (let ((eshell-test-value 0)) + (eshell-match-command-output + (concat "while {[ $eshell-test-value -ne 3 ]} " + "{ setq eshell-test-value (1+ eshell-test-value) }") + "1\n2\n3\n")))) + +(ert-deftest esh-cmd-test/until-loop () + "Test invocation of an until loop." + (with-temp-eshell + (let ((eshell-test-value nil)) + (eshell-match-command-output + (concat "until $eshell-test-value " + "{ setq eshell-test-value t }") + "t\n")))) + +(ert-deftest esh-cmd-test/until-loop-lisp-form () + "Test invocation of an until loop using a Lisp form." + (skip-unless (executable-find "[")) + (with-temp-eshell + (let ((eshell-test-value 0)) + (eshell-match-command-output + (concat "until (= eshell-test-value 3) " + "{ setq eshell-test-value (1+ eshell-test-value) }") + "1\n2\n3\n")))) + +(ert-deftest esh-cmd-test/until-loop-ext-cmd () + "Test invocation of an until loop using an external command." + (skip-unless (executable-find "[")) + (with-temp-eshell + (let ((eshell-test-value 0)) + (eshell-match-command-output + (concat "until {[ $eshell-test-value -eq 3 ]} " + "{ setq eshell-test-value (1+ eshell-test-value) }") + "1\n2\n3\n")))) + +(ert-deftest esh-cmd-test/if-statement () + "Test invocation of an if statement." + (let ((eshell-test-value t)) + (eshell-command-result-equal "if $eshell-test-value {echo yes}" + "yes")) + (let ((eshell-test-value nil)) + (eshell-command-result-equal "if $eshell-test-value {echo yes}" + nil))) + +(ert-deftest esh-cmd-test/if-else-statement () + "Test invocation of an if/else statement." + (let ((eshell-test-value t)) + (eshell-command-result-equal "if $eshell-test-value {echo yes} {echo no}" + "yes")) + (let ((eshell-test-value nil)) + (eshell-command-result-equal "if $eshell-test-value {echo yes} {echo no}" + "no"))) + +(ert-deftest esh-cmd-test/if-else-statement-lisp-form () + "Test invocation of an if/else statement using a Lisp form." + (eshell-command-result-equal "if (zerop 0) {echo yes} {echo no}" + "yes") + (eshell-command-result-equal "if (zerop 1) {echo yes} {echo no}" + "no") + (let ((debug-on-error nil)) + (eshell-command-result-equal "if (zerop \"foo\") {echo yes} {echo no}" + "no"))) + +(ert-deftest esh-cmd-test/if-else-statement-lisp-form-2 () + "Test invocation of an if/else statement using a Lisp form. +This tests when `eshell-lisp-form-nil-is-failure' is nil." + (let ((eshell-lisp-form-nil-is-failure nil)) + (eshell-command-result-equal "if (zerop 0) {echo yes} {echo no}" + "yes") + (eshell-command-result-equal "if (zerop 1) {echo yes} {echo no}" + "yes") + (let ((debug-on-error nil)) + (eshell-command-result-equal "if (zerop \"foo\") {echo yes} {echo no}" + "no")))) + +(ert-deftest esh-cmd-test/if-else-statement-ext-cmd () + "Test invocation of an if/else statement using an external command." + (skip-unless (executable-find "[")) + (eshell-command-result-equal "if {[ foo = foo ]} {echo yes} {echo no}" + "yes") + (eshell-command-result-equal "if {[ foo = bar ]} {echo yes} {echo no}" + "no")) + +(ert-deftest esh-cmd-test/unless-statement () + "Test invocation of an unless statement." + (let ((eshell-test-value t)) + (eshell-command-result-equal "unless $eshell-test-value {echo no}" + nil)) + (let ((eshell-test-value nil)) + (eshell-command-result-equal "unless $eshell-test-value {echo no}" + "no"))) + +(ert-deftest esh-cmd-test/unless-else-statement () + "Test invocation of an unless/else statement." + (let ((eshell-test-value t)) + (eshell-command-result-equal + "unless $eshell-test-value {echo no} {echo yes}" + "yes")) + (let ((eshell-test-value nil)) + (eshell-command-result-equal + "unless $eshell-test-value {echo no} {echo yes}" + "no"))) + +(ert-deftest esh-cmd-test/unless-else-statement-lisp-form () + "Test invocation of an unless/else statement using a Lisp form." + (eshell-command-result-equal "unless (zerop 0) {echo no} {echo yes}" + "yes") + (eshell-command-result-equal "unless (zerop 1) {echo no} {echo yes}" + "no") + (let ((debug-on-error nil)) + (eshell-command-result-equal "unless (zerop \"foo\") {echo no} {echo yes}" + "no"))) + +(ert-deftest esh-cmd-test/unless-else-statement-ext-cmd () + "Test invocation of an unless/else statement using an external command." + (skip-unless (executable-find "[")) + (eshell-command-result-equal "unless {[ foo = foo ]} {echo no} {echo yes}" + "yes") + (eshell-command-result-equal "unless {[ foo = bar ]} {echo no} {echo yes}" + "no")) + +;; esh-cmd-tests.el ends here diff --git a/test/lisp/eshell/esh-io-tests.el b/test/lisp/eshell/esh-io-tests.el new file mode 100644 index 00000000000..37b234eaf06 --- /dev/null +++ b/test/lisp/eshell/esh-io-tests.el @@ -0,0 +1,292 @@ +;;; esh-io-tests.el --- esh-io test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'ert-x) +(require 'esh-mode) +(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) + +(defun eshell-test-file-string (file) + "Return the contents of FILE as a string." + (with-temp-buffer + (insert-file-contents file) + (buffer-string))) + +(defun eshell/test-output () + "Write some test output separately to stdout and stderr." + (eshell-printn "stdout") + (eshell-errorn "stderr")) + +;;; Tests: + + +;; Basic redirection + +(ert-deftest esh-io-test/redirect-file/overwrite () + "Check that redirecting to a file in overwrite mode works." + (ert-with-temp-file temp-file + :text "old" + (with-temp-eshell + (eshell-insert-command (format "echo new > %s" temp-file))) + (should (equal (eshell-test-file-string temp-file) "new")))) + +(ert-deftest esh-io-test/redirect-file/append () + "Check that redirecting to a file in append mode works." + (ert-with-temp-file temp-file + :text "old" + (with-temp-eshell + (eshell-insert-command (format "echo new >> %s" temp-file))) + (should (equal (eshell-test-file-string temp-file) "oldnew")))) + +(ert-deftest esh-io-test/redirect-file/insert () + "Check that redirecting to a file in insert works." + (ert-with-temp-file temp-file + :text "old" + (with-temp-eshell + (eshell-insert-command (format "echo new >>> %s" temp-file))) + (should (equal (eshell-test-file-string temp-file) "newold")))) + +(ert-deftest esh-io-test/redirect-buffer/overwrite () + "Check that redirecting to a buffer in overwrite mode works." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-insert-command (format "echo new > #<%s>" bufname))) + (should (equal (buffer-string) "new")))) + +(ert-deftest esh-io-test/redirect-buffer/append () + "Check that redirecting to a buffer in append mode works." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-insert-command (format "echo new >> #<%s>" bufname))) + (should (equal (buffer-string) "oldnew")))) + +(ert-deftest esh-io-test/redirect-buffer/insert () + "Check that redirecting to a buffer in insert mode works." + (eshell-with-temp-buffer bufname "old" + (goto-char (point-min)) + (with-temp-eshell + (eshell-insert-command (format "echo new >>> #<%s>" bufname))) + (should (equal (buffer-string) "newold")))) + +(ert-deftest esh-io-test/redirect-buffer/escaped () + "Check that redirecting to a buffer with escaped characters works." + (with-temp-buffer + (rename-buffer "eshell\\temp\\buffer" t) + (let ((bufname (buffer-name))) + (with-temp-eshell + (eshell-insert-command (format "echo hi > #<%s>" + (string-replace "\\" "\\\\" bufname)))) + (should (equal (buffer-string) "hi"))))) + +(ert-deftest esh-io-test/redirect-symbol/overwrite () + "Check that redirecting to a symbol in overwrite mode works." + (let ((eshell-test-value "old")) + (with-temp-eshell + (eshell-insert-command "echo new > #'eshell-test-value")) + (should (equal eshell-test-value "new")))) + +(ert-deftest esh-io-test/redirect-symbol/append () + "Check that redirecting to a symbol in append mode works." + (let ((eshell-test-value "old")) + (with-temp-eshell + (eshell-insert-command "echo new >> #'eshell-test-value")) + (should (equal eshell-test-value "oldnew")))) + +(ert-deftest esh-io-test/redirect-marker () + "Check that redirecting to a marker works." + (with-temp-buffer + (let ((eshell-test-value (point-marker))) + (with-temp-eshell + (eshell-insert-command "echo hi > $eshell-test-value")) + (should (equal (buffer-string) "hi"))))) + +(ert-deftest esh-io-test/redirect-multiple () + "Check that redirecting to multiple targets works." + (let ((eshell-test-value "old")) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-insert-command (format "echo new > #<%s> > #'eshell-test-value" + bufname))) + (should (equal (buffer-string) "new")) + (should (equal eshell-test-value "new"))))) + +(ert-deftest esh-io-test/redirect-multiple/repeat () + "Check that redirecting to multiple targets works when repeating a target." + (let ((eshell-test-value "old")) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-insert-command + (format "echo new > #<%s> > #'eshell-test-value > #<%s>" + bufname bufname))) + (should (equal (buffer-string) "new")) + (should (equal eshell-test-value "new"))))) + + +;; Redirecting specific handles + +(ert-deftest esh-io-test/redirect-stdout () + "Check that redirecting to stdout doesn't redirect stderr." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output > #<%s>" bufname) + "stderr\n")) + (should (equal (buffer-string) "stdout\n"))) + ;; Also check explicitly specifying the stdout fd. + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output 1> #<%s>" bufname) + "stderr\n")) + (should (equal (buffer-string) "stdout\n")))) + +(ert-deftest esh-io-test/redirect-stderr/overwrite () + "Check that redirecting to stderr doesn't redirect stdout." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output 2> #<%s>" bufname) + "stdout\n")) + (should (equal (buffer-string) "stderr\n")))) + +(ert-deftest esh-io-test/redirect-stderr/append () + "Check that redirecting to stderr doesn't redirect stdout." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output 2>> #<%s>" bufname) + "stdout\n")) + (should (equal (buffer-string) "oldstderr\n")))) + +(ert-deftest esh-io-test/redirect-stderr/insert () + "Check that redirecting to stderr doesn't redirect stdout." + (eshell-with-temp-buffer bufname "old" + (goto-char (point-min)) + (with-temp-eshell + (eshell-match-command-output (format "test-output 2>>> #<%s>" bufname) + "stdout\n")) + (should (equal (buffer-string) "stderr\nold")))) + +(ert-deftest esh-io-test/redirect-stdout-and-stderr () + "Check that redirecting to both stdout and stderr works." + (eshell-with-temp-buffer bufname-1 "old" + (eshell-with-temp-buffer bufname-2 "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output > #<%s> 2> #<%s>" + bufname-1 bufname-2) + "\\`\\'")) + (should (equal (buffer-string) "stderr\n"))) + (should (equal (buffer-string) "stdout\n")))) + +(ert-deftest esh-io-test/redirect-all/overwrite () + "Check that redirecting to stdout and stderr via shorthand works." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output &> #<%s>" bufname) + "\\`\\'")) + (should (equal (buffer-string) "stdout\nstderr\n"))) + ;; Also check the alternate (and less-preferred in Bash) `>&' syntax. + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output >& #<%s>" bufname) + "\\`\\'")) + (should (equal (buffer-string) "stdout\nstderr\n")))) + +(ert-deftest esh-io-test/redirect-all/append () + "Check that redirecting to stdout and stderr via shorthand works." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output &>> #<%s>" bufname) + "\\`\\'")) + (should (equal (buffer-string) "oldstdout\nstderr\n"))) + ;; Also check the alternate (and less-preferred in Bash) `>>&' syntax. + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output >>& #<%s>" bufname) + "\\`\\'")) + (should (equal (buffer-string) "oldstdout\nstderr\n")))) + +(ert-deftest esh-io-test/redirect-all/insert () + "Check that redirecting to stdout and stderr via shorthand works." + (eshell-with-temp-buffer bufname "old" + (goto-char (point-min)) + (with-temp-eshell + (eshell-match-command-output (format "test-output &>>> #<%s>" bufname) + "\\`\\'")) + (should (equal (buffer-string) "stdout\nstderr\nold"))) + ;; Also check the alternate `>>>&' syntax. + (eshell-with-temp-buffer bufname "old" + (goto-char (point-min)) + (with-temp-eshell + (eshell-match-command-output (format "test-output >>>& #<%s>" bufname) + "\\`\\'")) + (should (equal (buffer-string) "stdout\nstderr\nold")))) + +(ert-deftest esh-io-test/redirect-copy () + "Check that redirecting stdout and then copying stdout to stderr works. +This should redirect both stdout and stderr to the same place." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output > #<%s> 2>&1" bufname) + "\\`\\'")) + (should (equal (buffer-string) "stdout\nstderr\n")))) + +(ert-deftest esh-io-test/redirect-copy-first () + "Check that copying stdout to stderr and then redirecting stdout works. +This should redirect stdout to a buffer, and stderr to where +stdout originally pointed (the terminal)." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output (format "test-output 2>&1 > #<%s>" bufname) + "stderr\n")) + (should (equal (buffer-string) "stdout\n")))) + +(ert-deftest esh-io-test/redirect-pipe () + "Check that \"redirecting\" to a pipe works." + ;; `|' should only redirect stdout. + (eshell-command-result-equal "test-output | rev" + "stderr\ntuodts\n") + ;; `|&' should redirect stdout and stderr. + (eshell-command-result-equal "test-output |& rev" + "tuodts\nrredts\n")) + + +;; Virtual targets + +(ert-deftest esh-io-test/virtual-dev-eshell () + "Check that redirecting to /dev/eshell works." + (with-temp-eshell + (eshell-match-command-output "echo hi > /dev/eshell" "hi"))) + +(ert-deftest esh-io-test/virtual-dev-kill () + "Check that redirecting to /dev/kill works." + (with-temp-eshell + (eshell-insert-command "echo one > /dev/kill") + (should (equal (car kill-ring) "one")) + (eshell-insert-command "echo two > /dev/kill") + (should (equal (car kill-ring) "two")) + (eshell-insert-command "echo three >> /dev/kill") + (should (equal (car kill-ring) "twothree")))) + +;;; esh-io-tests.el ends here diff --git a/test/lisp/eshell/esh-opt-tests.el b/test/lisp/eshell/esh-opt-tests.el new file mode 100644 index 00000000000..5b30de414a3 --- /dev/null +++ b/test/lisp/eshell/esh-opt-tests.el @@ -0,0 +1,289 @@ +;;; esh-opt-tests.el --- esh-opt test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2018-2022 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 <https://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'esh-opt) + +(ert-deftest esh-opt-test/process-args () + "Test behavior of `eshell--process-args'." + (should + (equal '(t) + (eshell--process-args + "sudo" '("-a") + '((?a "all" nil show-all + "do not ignore entries starting with ."))))) + (should + (equal '("root" "world") + (eshell--process-args + "sudo" '("-u" "root" "world") + '((?u "user" t user + "execute a command as another USER")))))) + +(ert-deftest esh-opt-test/process-args-parse-leading-options-only () + "Test behavior of :parse-leading-options-only in `eshell--process-args'." + (should + (equal '(nil "emerge" "-uDN" "world") + (eshell--process-args + "sudo" '("emerge" "-uDN" "world") + '((?u "user" t user + "execute a command as another USER") + :parse-leading-options-only)))) + (should + (equal '("root" "emerge" "-uDN" "world") + (eshell--process-args + "sudo" '("-u" "root" "emerge" "-uDN" "world") + '((?u "user" t user + "execute a command as another USER") + :parse-leading-options-only)))) + (should + (equal '("DN" "emerge" "world") + (eshell--process-args + "sudo" '("-u" "root" "emerge" "-uDN" "world") + '((?u "user" t user + "execute a command as another USER")))))) + +(ert-deftest esh-opt-test/process-args-external () + "Test behavior of :external in `eshell--process-args'." + (cl-letf (((symbol-function 'eshell-search-path) #'ignore)) + (should + (equal '(nil "/some/path") + (eshell--process-args + "ls" '("/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .") + :external "ls"))))) + (cl-letf (((symbol-function 'eshell-search-path) #'identity)) + (should + (equal '(no-catch eshell-ext-command "ls") + (should-error + (eshell--process-args + "ls" '("-u" "/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .") + :external "ls")) + :type 'no-catch)))) + (cl-letf (((symbol-function 'eshell-search-path) #'ignore)) + (should-error + (eshell--process-args + "ls" '("-u" "/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .") + :external "ls")) + :type 'error))) + +(ert-deftest esh-opt-test/eval-using-options-short () + "Test `eshell-eval-using-options' with short options." + (eshell-eval-using-options + "ls" '("-a" "/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .")) + (should (eq show-all t)) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .")) + (should (eq show-all nil)) + (should (equal args '("/some/path"))))) + +(ert-deftest esh-opt-test/eval-using-options-long () + "Test `eshell-eval-using-options' with long options." + (eshell-eval-using-options + "ls" '("--all" "/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .")) + (should (eq show-all t)) + (should (equal args '("/some/path"))))) + +(ert-deftest esh-opt-test/eval-using-options-constant () + "Test `eshell-eval-using-options' with options with constant values." + (eshell-eval-using-options + "ls" '("/some/path" "-h") + '((?h "human-readable" 1024 human-readable + "print sizes in human readable format")) + (should (eql human-readable 1024)) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("/some/path" "--human-readable") + '((?h "human-readable" 1024 human-readable + "print sizes in human readable format")) + (should (eql human-readable 1024)) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("/some/path") + '((?h "human-readable" 1024 human-readable + "print sizes in human readable format")) + (should (eq human-readable nil)) + (should (equal args '("/some/path"))))) + +(ert-deftest esh-opt-test/eval-using-options-user-specified () + "Test `eshell-eval-using-options' with options with user-specified values." + (eshell-eval-using-options + "ls" '("-I" "*.txt" "/some/path") + '((?I "ignore" t ignore-pattern + "do not list implied entries matching pattern")) + (should (equal ignore-pattern "*.txt")) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("-I*.txt" "/some/path") + '((?I "ignore" t ignore-pattern + "do not list implied entries matching pattern")) + (should (equal ignore-pattern "*.txt")) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("--ignore" "*.txt" "/some/path") + '((?I "ignore" t ignore-pattern + "do not list implied entries matching pattern")) + (should (equal ignore-pattern "*.txt")) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("--ignore=*.txt" "/some/path") + '((?I "ignore" t ignore-pattern + "do not list implied entries matching pattern")) + (should (equal ignore-pattern "*.txt")) + (should (equal args '("/some/path"))))) + +(ert-deftest esh-opt-test/eval-using-options-short-single-token () + "Test `eshell-eval-using-options' with multiple short options in one token." + (eshell-eval-using-options + "ls" '("-al" "/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .") + (?l nil long-listing listing-style + "use a long listing format")) + (should (eq t show-all)) + (should (eql listing-style 'long-listing)) + (should (equal args '("/some/path")))) + (eshell-eval-using-options + "ls" '("-aI*.txt" "/some/path") + '((?a "all" nil show-all + "do not ignore entries starting with .") + (?I "ignore" t ignore-pattern + "do not list implied entries matching pattern")) + (should (eq t show-all)) + (should (equal ignore-pattern "*.txt")) + (should (equal args '("/some/path"))))) + +(ert-deftest esh-opt-test/eval-using-options-stdin () + "Test that \"-\" is a positional arg in `eshell-eval-using-options'." + (eshell-eval-using-options + "cat" '("-") + '((?A "show-all" nil show-all + "show all characters")) + (should (eq show-all nil)) + (should (equal args '("-")))) + (eshell-eval-using-options + "cat" '("-A" "-") + '((?A "show-all" nil show-all + "show all characters")) + (should (eq show-all t)) + (should (equal args '("-")))) + (eshell-eval-using-options + "cat" '("-" "-A") + '((?A "show-all" nil show-all + "show all characters")) + (should (eq show-all t)) + (should (equal args '("-"))))) + +(ert-deftest esh-opt-test/eval-using-options-terminate-options () + "Test that \"--\" terminates options in `eshell-eval-using-options'." + (eshell-eval-using-options + "ls" '("--" "-a") + '((?a "all" nil show-all + "do not ignore entries starting with .")) + (should (eq show-all nil)) + (should (equal args '("-a")))) + (eshell-eval-using-options + "ls" '("--" "--all") + '((?a "all" nil show-all + "do not ignore entries starting with .")) + (should (eq show-all nil)) + (should (equal args '("--all"))))) + +(ert-deftest esh-opt-test/eval-using-options-parse-leading-options-only () + "Test :parse-leading-options-only in `eshell-eval-using-options'." + (eshell-eval-using-options + "sudo" '("-u" "root" "whoami") + '((?u "user" t user "execute a command as another USER") + :parse-leading-options-only) + (should (equal user "root")) + (should (equal args '("whoami")))) + (eshell-eval-using-options + "sudo" '("--user" "root" "whoami") + '((?u "user" t user "execute a command as another USER") + :parse-leading-options-only) + (should (equal user "root")) + (should (equal args '("whoami")))) + (eshell-eval-using-options + "sudo" '("emerge" "-uDN" "world") + '((?u "user" t user "execute a command as another USER")) + (should (equal user "DN")) + (should (equal args '("emerge" "world")))) + (eshell-eval-using-options + "sudo" '("emerge" "-uDN" "world") + '((?u "user" t user "execute a command as another USER") + :parse-leading-options-only) + (should (eq user nil)) + (should (equal args '("emerge" "-uDN" "world"))))) + +(ert-deftest esh-opt-test/eval-using-options-unrecognized () + "Test `eshell-eval-using-options' with unrecognized options." + (should-error + (eshell-eval-using-options + "ls" '("-u" "/some/path") + '((?a "all" nil _show-all + "do not ignore entries starting with .")))) + (should-error + (eshell-eval-using-options + "ls" '("-au" "/some/path") + '((?a "all" nil _show-all + "do not ignore entries starting with .")))) + (should-error + (eshell-eval-using-options + "ls" '("--unrecognized" "/some/path") + '((?a "all" nil _show-all + "do not ignore entries starting with ."))))) + +(ert-deftest esh-opt-test/eval-using-options-external () + "Test :external in `eshell-eval-using-options'." + (cl-letf (((symbol-function 'eshell-search-path) #'identity) + ((symbol-function 'eshell-external-command) #'list)) + (should + (equal (catch 'eshell-external + (eshell-eval-using-options + "ls" '("/some/path" "-u") + '((?a "all" nil _show-all + "do not ignore entries starting with .") + :external "ls"))) + '("ls" ("/some/path" "-u")))) + (should + (equal (catch 'eshell-external + (eshell-eval-using-options + "ls" '("/some/path2" "-u") + '((?a "all" nil _show-all + "do not ignore entries starting with .") + :preserve-args + :external "ls"))) + '("ls" ("/some/path2" "-u")))))) + +(provide 'esh-opt-tests) + +;;; esh-opt-tests.el ends here diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el new file mode 100644 index 00000000000..abe363bee0d --- /dev/null +++ b/test/lisp/eshell/esh-proc-tests.el @@ -0,0 +1,249 @@ +;;; esh-proc-tests.el --- esh-proc test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(require 'eshell) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) + +(defvar esh-proc-test--output-cmd + (concat "sh -c '" + "echo stdout; " + "echo stderr >&2" + "'") + "A shell command that prints to both stdout and stderr.") + +(defvar esh-proc-test--detect-pty-cmd + (concat "sh -c '" + "if [ -t 0 ]; then echo stdin; fi; " + "if [ -t 1 ]; then echo stdout; fi; " + "if [ -t 2 ]; then echo stderr; fi" + "'") + "A shell command that prints the standard streams connected as TTYs.") + +;;; Tests: + + +;; Output and redirection + +(ert-deftest esh-proc-test/output/to-screen () + "Check that outputting stdout and stderr to the screen works." + (skip-unless (executable-find "sh")) + (with-temp-eshell + (eshell-match-command-output esh-proc-test--output-cmd + "stdout\nstderr\n"))) + +(ert-deftest esh-proc-test/output/stdout-to-buffer () + "Check that redirecting only stdout works." + (skip-unless (executable-find "sh")) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output + (format "%s > #<%s>" esh-proc-test--output-cmd bufname) + "stderr\n")) + (should (equal (buffer-string) "stdout\n")))) + +(ert-deftest esh-proc-test/output/stderr-to-buffer () + "Check that redirecting only stderr works." + (skip-unless (executable-find "sh")) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output + (format "%s 2> #<%s>" esh-proc-test--output-cmd bufname) + "stdout\n")) + (should (equal (buffer-string) "stderr\n")))) + +(ert-deftest esh-proc-test/output/stdout-and-stderr-to-buffer () + "Check that redirecting stdout and stderr works." + (skip-unless (executable-find "sh")) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output + (format "%s &> #<%s>" esh-proc-test--output-cmd bufname) + "\\`\\'")) + (should (equal (buffer-string) "stdout\nstderr\n")))) + + +;; Exit status + +(ert-deftest esh-proc-test/exit-status/success () + "Check that successful execution is properly recorded." + (skip-unless (executable-find "sh")) + (with-temp-eshell + (eshell-insert-command "sh -c 'exit 0'") + (eshell-wait-for-subprocess) + (should (= eshell-last-command-status 0)) + (should (eq eshell-last-command-result t)))) + +(ert-deftest esh-proc-test/exit-status/failure () + "Check that failed execution is properly recorded." + (skip-unless (executable-find "sh")) + (with-temp-eshell + (eshell-insert-command "sh -c 'exit 1'") + (eshell-wait-for-subprocess) + (should (= eshell-last-command-status 1)) + (should (eq eshell-last-command-result nil)))) + +(ert-deftest esh-proc-test/exit-status/with-stderr-pipe () + "Check that failed execution is properly recorded even with a pipe process." + (skip-unless (executable-find "sh")) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-insert-command (format "sh -c 'exit 1' > #<%s>" bufname)) + (eshell-wait-for-subprocess) + (should (= eshell-last-command-status 1)) + (should (eq eshell-last-command-result nil))))) + + +;; Pipelines + +(ert-deftest esh-proc-test/sigpipe-exits-process () + "Test that a SIGPIPE is properly sent to a process if a pipe closes" + (skip-unless (and (executable-find "sh") + (executable-find "echo") + (executable-find "sleep"))) + (with-temp-eshell + (eshell-match-command-output + ;; The first command is like `yes' but slower. This is to prevent + ;; it from taxing Emacs's process filter too much and causing a + ;; hang. Note that we use "|&" to connect the processes so that + ;; Emacs doesn't create an extra pipe process for the first "sh" + ;; invocation. + (concat "sh -c 'while true; do echo y; sleep 1; done' |& " + "sh -c 'read NAME; echo ${NAME}'") + "y\n") + (eshell-wait-for-subprocess t) + (should (eq (process-list) nil)))) + +(ert-deftest esh-proc-test/pipeline-connection-type/no-pipeline () + "Test that all streams are PTYs when a command is not in a pipeline." + (skip-unless (executable-find "sh")) + (eshell-command-result-equal + esh-proc-test--detect-pty-cmd + ;; PTYs aren't supported on MS-Windows. + (unless (eq system-type 'windows-nt) + "stdin\nstdout\nstderr\n"))) + +(ert-deftest esh-proc-test/pipeline-connection-type/first () + "Test that only stdin is a PTY when a command starts a pipeline." + (skip-unless (and (executable-find "sh") + (executable-find "cat"))) + (eshell-command-result-equal + (concat esh-proc-test--detect-pty-cmd " | cat") + (unless (eq system-type 'windows-nt) + "stdin\n"))) + +(ert-deftest esh-proc-test/pipeline-connection-type/middle () + "Test that all streams are pipes when a command is in the middle of a +pipeline." + (skip-unless (and (executable-find "sh") + (executable-find "cat"))) + ;; An `eshell-pipe-broken' signal might occur internally; let Eshell + ;; handle it! + (let ((debug-on-error nil)) + (eshell-command-result-equal + (concat "echo hi | " esh-proc-test--detect-pty-cmd " | cat") + nil))) + +(ert-deftest esh-proc-test/pipeline-connection-type/last () + "Test that only output streams are PTYs when a command ends a pipeline." + (skip-unless (executable-find "sh")) + ;; An `eshell-pipe-broken' signal might occur internally; let Eshell + ;; handle it! + (let ((debug-on-error nil)) + (eshell-command-result-equal + (concat "echo hi | " esh-proc-test--detect-pty-cmd) + (unless (eq system-type 'windows-nt) + "stdout\nstderr\n")))) + + +;; Killing processes + +(ert-deftest esh-proc-test/kill-process/foreground-only () + "Test that `eshell-kill-process' only kills foreground processes." + (with-temp-eshell + (eshell-insert-command "sleep 100 &") + (eshell-insert-command "sleep 100") + (should (equal (length eshell-process-list) 2)) + ;; This should kill only the foreground process. + (eshell-kill-process) + (eshell-wait-for-subprocess) + (should (equal (length eshell-process-list) 1)) + ;; Now kill everything, including the background process. + (eshell-process-interact 'kill-process t) + (eshell-wait-for-subprocess t) + (should (equal (length eshell-process-list) 0)))) + +(ert-deftest esh-proc-test/kill-process/background-prompt () + "Test that killing a background process doesn't emit a new +prompt. See bug#54136." + (skip-unless (and (executable-find "sh") + (executable-find "sleep"))) + (with-temp-eshell + (eshell-insert-command "sh -c 'while true; do sleep 1; done' &") + (kill-process (caar eshell-process-list)) + (eshell-wait-for-subprocess) + (should (eshell-match-output "\\[sh\\(\\.exe\\)?\\] [[:digit:]]+\n")))) + +(ert-deftest esh-proc-test/kill-pipeline () + "Test that killing a pipeline of processes only emits a single +prompt. See bug#54136." + (skip-unless (and (executable-find "sh") + (executable-find "echo") + (executable-find "sleep"))) + ;; This test doesn't work on EMBA with AOT nativecomp, but works + ;; fine elsewhere. + (skip-unless (not (getenv "EMACS_EMBA_CI"))) + (with-temp-eshell + (eshell-insert-command + (concat "sh -c 'while true; do echo y; sleep 1; done' | " + "sh -c 'while true; do read NAME; done'")) + (let ((output-start (eshell-beginning-of-output))) + (eshell-kill-process) + (eshell-wait-for-subprocess t) + (should (string-match-p + ;; "interrupt\n" is for MS-Windows. + (rx (or "interrupt\n" "killed\n" "killed: 9\n")) + (buffer-substring-no-properties + output-start (eshell-end-of-output))))))) + +(ert-deftest esh-proc-test/kill-pipeline-head () + "Test that killing the first process in a pipeline doesn't +write the exit status to the pipe. See bug#54136." + (skip-unless (and (executable-find "sh") + (executable-find "echo") + (executable-find "sleep"))) + (with-temp-eshell + (eshell-insert-command + (concat "sh -c 'while true; do sleep 1; done' | " + "sh -c 'while read NAME; do echo =${NAME}=; done'")) + (let ((output-start (eshell-beginning-of-output))) + (kill-process (eshell-head-process)) + (eshell-wait-for-subprocess t) + (should (equal (buffer-substring-no-properties + output-start (eshell-end-of-output)) + ""))))) + +;;; esh-proc-tests.el ends here diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el new file mode 100644 index 00000000000..cb5b1766bb5 --- /dev/null +++ b/test/lisp/eshell/esh-var-tests.el @@ -0,0 +1,569 @@ +;;; esh-var-tests.el --- esh-var test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests for Eshell's variable handling. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(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-var-indices () + "Interpolate list variable with 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 2 4]" + '("zero" "two" "four")))) + +(ert-deftest esh-var-test/interp-var-split-indices () + "Interpolate string variable with 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 2 4]" + '("zero" "two" "four")))) + +(ert-deftest esh-var-test/interp-var-string-split-indices () + "Interpolate string variable with string 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"))) + (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/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/interp-var-assoc () + "Interpolate alist variable with index" + (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/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-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 $<echo hi>" "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"))) + +(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-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/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-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 "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 "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 \"$<echo hi>\"" "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")) + + +;; 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 "type-of $'eshell-test-value'" + 'integer) + (eshell-command-result-equal "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 "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 "type-of \"$'eshell-test-value'\"" + 'string) + (eshell-command-result-equal "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")) + + +;; 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/last-status-var-lisp-command () + "Test using the \"last exit status\" ($?) variable with a Lisp command" + (with-temp-eshell + (eshell-match-command-output "zerop 0; echo $?" + "t\n0\n") + (eshell-match-command-output "zerop 1; echo $?" + "0\n") + (let ((debug-on-error nil)) + (eshell-match-command-output "zerop foo; echo $?" + "1\n")))) + +(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") + (let ((debug-on-error nil)) + (eshell-match-command-output "(zerop \"foo\"); echo $?" + "1\n"))))) + +(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") + (let ((debug-on-error nil)) + (eshell-match-command-output "(zerop \"foo\"); echo $?" + "1\n"))))) + +(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 + "string-join (list \"01\" \"02\") :; + $$[: 1] 3" + "01:02\n5\n") + (eshell-match-command-output + "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 "concat 01:02 03:04; + $_[0][: 1] 5" + "01:0203:04\n7\n") + (eshell-match-command-output "concat 01:02 03:04; echo \"$_[0][: 1]\"" + "01:0203:04\n02\n"))) + +;; esh-var-tests.el ends here diff --git a/test/lisp/eshell/eshell-tests-helpers.el b/test/lisp/eshell/eshell-tests-helpers.el new file mode 100644 index 00000000000..73abfcbb557 --- /dev/null +++ b/test/lisp/eshell/eshell-tests-helpers.el @@ -0,0 +1,140 @@ +;;; eshell-tests-helpers.el --- Eshell test suite helpers -*- lexical-binding:t -*- + +;; Copyright (C) 1999-2022 Free Software Foundation, Inc. + +;; Author: John Wiegley <johnw@gnu.org> + +;; 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 <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Eshell test suite helpers. + +;;; Code: + +(require 'ert) +(require 'ert-x) +(require 'esh-mode) +(require 'eshell) + +(defvar eshell-history-file-name nil) + +(defvar eshell-test--max-subprocess-time 5 + "The maximum amount of time to wait for a subprocess to finish, in seconds. +See `eshell-wait-for-subprocess'.") + +(defmacro with-temp-eshell (&rest body) + "Evaluate BODY in a temporary Eshell buffer." + `(save-current-buffer + (ert-with-temp-directory eshell-directory-name + (let* (;; We want no history file, so prevent Eshell from falling + ;; back on $HISTFILE. + (process-environment (cons "HISTFILE" process-environment)) + (eshell-history-file-name nil) + (eshell-buffer (eshell t))) + (unwind-protect + (with-current-buffer eshell-buffer + ,@body) + (let (kill-buffer-query-functions) + (kill-buffer eshell-buffer))))))) + +(defmacro eshell-with-temp-buffer (bufname text &rest body) + "Create a temporary buffer containing TEXT and evaluate BODY there. +BUFNAME will be set to the name of the temporary buffer." + (declare (indent 2)) + `(with-temp-buffer + (insert ,text) + (rename-buffer "eshell-temp-buffer" t) + (let ((,bufname (buffer-name))) + ,@body))) + +(defun eshell-wait-for-subprocess (&optional all) + "Wait until there is no interactive subprocess running in Eshell. +If ALL is non-nil, wait until there are no Eshell subprocesses at +all running. + +If this takes longer than `eshell-test--max-subprocess-time', +raise an error." + (let ((start (current-time))) + (while (if all eshell-process-list (eshell-interactive-process-p)) + (when (> (float-time (time-since start)) + eshell-test--max-subprocess-time) + (error "timed out waiting for subprocess(es)")) + (sit-for 0.1)))) + +(defun eshell-insert-command (command &optional func) + "Insert a COMMAND at the end of the buffer. +After inserting, call FUNC. If FUNC is nil, instead call +`eshell-send-input'." + (goto-char eshell-last-output-end) + (insert-and-inherit command) + (funcall (or func 'eshell-send-input))) + +(defun eshell-match-output (regexp) + "Test whether the output of the last command matches REGEXP." + (string-match-p + regexp (buffer-substring-no-properties + (eshell-beginning-of-output) (eshell-end-of-output)))) + +(defun eshell-match-output--explainer (regexp) + "Explain the result of `eshell-match-output'." + `(mismatched-output + (command ,(buffer-substring-no-properties + eshell-last-input-start eshell-last-input-end)) + (output ,(buffer-substring-no-properties + (eshell-beginning-of-output) (eshell-end-of-output))) + (regexp ,regexp))) + +(put 'eshell-match-output 'ert-explainer #'eshell-match-output--explainer) + +(defun eshell-match-command-output (command regexp &optional func) + "Insert a COMMAND at the end of the buffer and match the output with REGEXP." + (eshell-insert-command command func) + (eshell-wait-for-subprocess) + (should (eshell-match-output regexp))) + +(defvar eshell-history-file-name) + +(defun eshell-test-command-result (command) + "Like `eshell-command-result', but not using HOME." + (ert-with-temp-directory eshell-directory-name + (let ((eshell-history-file-name nil)) + (eshell-command-result command)))) + +(defun eshell-command-result--equal (_command actual expected) + "Compare the ACTUAL result of a COMMAND with its EXPECTED value." + (equal actual expected)) + +(defun eshell-command-result--equal-explainer (command actual expected) + "Explain the result of `eshell-command-result--equal'." + `(nonequal-result + (command ,command) + (result ,actual) + (expected ,expected))) + +(put 'eshell-command-result--equal 'ert-explainer + #'eshell-command-result--equal-explainer) + +(defun eshell-command-result-equal (command result) + "Execute COMMAND non-interactively and compare it to RESULT." + (should (eshell-command-result--equal + command + (eshell-test-command-result command) + result))) + +(provide 'eshell-tests-helpers) + +;;; eshell-tests-helpers.el ends here diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el index 4e0d6dc7621..d5112146c2d 100644 --- a/test/lisp/eshell/eshell-tests.el +++ b/test/lisp/eshell/eshell-tests.el @@ -1,6 +1,6 @@ -;;; tests/eshell-tests.el --- Eshell test suite +;;; eshell-tests.el --- Eshell test suite -*- lexical-binding:t -*- -;; Copyright (C) 1999-2017 Free Software Foundation, Inc. +;; Copyright (C) 1999-2022 Free Software Foundation, Inc. ;; Author: John Wiegley <johnw@gnu.org> @@ -26,176 +26,115 @@ ;;; Code: (require 'ert) +(require 'ert-x) +(require 'esh-mode) (require 'eshell) - -(defmacro with-temp-eshell (&rest body) - "Evaluate BODY in a temporary Eshell buffer." - `(let* ((eshell-directory-name (make-temp-file "eshell" t)) - (eshell-history-file-name nil) - (eshell-buffer (eshell t))) - (unwind-protect - (with-current-buffer eshell-buffer - ,@body) - (let (kill-buffer-query-functions) - (kill-buffer eshell-buffer) - (delete-directory eshell-directory-name t))))) - -(defun eshell-insert-command (text &optional func) - "Insert a command at the end of the buffer." - (goto-char eshell-last-output-end) - (insert-and-inherit text) - (funcall (or func 'eshell-send-input))) - -(defun eshell-match-result (regexp) - "Check that text after `eshell-last-input-end' matches REGEXP." - (goto-char eshell-last-input-end) - (should (string-match-p regexp (buffer-substring-no-properties - (point) (point-max))))) - -(defun eshell-command-result-p (text regexp &optional func) - "Insert a command at the end of the buffer." - (eshell-insert-command text func) - (eshell-match-result regexp)) - -(defun eshell-test-command-result (command) - "Like `eshell-command-result', but not using HOME." - (let ((eshell-directory-name (make-temp-file "eshell" t)) - (eshell-history-file-name nil)) - (unwind-protect - (eshell-command-result command) - (delete-directory eshell-directory-name t)))) +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) ;;; Tests: -(ert-deftest eshell-test/simple-command-result () - "Test `eshell-command-result' with a simple command." - (should (equal (eshell-test-command-result "+ 1 2") 3))) - -(ert-deftest eshell-test/lisp-command () - "Test `eshell-command-result' with an elisp command." - (should (equal (eshell-test-command-result "(+ 1 2)") 3))) - -(ert-deftest eshell-test/for-loop () - "Test `eshell-command-result' with a for loop.." - (let ((process-environment (cons "foo" process-environment))) - (should (equal (eshell-test-command-result - "for foo in 5 { echo $foo }") 5)))) - -(ert-deftest eshell-test/for-name-loop () ;Bug#15231 - "Test `eshell-command-result' with a for loop using `name'." - (let ((process-environment (cons "name" process-environment))) - (should (equal (eshell-test-command-result - "for name in 3 { echo $name }") 3)))) - -(ert-deftest eshell-test/for-name-shadow-loop () ; bug#15372 - "Test `eshell-command-result' with a for loop using an env-var." - (let ((process-environment (cons "name=env-value" process-environment))) - (with-temp-eshell - (eshell-command-result-p "echo $name; for name in 3 { echo $name }; echo $name" - "env-value\n3\nenv-value\n")))) - -(ert-deftest eshell-test/lisp-command-args () - "Test `eshell-command-result' with elisp and trailing args. -Test that trailing arguments outside the S-expression are -ignored. e.g. \"(+ 1 2) 3\" => 3" - (should (equal (eshell-test-command-result "(+ 1 2) 3") 3))) - -(ert-deftest eshell-test/subcommand () - "Test `eshell-command-result' with a simple subcommand." - (should (equal (eshell-test-command-result "{+ 1 2}") 3))) - -(ert-deftest eshell-test/subcommand-args () - "Test `eshell-command-result' with a subcommand and trailing args. -Test that trailing arguments outside the subcommand are ignored. -e.g. \"{+ 1 2} 3\" => 3" - (should (equal (eshell-test-command-result "{+ 1 2} 3") 3))) - -(ert-deftest eshell-test/subcommand-lisp () - "Test `eshell-command-result' with an elisp subcommand and trailing args. -Test that trailing arguments outside the subcommand are ignored. -e.g. \"{(+ 1 2)} 3\" => 3" - (should (equal (eshell-test-command-result "{(+ 1 2)} 3") 3))) - -(ert-deftest eshell-test/interp-cmd () - "Interpolate command result" - (should (equal (eshell-test-command-result "+ ${+ 1 2} 3") 6))) - -(ert-deftest eshell-test/interp-lisp () - "Interpolate Lisp form evaluation" - (should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6))) - -(ert-deftest eshell-test/interp-concat () - "Interpolate and concat command" - (should (equal (eshell-test-command-result "+ ${+ 1 2}3 3") 36))) - -(ert-deftest eshell-test/interp-concat-lisp () - "Interpolate and concat Lisp form" - (should (equal (eshell-test-command-result "+ $(+ 1 2)3 3") 36))) - -(ert-deftest eshell-test/interp-concat2 () - "Interpolate and concat two commands" - (should (equal (eshell-test-command-result "+ ${+ 1 2}${+ 1 2} 3") 36))) - -(ert-deftest eshell-test/interp-concat-lisp2 () - "Interpolate and concat two Lisp forms" - (should (equal (eshell-test-command-result "+ $(+ 1 2)$(+ 1 2) 3") 36))) - -(ert-deftest eshell-test/window-height () - "$LINES should equal (window-height)" - (should (eshell-test-command-result "= $LINES (window-height)"))) - -(ert-deftest eshell-test/window-width () - "$COLUMNS should equal (window-width)" - (should (eshell-test-command-result "= $COLUMNS (window-width)"))) - -(ert-deftest eshell-test/last-result-var () - "Test using the \"last result\" ($$) variable" +(ert-deftest eshell-test/pipe-headproc () + "Check that piping a non-process to a process command waits for the process" + (skip-unless (executable-find "cat")) + (with-temp-eshell + (eshell-match-command-output "echo hi | *cat" + "hi"))) + +(ert-deftest eshell-test/pipe-tailproc () + "Check that piping a process to a non-process command waits for the process" + (skip-unless (executable-find "echo")) (with-temp-eshell - (eshell-command-result-p "+ 1 2; + $$ 2" - "3\n5\n"))) + (eshell-match-command-output "*echo hi | echo bye" + "bye\nhi\n"))) -(ert-deftest eshell-test/last-result-var2 () - "Test using the \"last result\" ($$) variable twice" +(ert-deftest eshell-test/pipe-headproc-stdin () + "Check that standard input is sent to the head process in a pipeline" + (skip-unless (and (executable-find "tr") + (executable-find "rev"))) + (with-temp-eshell + (eshell-insert-command "tr a-z A-Z | rev") + (eshell-insert-command "hello") + (eshell-send-eof-to-process) + (eshell-wait-for-subprocess) + (should (eshell-match-output "OLLEH\n")))) + +(ert-deftest eshell-test/pipe-subcommand () + "Check that piping with an asynchronous subcommand works" + (skip-unless (and (executable-find "echo") + (executable-find "cat"))) (with-temp-eshell - (eshell-command-result-p "+ 1 2; + $$ $$" - "3\n6\n"))) + (eshell-match-command-output "echo ${*echo hi} | *cat" + "hi"))) -(ert-deftest eshell-test/last-arg-var () - "Test using the \"last arg\" ($_) variable" +(ert-deftest eshell-test/pipe-subcommand-with-pipe () + "Check that piping with an asynchronous subcommand with its own pipe works" + (skip-unless (and (executable-find "echo") + (executable-find "cat"))) (with-temp-eshell - (eshell-command-result-p "+ 1 2; + $_ 4" - "3\n6\n"))) + (eshell-match-command-output "echo ${*echo hi | *cat} | *cat" + "hi"))) + +(ert-deftest eshell-test/subcommand-reset-in-pipeline () + "Check that subcommands reset `eshell-in-pipeline-p'." + (skip-unless (executable-find "cat")) + (dolist (template '("echo {%s} | *cat" + "echo ${%s} | *cat" + "*cat $<%s> | *cat")) + (eshell-command-result-equal + (format template "echo $eshell-in-pipeline-p") + nil) + (eshell-command-result-equal + (format template "echo | echo $eshell-in-pipeline-p") + "last") + (eshell-command-result-equal + (format template "echo $eshell-in-pipeline-p | echo") + "first") + (eshell-command-result-equal + (format template "echo | echo $eshell-in-pipeline-p | echo") + "t"))) + +(ert-deftest eshell-test/lisp-reset-in-pipeline () + "Check that interpolated Lisp forms reset `eshell-in-pipeline-p'." + (skip-unless (executable-find "cat")) + (dolist (template '("echo (%s) | *cat" + "echo $(%s) | *cat")) + (eshell-command-result-equal + (format template "format \"%s\" eshell-in-pipeline-p") + "nil"))) (ert-deftest eshell-test/escape-nonspecial () "Test that \"\\c\" and \"c\" are equivalent when \"c\" is not a special character." (with-temp-eshell - (eshell-command-result-p "echo he\\llo" - "hello\n"))) + (eshell-match-command-output "echo he\\llo" + "hello\n"))) (ert-deftest eshell-test/escape-nonspecial-unicode () "Test that \"\\c\" and \"c\" are equivalent when \"c\" is a unicode character (unicode characters are nonspecial by definition)." (with-temp-eshell - (eshell-command-result-p "echo Vid\\éos" - "Vidéos\n"))) + (eshell-match-command-output "echo Vid\\éos" + "Vidéos\n"))) (ert-deftest eshell-test/escape-nonspecial-quoted () "Test that the backslash is preserved for escaped nonspecial chars" (with-temp-eshell - (eshell-command-result-p "echo \"h\\i\"" - ;; Backslashes are doubled for regexp. - "h\\\\i\n"))) + (eshell-match-command-output "echo \"h\\i\"" + ;; Backslashes are doubled for regexp. + "h\\\\i\n"))) (ert-deftest eshell-test/escape-special-quoted () "Test that the backslash is not preserved for escaped special chars" (with-temp-eshell - (eshell-command-result-p "echo \"h\\\\i\"" - ;; Backslashes are doubled for regexp. - "h\\\\i\n"))) + (eshell-match-command-output "echo \"\\\"hi\\\\\"" + ;; Backslashes are doubled for regexp. + "\\\"hi\\\\\n"))) (ert-deftest eshell-test/command-running-p () "Modeline should show no command running" @@ -229,16 +168,15 @@ chars" (> count 0)) (sit-for 1) (setq count (1- count)))) - (eshell-match-result "alpha\n"))) + (should (eshell-match-output "alpha\n")))) (ert-deftest eshell-test/flush-output () "Test flushing of previous output" (with-temp-eshell (eshell-insert-command "echo alpha") (eshell-kill-output) - (eshell-match-result (regexp-quote "*** output flushed ***\n")) - (should (forward-line)) - (should (= (point) eshell-last-output-start)))) + (should (eshell-match-output + (concat "^" (regexp-quote "*** output flushed ***\n") "$"))))) (ert-deftest eshell-test/run-old-command () "Re-run an old command" @@ -247,6 +185,6 @@ chars" (goto-char eshell-last-input-start) (string= (eshell-get-old-input) "echo alpha"))) -(provide 'esh-test) +(provide 'eshell-tests) -;;; tests/eshell-tests.el ends here +;;; eshell-tests.el ends here |