diff options
Diffstat (limited to 'test/lisp/term-tests.el')
-rw-r--r-- | test/lisp/term-tests.el | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/test/lisp/term-tests.el b/test/lisp/term-tests.el new file mode 100644 index 00000000000..f60d2ff5747 --- /dev/null +++ b/test/lisp/term-tests.el @@ -0,0 +1,406 @@ +;;; term-tests.el --- tests for term.el -*- lexical-binding: t -*- + +;; Copyright (C) 2017, 2019-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: + + +;;; Code: +(require 'ert) +(require 'term) +(eval-when-compile (require 'cl-lib)) + +(defvar term-height) ; Number of lines in window. +(defvar term-width) ; Number of columns in window. + +(defvar yellow-fg-props + `( :foreground ,(face-foreground 'term-color-yellow nil 'default) + :background "unspecified-bg" :inverse-video nil)) +(defvar yellow-bg-props + `( :foreground "unspecified-fg" + :background ,(face-background 'term-color-yellow nil 'default) + :inverse-video nil)) +(defvar bright-yellow-fg-props + `( :foreground ,(face-foreground 'term-color-bright-yellow nil 'default) + :background "unspecified-bg" :inverse-video nil)) +(defvar bright-yellow-bg-props + `( :foreground "unspecified-fg" + :background ,(face-background 'term-color-bright-yellow nil 'default) + :inverse-video nil)) +(defvar custom-color-fg-props + `( :foreground "#87FFFF" + :background "unspecified-bg" :inverse-video nil)) + +(defvar ansi-test-strings + `(("\e[33mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face `(,yellow-fg-props))) + ("\e[43mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face `(,yellow-bg-props))) + ("\e[93mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face `(,bright-yellow-fg-props))) + ("\e[103mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face `(,bright-yellow-bg-props))) + ("\e[1;33mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,yellow-fg-props term-bold)) + ,(propertize "Hello World" 'font-lock-face + `(,bright-yellow-fg-props term-bold))) + ("\e[33;1mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,yellow-fg-props term-bold)) + ,(propertize "Hello World" 'font-lock-face + `(,bright-yellow-fg-props term-bold))) + ("\e[1m\e[33mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,yellow-fg-props term-bold)) + ,(propertize "Hello World" 'font-lock-face + `(,bright-yellow-fg-props term-bold))) + ("\e[33m\e[1mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,yellow-fg-props term-bold)) + ,(propertize "Hello World" 'font-lock-face + `(,bright-yellow-fg-props term-bold))) + ("\e[38;5;3;1mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,yellow-fg-props term-bold)) + ,(propertize "Hello World" 'font-lock-face + `(,bright-yellow-fg-props term-bold))) + ("\e[38;5;123;1mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,custom-color-fg-props term-bold))) + ("\e[38;2;135;255;255;1mHello World\e[0m" + ,(propertize "Hello World" 'font-lock-face + `(,custom-color-fg-props term-bold))))) + +(defun term-test-screen-from-input (width height input &optional return-var) + (with-temp-buffer + (term-mode) + ;; Keep dimensions independent from window size. + (remove-function (local 'window-adjust-process-window-size-function) + 'term-maybe-reset-size) + (term-exec (current-buffer) "test" "cat" nil nil) + (term-char-mode) + (setq term-width width) + (setq term-height height) + ;; Pass input directly to `term-emulate-terminal', it's easier to + ;; control chunking, and we don't have to worry about wrestling + ;; with stty settings. + (let ((proc (get-buffer-process (current-buffer)))) + ;; Don't get stuck when we close the buffer. + (set-process-query-on-exit-flag proc nil) + (if (consp input) + (mapc (lambda (input) (term-emulate-terminal proc input)) input) + (term-emulate-terminal proc input)) + (if return-var (buffer-local-value return-var (current-buffer)) + (buffer-substring (point-min) (point-max)))))) + +(ert-deftest term-simple-lines () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (let ((str "\ +first line\r +next line\r\n")) + (should (equal (term-test-screen-from-input 40 12 str) + (string-replace "\r" "" str))))) + +(ert-deftest term-carriage-return () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (let ((str "\ +first line\r_next line\r\n")) + (should (equal (term-test-screen-from-input 40 12 str) + "_next line\n")))) + +(ert-deftest term-line-wrap () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (should (string-match-p + ;; Don't be strict about trailing whitespace. + "\\`a\\{40\\}\na\\{20\\} *\\'" + (term-test-screen-from-input 40 12 (make-string 60 ?a)))) + ;; Again, but split input into chunks. + (should (string-match-p + "\\`a\\{40\\}\na\\{20\\} *\\'" + (term-test-screen-from-input 40 12 (let ((str (make-string 30 ?a))) + (list str str)))))) + +(ert-deftest term-colors () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (pcase-dolist (`(,str ,expected) ansi-test-strings) + (let ((result (term-test-screen-from-input 40 12 str))) + (should (equal result expected)) + (should (equal (text-properties-at 0 result) + (text-properties-at 0 expected)))))) + +(ert-deftest term-colors-bold-is-bright () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (let ((ansi-color-bold-is-bright t)) + (pcase-dolist (`(,str ,expected ,bright-expected) ansi-test-strings) + (let ((expected (or bright-expected expected)) + (result (term-test-screen-from-input 40 12 str))) + (should (equal result expected)) + (should (equal (text-properties-at 0 result) + (text-properties-at 0 expected))))))) + +(ert-deftest term-cursor-movement () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + ;; Absolute positioning. + (should (equal "ab\ncd" + (term-test-screen-from-input + 40 12 (concat "\e[2;2Hd" + "\e[2;1Hc" + "\e[1;2Hb" + "\e[1;1Ha")))) + ;; Send one byte at a time. + (should (equal "ab\ncd" + (term-test-screen-from-input + 40 12 (split-string (concat "\e[2;2Hd" + "\e[2;1Hc" + "\e[1;2Hb" + "\e[1;1Ha") "" t)))) + (should (equal "abcde j" + (term-test-screen-from-input + 10 12 '("abcdefghij" + "\e[H" ;move back to point-min + "abcde" + " j")))) + + ;; Relative positioning. + (should (equal "ab\ncd" + (term-test-screen-from-input + 40 12 (concat "\e[B\e[Cd" + "\e[D\e[Dc" + "\e[Ab" + "\e[D\e[Da"))))) + +(ert-deftest term-scrolling-region () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (should (equal "\ +line3 +line4 +line5 +line6 +" + (term-test-screen-from-input + 40 12 "\e[1;5r\ +line1\r +line2\r +line3\r +line4\r +line5\r +line6\r +"))) + + ;; test reverse scrolling + (should (equal "line1 +line7 +line6 +line2 +line5" + (term-test-screen-from-input 40 5 + '("\e[0;0H" + "\e[J" + "line1\r +line2\r +line3\r +line4\r +line5" + "\e[2;4r" + "\e[2;0H" + "\e[2;0H" + "\eMline6" + "\e[2;0H" + "\eMline7")))) + + ;; test scrolling down + (should (equal "line1 +line3 +line4 +line7 +line5" + (term-test-screen-from-input 40 5 + '("\e[0;0H" + "\e[J" + "line1\r +line2\r +line3\r +line4\r +line5" + "\e[2;4r" + "\e[2;0H" + "\e[4;5H" + "\n\rline7")))) + + ;; setting the scroll region end beyond the max height should not + ;; turn on term-scroll-with-delete + (should (equal "line1 +line2 +line3 +line4 +line5 +line6 +line7" + (term-test-screen-from-input 40 5 + '("\e[1;10r" + "line1\r +line2\r +line3\r +line4\r +line5\r +line6\r +line7")))) + + + ;; resetting the terminal should set the scroll region end to (1- term-height). + (should (equal " +line1 +line2 +line3 +line4 +" + (term-test-screen-from-input 40 5 + '("\e[1;10r" + "\ec" ;reset + "line1\r +line2\r +line3\r +line4\r +line5" + "\e[1;1H" + "\e[L")))) + + ;; scroll region should be limited to the (1- term-height). Note, + ;; this fixes an off by one error when comparing the scroll region + ;; end with term-height. + (should (equal " +line1 +line2 +line3 +line4 +" + (term-test-screen-from-input 40 5 + '("\e[1;6r" + "line1\r +line2\r +line3\r +line4\r +line5" + "\e[1;1H" ;go back to home + "\e[L" ;insert a new line at the top + )))) + + ;; setting the scroll region to the entire height should not turn on + ;; term-scroll-with-delete + (should (equal "line1 +line2 +line3 +line4 +line5 +line6" + (term-test-screen-from-input 40 5 + '("\e[1;5r" + "line1\r +line2\r +line3\r +line4\r +line5\r +line6")))) + + ;; reset should reset term-scroll-with-delete + (should (equal "line1 +line2 +line3 +line4 +line5 +line6 +line7" + (term-test-screen-from-input 40 5 + '("\e[2;5r" ;set the region + "\ec" ;reset + "line1\r +line2\r +line3\r +line4\r +line5\r +line6\r +line7"))))) + +(ert-deftest term-set-directory () + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (let ((term-ansi-at-user (user-real-login-name))) + (should (equal (term-test-screen-from-input + 40 12 "\eAnSiTc /foo/\n" 'default-directory) + "/foo/")) + ;; Split input (Bug#17231). + (should (equal (term-test-screen-from-input + 40 12 (list "\eAnSiTc /f" "oo/\n") 'default-directory) + "/foo/")))) + +(ert-deftest term-line-wrapping-then-motion () + "Make sure we reset the line-wrapping state after moving cursor. +A real-life example is the default zsh prompt which writes spaces +to the end of line (triggering line-wrapping state), and then +sends a carriage return followed by another space to overwrite +the first character of the line." + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (let* ((width 10) + (strs (list "x" (make-string (1- width) ?_) + "\r_"))) + (should (equal (term-test-screen-from-input width 12 strs) + (make-string width ?_))))) + +(ert-deftest term-to-margin () + "Test cursor movement at the scroll margin. +This is a reduced example from GNU nano's initial screen." + (skip-unless (not (memq system-type '(windows-nt ms-dos)))) + (let* ((width 10) + (x (make-string width ?x)) + (y (make-string width ?y))) + (should (equal (term-test-screen-from-input + width 3 + `("\e[1;3r" ; Setup 3 line scrolling region. + "\e[2;1H" ; Move to 2nd last line. + ,x ; Fill with 'x'. + "\r\e[1B" ; Next line. + ,y)) ; Fill with 'y'. + (concat "\n" x "\n" y))) + ;; Same idea, but moving upwards. + (should (equal (term-test-screen-from-input + width 3 + `("\e[1;3r" "\e[2;1H" ,x "\r\e[1A" ,y)) + (concat y "\n" x))))) + +(ert-deftest term-decode-partial () ;; Bug#25288. + "Test multibyte characters sent into multiple chunks." + ;; Set `locale-coding-system' so test will be deterministic. + (let* ((locale-coding-system 'utf-8-unix) + (string (make-string 7 ?ш)) + (bytes (encode-coding-string string locale-coding-system))) + (should (equal string + (term-test-screen-from-input + 40 1 `(,(substring bytes 0 (/ (length bytes) 2)) + ,(substring bytes (/ (length bytes) 2)))))))) +(ert-deftest term-undecodable-input () ;; Bug#29918. + "Undecodable bytes should be passed through without error." + (let* ((locale-coding-system 'utf-8-unix) ; As above. + (bytes "\376\340\360\370") + (string (decode-coding-string bytes locale-coding-system))) + (should (equal string + (term-test-screen-from-input + 40 1 bytes))))) + +(provide 'term-tests) + +;;; term-tests.el ends here |