diff options
Diffstat (limited to 'test/lisp/progmodes/python-tests.el')
-rw-r--r-- | test/lisp/progmodes/python-tests.el | 1463 |
1 files changed, 1359 insertions, 104 deletions
diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index a59885637e9..fdaedb5fd7a 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el @@ -1,6 +1,6 @@ -;;; python-tests.el --- Test suite for python.el +;;; python-tests.el --- Test suite for python.el -*- lexical-binding:t -*- -;; Copyright (C) 2013-2017 Free Software Foundation, Inc. +;; Copyright (C) 2013-2022 Free Software Foundation, Inc. ;; This file is part of GNU Emacs. @@ -22,6 +22,7 @@ ;;; Code: (require 'ert) +(require 'ert-x) (require 'python) ;; Dependencies for testing: @@ -48,17 +49,17 @@ BODY is code to be executed within the temp buffer. Point is always located at the beginning of buffer." (declare (indent 1) (debug t)) ;; temp-file never actually used for anything? - `(let* ((temp-file (make-temp-file "python-tests" nil ".py")) - (buffer (find-file-noselect temp-file)) - (python-indent-guess-indent-offset nil)) - (unwind-protect - (with-current-buffer buffer - (python-mode) - (insert ,contents) - (goto-char (point-min)) - ,@body) - (and buffer (kill-buffer buffer)) - (delete-file temp-file)))) + `(ert-with-temp-file temp-file + :suffix "-python.py" + (let ((buffer (find-file-noselect temp-file)) + (python-indent-guess-indent-offset nil)) + (unwind-protect + (with-current-buffer buffer + (python-mode) + (insert ,contents) + (goto-char (point-min)) + ,@body) + (and buffer (kill-buffer buffer)))))) (defun python-tests-look-at (string &optional num restore-point) "Move point at beginning of STRING in the current buffer. @@ -94,6 +95,33 @@ STRING, it is skipped so the next STRING occurrence is selected." found-point (and restore-point (goto-char starting-point))))) +(defun python-tests-assert-faces (content faces) + "Assert that font faces for CONTENT are equal to FACES." + (python-tests-with-temp-buffer content + (font-lock-ensure nil nil) + (should (equal faces (python-tests-get-buffer-faces))))) + +(defun python-tests-get-buffer-faces () + "Return a list of (position . face) for each face change positions." + (cl-loop for pos = (point-min) + then (next-single-property-change pos 'face) + while pos + collect (cons pos (get-text-property pos 'face)))) + +(defun python-tests-assert-faces-after-change (content faces search replace) + "Assert that font faces for CONTENT are equal to FACES after change. +All occurrences of SEARCH are changed to REPLACE." + (python-tests-with-temp-buffer + content + ;; Force enable font-lock mode without jit-lock. + (rename-buffer "*python-font-lock-test*" t) + (let (noninteractive font-lock-support-mode) + (font-lock-mode)) + (while + (re-search-forward search nil t) + (replace-match replace)) + (should (equal faces (python-tests-get-buffer-faces))))) + (defun python-tests-self-insert (char-or-str) "Call `self-insert-command' for chars in CHAR-OR-STR." (let ((chars @@ -118,7 +146,6 @@ Argument MIN and MAX delimit the region to be returned and default to `point-min' and `point-max' respectively." (let* ((min (or min (point-min))) (max (or max (point-max))) - (buffer (current-buffer)) (buffer-contents (buffer-substring-no-properties min max)) (overlays (sort (overlays-in min max) @@ -134,6 +161,16 @@ default to `point-min' and `point-max' respectively." (overlay-end overlay)))) (buffer-substring-no-properties (point-min) (point-max))))) +(defun python-virt-bin (&optional virt-root) + "Return the virtualenv bin dir, starting from VIRT-ROOT. +If nil, VIRT-ROOT defaults to `python-shell-virtualenv-root'. +The name of this directory depends on `system-type'." + (expand-file-name + (concat + (file-name-as-directory (or virt-root + python-shell-virtualenv-root)) + (if (eq system-type 'windows-nt) "Scripts" "bin")))) + ;;; Tests for your tests, so you can test while you test. @@ -144,7 +181,7 @@ default to `point-min' and `point-max' respectively." sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." (let ((expected (save-excursion - (dotimes (i 3) + (dotimes (_ 3) (re-search-forward "et" nil t)) (forward-char -2) (point)))) @@ -153,7 +190,7 @@ aliqua." ;; one should be returned. (should (= (python-tests-look-at "et" 6 t) expected)) ;; If already looking at STRING, it should skip it. - (dotimes (i 2) (re-search-forward "et")) + (dotimes (_ 2) (re-search-forward "et")) (forward-char -2) (should (= (python-tests-look-at "et") expected))))) @@ -168,7 +205,7 @@ aliqua." (re-search-forward "et" nil t) (forward-char -2) (point)))) - (dotimes (i 3) + (dotimes (_ 3) (re-search-forward "et" nil t)) (should (= (python-tests-look-at "et" -3 t) expected)) (should (= (python-tests-look-at "et" -6 t) expected))))) @@ -184,7 +221,6 @@ aliqua." (ert-deftest python-syntax-after-python-backspace () ;; `python-indent-dedent-line-backspace' garbles syntax - :expected-result :failed (python-tests-with-temp-buffer "\"\"\"" (goto-char (point-max)) @@ -192,10 +228,435 @@ aliqua." (should (string= (buffer-string) "\"\"")) (should (null (nth 3 (syntax-ppss)))))) +(ert-deftest python-font-lock-keywords-level-1-1 () + (python-tests-assert-faces + "def func():" + '((1 . font-lock-keyword-face) (4) + (5 . font-lock-function-name-face) (9)))) + +(ert-deftest python-font-lock-keywords-level-1-2 () + "Invalid function name should not be font-locked." + (python-tests-assert-faces + "def 1func():" + '((1 . font-lock-keyword-face) (4)))) + +(ert-deftest python-font-lock-keywords-level-1-3 () + (python-tests-assert-faces + "def \\ + func():" + '((1 . font-lock-keyword-face) (4) + (15 . font-lock-function-name-face) (19)))) + +(ert-deftest python-font-lock-assignment-statement-1 () + (python-tests-assert-faces + "a, b, c = 1, 2, 3" + '((1 . font-lock-variable-name-face) (2) + (4 . font-lock-variable-name-face) (5) + (7 . font-lock-variable-name-face) (8)))) + +(ert-deftest python-font-lock-assignment-statement-2 () + (python-tests-assert-faces + "a, *b, c = 1, 2, 3, 4, 5" + '((1 . font-lock-variable-name-face) (2) + (5 . font-lock-variable-name-face) (6) + (8 . font-lock-variable-name-face) (9)))) + +(ert-deftest python-font-lock-assignment-statement-3 () + (python-tests-assert-faces + "[a, b] = (1, 2)" + '((1) + (2 . font-lock-variable-name-face) (3) + (5 . font-lock-variable-name-face) (6)))) + +(ert-deftest python-font-lock-assignment-statement-4 () + (python-tests-assert-faces + "(l[1], l[2]) = (10, 11)" + '((1) + (2 . font-lock-variable-name-face) (3) + (8 . font-lock-variable-name-face) (9)))) + +(ert-deftest python-font-lock-assignment-statement-5 () + (python-tests-assert-faces + "(a, b, c, *d) = *x, y = 5, 6, 7, 8, 9" + '((1) + (2 . font-lock-variable-name-face) (3) + (5 . font-lock-variable-name-face) (6) + (8 . font-lock-variable-name-face) (9) + (12 . font-lock-variable-name-face) (13) + (18 . font-lock-variable-name-face) (19) + (21 . font-lock-variable-name-face) (22)))) + +(ert-deftest python-font-lock-assignment-statement-6 () + (python-tests-assert-faces + "(a,) = 'foo'," + '((1) + (2 . font-lock-variable-name-face) (3) + (8 . font-lock-string-face) (13)))) + +(ert-deftest python-font-lock-assignment-statement-7 () + (python-tests-assert-faces + "(*a,) = ['foo', 'bar', 'baz']" + '((1) + (3 . font-lock-variable-name-face) (4) + (10 . font-lock-string-face) (15) + (17 . font-lock-string-face) (22) + (24 . font-lock-string-face) (29)))) + +(ert-deftest python-font-lock-assignment-statement-8 () + (python-tests-assert-faces + "d = D('a', ['b'], 'c')" + '((1 . font-lock-variable-name-face) (2) + (7 . font-lock-string-face) (10) + (13 . font-lock-string-face) (16) + (19 . font-lock-string-face) (22)))) + +(ert-deftest python-font-lock-assignment-statement-9 () + (python-tests-assert-faces + "d.x, d.y[0], *d.z = 'a', 'b', 'c', 'd', 'e'" + '((1) + (3 . font-lock-variable-name-face) (4) + (8 . font-lock-variable-name-face) (9) + (17 . font-lock-variable-name-face) (18) + (21 . font-lock-string-face) (24) + (26 . font-lock-string-face) (29) + (31 . font-lock-string-face) (34) + (36 . font-lock-string-face) (39) + (41 . font-lock-string-face)))) + +(ert-deftest python-font-lock-assignment-statement-10 () + (python-tests-assert-faces + "a: int = 5" + '((1 . font-lock-variable-name-face) (2) + (4 . font-lock-builtin-face) (7)))) + +(ert-deftest python-font-lock-assignment-statement-11 () + (python-tests-assert-faces + "b: Tuple[Optional[int], Union[Sequence[str], str]] = (None, 'foo')" + '((1 . font-lock-variable-name-face) (2) + (19 . font-lock-builtin-face) (22) + (40 . font-lock-builtin-face) (43) + (46 . font-lock-builtin-face) (49) + (55 . font-lock-constant-face) (59) + (61 . font-lock-string-face) (66)))) + +(ert-deftest python-font-lock-assignment-statement-12 () + (python-tests-assert-faces + "c: Collection = {1, 2, 3}" + '((1 . font-lock-variable-name-face) (2)))) + +(ert-deftest python-font-lock-assignment-statement-13 () + (python-tests-assert-faces + "d: Mapping[int, str] = {1: 'bar', 2: 'baz'}" + '((1 . font-lock-variable-name-face) (2) + (12 . font-lock-builtin-face) (15) + (17 . font-lock-builtin-face) (20) + (28 . font-lock-string-face) (33) + (38 . font-lock-string-face) (43)))) + +(ert-deftest python-font-lock-assignment-statement-14 () + (python-tests-assert-faces + "(a) = 5; (b) = 6" + '((1) + (2 . font-lock-variable-name-face) (3) + (11 . font-lock-variable-name-face) (12)))) + +(ert-deftest python-font-lock-assignment-statement-15 () + (python-tests-assert-faces + "[a] = 5,; [b] = 6," + '((1) + (2 . font-lock-variable-name-face) (3) + (12 . font-lock-variable-name-face) (13)))) + +(ert-deftest python-font-lock-assignment-statement-16 () + (python-tests-assert-faces + "[*a] = 5, 6; [*b] = 7, 8" + '((1) + (3 . font-lock-variable-name-face) (4) + (16 . font-lock-variable-name-face) (17)))) + +(ert-deftest python-font-lock-assignment-statement-17 () + (python-tests-assert-faces + "(a) = (b) = 1" + `((1) + (2 . font-lock-variable-name-face) (3) + (8 . font-lock-variable-name-face) (9)))) + +(ert-deftest python-font-lock-assignment-statement-18 () + (python-tests-assert-faces + "CustomInt = int + +def f(x: CustomInt) -> CustomInt: + y = x + 1 + ys: Sequence[CustomInt] = [y, y + 1] + res: CustomInt = sum(ys) + 1 + return res +" + '((1 . font-lock-variable-name-face) (10) + (13 . font-lock-builtin-face) (16) + (18 . font-lock-keyword-face) (21) + (22 . font-lock-function-name-face) (23) + (56 . font-lock-variable-name-face) (57) + (70 . font-lock-variable-name-face) (72) + (111 . font-lock-variable-name-face) (114) + (128 . font-lock-builtin-face) (131) + (144 . font-lock-keyword-face) (150)))) + +(ert-deftest python-font-lock-assignment-statement-multiline-1 () + (python-tests-assert-faces-after-change + " +[ + a, + b +] # ( + 1, + 2 +) +" + '((1) + (8 . font-lock-variable-name-face) (9) + (15 . font-lock-variable-name-face) (16)) + "#" "=")) + +(ert-deftest python-font-lock-assignment-statement-multiline-2 () + (python-tests-assert-faces-after-change + " +[ + *a +] # 5, 6 +" + '((1) + (9 . font-lock-variable-name-face) (10)) + "#" "=")) + +(ert-deftest python-font-lock-assignment-statement-multiline-3 () + (python-tests-assert-faces-after-change + "a\\ + ,\\ + b\\ + ,\\ + c\\ + #\\ + 1\\ + ,\\ + 2\\ + ,\\ + 3" + '((1 . font-lock-variable-name-face) (2) + (15 . font-lock-variable-name-face) (16) + (29 . font-lock-variable-name-face) (30)) + "#" "=")) + +(ert-deftest python-font-lock-assignment-statement-multiline-4 () + (python-tests-assert-faces-after-change + "a\\ + :\\ + int\\ + #\\ + 5" + '((1 . font-lock-variable-name-face) (2) + (15 . font-lock-builtin-face) (18)) + "#" "=")) + +(ert-deftest python-font-lock-assignment-statement-multiline-5 () + (python-tests-assert-faces-after-change + "(\\ + a\\ +)\\ + #\\ + 5\\ + ;\\ + (\\ + b\\ + )\\ + #\\ + 6" + '((1) + (8 . font-lock-variable-name-face) (9) + (46 . font-lock-variable-name-face) (47)) + "#" "=")) + +(ert-deftest python-font-lock-assignment-statement-multiline-6 () + (python-tests-assert-faces-after-change + "( + a +)\\ + #\\ + 5\\ + ;\\ + ( + b + )\\ + #\\ + 6" + '((1) + (7 . font-lock-variable-name-face) (8) + (43 . font-lock-variable-name-face) (44)) + "#" "=")) + +(ert-deftest python-font-lock-escape-sequence-string-newline () + (python-tests-assert-faces + "'\\n' +\"\\n\" +f'\\n' +f\"\\n\" +u'\\n' +u\"\\n\"" + '((1 . font-lock-doc-face) + (2 . font-lock-constant-face) + (4 . font-lock-doc-face) (5) + (6 . font-lock-doc-face) + (7 . font-lock-constant-face) + (9 . font-lock-doc-face) (10) + (12 . font-lock-string-face) + (13 . font-lock-constant-face) + (15 . font-lock-string-face) (16) + (18 . font-lock-string-face) + (19 . font-lock-constant-face) + (21 . font-lock-string-face) (22) + (24 . font-lock-string-face) + (25 . font-lock-constant-face) + (27 . font-lock-string-face) (28) + (30 . font-lock-string-face) + (31 . font-lock-constant-face) + (33 . font-lock-string-face)))) + +(ert-deftest python-font-lock-escape-sequence-multiline-string () + (python-tests-assert-faces + (let ((escape-sequences "\\x12 \123 \\n \\u1234 \\U00010348 \\N{Plus-Minus Sign}")) + (cl-loop for string-prefix in '("" "f" "rf" "fr" "r" "rb" "br" "b") + concat (cl-loop for quote-string in '("\"\"\"" "'''") + concat (concat string-prefix + quote-string + escape-sequences + quote-string + "\n")))) + '((1 . font-lock-doc-face) + (4 . font-lock-constant-face) + (8 . font-lock-doc-face) + (11 . font-lock-constant-face) + (13 . font-lock-doc-face) + (14 . font-lock-constant-face) + (20 . font-lock-doc-face) + (21 . font-lock-constant-face) + (31 . font-lock-doc-face) + (32 . font-lock-constant-face) + (51 . font-lock-doc-face) (54) + (55 . font-lock-doc-face) + (58 . font-lock-constant-face) + (62 . font-lock-doc-face) + (65 . font-lock-constant-face) + (67 . font-lock-doc-face) + (68 . font-lock-constant-face) + (74 . font-lock-doc-face) + (75 . font-lock-constant-face) + (85 . font-lock-doc-face) + (86 . font-lock-constant-face) + (105 . font-lock-doc-face) (108) + (110 . font-lock-string-face) + (113 . font-lock-constant-face) + (117 . font-lock-string-face) + (120 . font-lock-constant-face) + (122 . font-lock-string-face) + (123 . font-lock-constant-face) + (129 . font-lock-string-face) + (130 . font-lock-constant-face) + (140 . font-lock-string-face) + (141 . font-lock-constant-face) + (160 . font-lock-string-face) (163) + (165 . font-lock-string-face) + (168 . font-lock-constant-face) + (172 . font-lock-string-face) + (175 . font-lock-constant-face) + (177 . font-lock-string-face) + (178 . font-lock-constant-face) + (184 . font-lock-string-face) + (185 . font-lock-constant-face) + (195 . font-lock-string-face) + (196 . font-lock-constant-face) + (215 . font-lock-string-face) (218) + (221 . font-lock-string-face) (254) + (271 . font-lock-string-face) (274) + (277 . font-lock-string-face) (310) + (327 . font-lock-string-face) (330) + (333 . font-lock-string-face) (366) + (383 . font-lock-string-face) (386) + (389 . font-lock-string-face) (422) + (439 . font-lock-string-face) (442) + (444 . font-lock-string-face) (497) + (499 . font-lock-string-face) (552) + (555 . font-lock-string-face) (608) + (611 . font-lock-string-face) (664) + (667 . font-lock-string-face) (720) + (723 . font-lock-string-face) (776) + (778 . font-lock-string-face) + (781 . font-lock-constant-face) + (785 . font-lock-string-face) + (788 . font-lock-constant-face) + (790 . font-lock-string-face) (831) + (833 . font-lock-string-face) + (836 . font-lock-constant-face) + (840 . font-lock-string-face) + (843 . font-lock-constant-face) + (845 . font-lock-string-face) (886)))) + +(ert-deftest python-font-lock-escape-sequence-bytes-newline () + (python-tests-assert-faces + "b'\\n' +b\"\\n\"" + '((1) + (2 . font-lock-doc-face) + (3 . font-lock-constant-face) + (5 . font-lock-doc-face) (6) + (8 . font-lock-doc-face) + (9 . font-lock-constant-face) + (11 . font-lock-doc-face)))) + +(ert-deftest python-font-lock-escape-sequence-hex-octal () + (python-tests-assert-faces + "b'\\x12 \\777 \\1\\23' +'\\x12 \\777 \\1\\23'" + '((1) + (2 . font-lock-doc-face) + (3 . font-lock-constant-face) + (7 . font-lock-doc-face) + (8 . font-lock-constant-face) + (12 . font-lock-doc-face) + (13 . font-lock-constant-face) + (18 . font-lock-doc-face) (19) + (20 . font-lock-doc-face) + (21 . font-lock-constant-face) + (25 . font-lock-doc-face) + (26 . font-lock-constant-face) + (30 . font-lock-doc-face) + (31 . font-lock-constant-face) + (36 . font-lock-doc-face)))) + +(ert-deftest python-font-lock-escape-sequence-unicode () + (python-tests-assert-faces + "b'\\u1234 \\U00010348 \\N{Plus-Minus Sign}' +'\\u1234 \\U00010348 \\N{Plus-Minus Sign}'" + '((1) + (2 . font-lock-doc-face) (41) + (42 . font-lock-doc-face) + (43 . font-lock-constant-face) + (49 . font-lock-doc-face) + (50 . font-lock-constant-face) + (60 . font-lock-doc-face) + (61 . font-lock-constant-face) + (80 . font-lock-doc-face)))) + +(ert-deftest python-font-lock-raw-escape-sequence () + (python-tests-assert-faces + "rb'\\x12 \123 \\n' +r'\\x12 \123 \\n \\u1234 \\U00010348 \\N{Plus-Minus Sign}'" + '((1) + (3 . font-lock-doc-face) (14) + (16 . font-lock-doc-face)))) + ;;; Indentation -;; See: http://www.python.org/dev/peps/pep-0008/#indentation +;; See: https://www.python.org/dev/peps/pep-0008/#indentation (ert-deftest python-indent-pep8-1 () "First pep8 case." @@ -260,6 +721,19 @@ foo = long_function_name( (should (eq (car (python-indent-context)) :inside-paren-newline-start)) (should (= (python-indent-calculate-indentation) 4)))) +(ert-deftest python-indent-hanging-close-paren () + "Like first pep8 case, but with hanging close paren." ;; See Bug#20742. + (python-tests-with-temp-buffer + "\ +foo = long_function_name(var_one, var_two, + var_three, var_four + ) +" + (should (= (python-indent-calculate-indentation) 0)) + (python-tests-look-at ")") + (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren)) + (should (= (python-indent-calculate-indentation) 25)))) + (ert-deftest python-indent-base-case () "Check base case does not trigger errors." (python-tests-with-temp-buffer @@ -317,7 +791,7 @@ def func(arg): # I don't do much return arg # This comment is badly indented because the user forced so. - # At this line python.el wont dedent, user is always right. + # At this line python.el won't dedent, user is always right. comment_wins_over_ender = True @@ -336,7 +810,7 @@ comment_wins_over_ender = True ;; The return keyword do make indentation lose a level... (should (= (python-indent-calculate-indentation) 0)) ;; ...but the current indentation was forced by the user. - (python-tests-look-at "# At this line python.el wont dedent") + (python-tests-look-at "# At this line python.el won't dedent") (should (eq (car (python-indent-context)) :after-comment)) (should (= (python-indent-calculate-indentation) 4)) ;; Should behave the same for blank lines: potentially a comment. @@ -780,6 +1254,25 @@ def delete_all_things(): :after-backslash-dotted-continuation)) (should (= (python-indent-calculate-indentation) 16)))) +(ert-deftest python-indent-after-backslash-6 () + "Backslash continuation from for block." + (python-tests-with-temp-buffer + " +for long_variable_name \\ + in (1, 2): + print(long_variable_name) +" + (python-tests-look-at "for long_variable_name \\") + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (python-tests-look-at "in (1, 2):") + (should (eq (car (python-indent-context)) + :after-backslash-block-continuation)) + (should (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "print(long_variable_name)") + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + (ert-deftest python-indent-block-enders-1 () "Test de-indentation for pass keyword." (python-tests-with-temp-buffer @@ -921,6 +1414,35 @@ if save: (python-indent-line t) (should (= (python-indent-calculate-indentation t) 8)))) +(ert-deftest python-indent-dedenters-comment-else () + "Test de-indentation for the else keyword with comments before it." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + # comment + else + finally: + data.free() +" + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 8)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 4)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 0)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 8)))) + (ert-deftest python-indent-dedenters-3 () "Test de-indentation for the except keyword." (python-tests-with-temp-buffer @@ -1109,6 +1631,37 @@ def fn(a, b, c=True): (should (eq (car (python-indent-context)) :inside-string)) (should (= (python-indent-calculate-indentation) 4)))) +(ert-deftest python-indent-electric-comma-inside-multiline-string () + "Test indentation ...." + (python-tests-with-temp-buffer + " +a = ( + '''\ +- foo, +- bar +''' +" + (python-tests-look-at "- bar") + (should (eq (car (python-indent-context)) :inside-string)) + (goto-char (pos-eol)) + (python-tests-self-insert ",") + (should (= (current-indentation) 0)))) + +(ert-deftest python-indent-electric-comma-after-multiline-string () + "Test indentation ...." + (python-tests-with-temp-buffer + " +a = ( + '''\ +- foo, +- bar''' +" + (python-tests-look-at "- bar'''") + (should (eq (car (python-indent-context)) :inside-string)) + (goto-char (pos-eol)) + (python-tests-self-insert ",") + (should (= (current-indentation) 0)))) + (ert-deftest python-indent-electric-colon-1 () "Test indentation case from Bug#18228." (python-tests-with-temp-buffer @@ -1119,7 +1672,7 @@ def a(): def b() " (python-tests-look-at "def b()") - (goto-char (line-end-position)) + (goto-char (pos-eol)) (python-tests-self-insert ":") (should (= (current-indentation) 0)))) @@ -1130,10 +1683,13 @@ def b() if do: something() else +outside " (python-tests-look-at "else") - (goto-char (line-end-position)) + (goto-char (pos-eol)) (python-tests-self-insert ":") + (should (= (current-indentation) 0)) + (python-tests-look-at "outside") (should (= (current-indentation) 0)))) (ert-deftest python-indent-electric-colon-3 () @@ -1147,7 +1703,7 @@ if do: that) " (python-tests-look-at "that)") - (goto-char (line-end-position)) + (goto-char (pos-eol)) (python-tests-self-insert ":") (python-tests-look-at "elif" -1) (should (= (current-indentation) 0)) @@ -1172,7 +1728,7 @@ def f(): else " (python-tests-look-at "else") - (goto-char (line-end-position)) + (goto-char (pos-eol)) (python-tests-self-insert ":") (python-tests-look-at "else" -1) (should (= (current-indentation) 4)))) @@ -1293,6 +1849,60 @@ this is an arbitrarily (should (string= (buffer-substring-no-properties (point-min) (point-max)) expected))))) +(ert-deftest python-indent-after-match-block () + "Test PEP634 match." + (python-tests-with-temp-buffer + " +match foo: +" + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-after-case-block () + "Test PEP634 case." + (python-tests-with-temp-buffer + " +match foo: + case 1: +" + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 8)))) + + +;;; Filling + +(ert-deftest python-auto-fill-docstring () + (python-tests-with-temp-buffer + "\ +def some_function(arg1, + arg2): + \"\"\" + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + (auto-fill-mode +1) + (goto-char (point-max)) + (newline) + (search-backward "Lorem") + (let ((docindent (current-indentation))) + (forward-line 1) + (should (= docindent (current-indentation)))))) + +(ert-deftest python-fill-docstring () + (python-tests-with-temp-buffer + "\ +r'''aaa + +this is a test this is a test this is a test this is a test this is a test this is a test. +'''" + (search-forward "test.") + (fill-paragraph) + (should (= (current-indentation) 0)))) + ;;; Mark @@ -1378,7 +1988,7 @@ class C: (expected-mark-beginning-position (progn (python-tests-look-at "def __init__(self):") - (1- (line-beginning-position)))) + (1- (pos-bol)))) (expected-mark-end-position-1 (save-excursion (python-tests-look-at "self.b = 'b'") @@ -1435,7 +2045,7 @@ class C: (progn (python-tests-look-at "def fun(self):") (python-tests-look-at "(self):") - (1- (line-beginning-position)))) + (1- (pos-bol)))) (expected-mark-end-position (save-excursion (python-tests-look-at "return self.b") @@ -1447,6 +2057,57 @@ class C: (should (= (marker-position (mark-marker)) expected-mark-end-position))))) +(ert-deftest python-mark-defun-4 () + "Test `python-mark-defun' with nested functions." + (python-tests-with-temp-buffer + " +def foo(x): + def bar(): + return x + if True: + return bar +" + (let ((expected-mark-beginning-position + (progn + (python-tests-look-at "def foo(x):") + (1- (pos-bol)))) + (expected-mark-end-position (point-max))) + (python-tests-look-at "return bar") + (python-mark-defun 1) + (should (= (point) expected-mark-beginning-position)) + (should (= (marker-position (mark-marker)) + expected-mark-end-position))))) + +(ert-deftest python-mark-defun-5 () + "Test `python-mark-defun' with point inside backslash escaped defun." + (python-tests-with-temp-buffer + " +def \\ + foo(x): + return x +" + (let ((transient-mark-mode t) + (expected-mark-beginning-position + (progn + (python-tests-look-at "def ") + (1- (pos-bol)))) + (expected-mark-end-position + (save-excursion + (python-tests-look-at "return x") + (forward-line) + (point)))) + (python-tests-look-at "def ") + (python-mark-defun 1) + (should (= (point) expected-mark-beginning-position)) + (should (= (marker-position (mark-marker)) + expected-mark-end-position)) + (deactivate-mark) + (python-tests-look-at "foo(x)") + (python-mark-defun 1) + (should (= (point) expected-mark-beginning-position)) + (should (= (marker-position (mark-marker)) + expected-mark-end-position))))) + ;;; Navigation @@ -1473,12 +2134,20 @@ def decoratorFunctionWithArguments(arg1, arg2, arg3): return wrapped_f return wwrap " - (python-tests-look-at "return wrap") + (python-tests-look-at "return wwrap") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def decoratorFunctionWithArguments" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "return wrap" -1) (should (= (save-excursion (python-nav-beginning-of-defun) (point)) (save-excursion - (python-tests-look-at "def wrapped_f(*args):" -1) + (python-tests-look-at "def wwrap(f):" -1) (beginning-of-line) (point)))) (python-tests-look-at "def wrapped_f(*args):" -1) @@ -1512,6 +2181,9 @@ class C(object): def a(): pass + if True: + return a + def c(self): pass " @@ -1524,7 +2196,25 @@ class C(object): (python-tests-look-at "def m(self):" -1) (beginning-of-line) (point)))) + ;; Nested defuns should be skipped. + (forward-line -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def m(self):" -1) + (beginning-of-line) + (point)))) ;; Defuns on same levels should be respected. + (python-tests-look-at "if True:" -1) + (forward-line -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def a():" -1) + (beginning-of-line) + (point)))) (python-tests-look-at "def a():" -1) (should (= (save-excursion (python-nav-beginning-of-defun) @@ -1533,7 +2223,7 @@ class C(object): (python-tests-look-at "def b():" -1) (beginning-of-line) (point)))) - ;; Jump to a top level defun. + ;; Jump to an upper level defun. (python-tests-look-at "def b():" -1) (should (= (save-excursion (python-nav-beginning-of-defun) @@ -1542,6 +2232,14 @@ class C(object): (python-tests-look-at "def m(self):" -1) (beginning-of-line) (point)))) + (forward-line -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def m(self):" -1) + (beginning-of-line) + (point)))) ;; Jump to a top level defun again. (python-tests-look-at "def m(self):" -1) (should (= (save-excursion @@ -1572,6 +2270,93 @@ class C(object): (beginning-of-line) (point)))))) +(ert-deftest python-nav-beginning-of-defun-4 () + (python-tests-with-temp-buffer + " +def a(): + pass + +def \\ + b(): + return 0 + +def c(): + pass +" + (python-tests-look-at "def c():") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def \\" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "return 0" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def \\" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "b():" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def \\" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "def a():" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun -1) + (point)) + (save-excursion + (python-tests-look-at "def \\") + (beginning-of-line) + (point)))))) + +(ert-deftest python-nav-beginning-of-defun-5 () + (python-tests-with-temp-buffer + " +class C: + + def \\ + m(self): + pass +" + (python-tests-look-at "m(self):") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def \\" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "class C:" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun -1) + (point)) + (save-excursion + (python-tests-look-at "def \\") + (beginning-of-line) + (point)))))) + +(ert-deftest python-nav-beginning-of-defun-6 () + (python-tests-with-temp-buffer + " +class C: + def foo(self): + pass +" + (python-tests-look-at "self") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (beginning-of-line) + (point)))))) + (ert-deftest python-nav-end-of-defun-1 () (python-tests-with-temp-buffer " @@ -1605,12 +2390,25 @@ class C(object): (point)))) (should (= (save-excursion (python-tests-look-at "def b():") + (forward-line -1) + (python-nav-end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def c(self):") + (forward-line -1) + (point)))) + (should (= (save-excursion + (python-tests-look-at "def b():") (python-nav-end-of-defun) (point)) (save-excursion (python-tests-look-at "def b():") (forward-line 2) (point)))) + (should (not (save-excursion + (python-tests-look-at "def a():") + (forward-line -1) + (python-nav-end-of-defun)))) (should (= (save-excursion (python-tests-look-at "def c(self):") (python-nav-end-of-defun) @@ -1659,21 +2457,55 @@ def decoratorFunctionWithArguments(arg1, arg2, arg3): (point)) (save-excursion (python-tests-look-at "return wwrap") - (line-beginning-position)))) + (pos-bol)))) (should (= (save-excursion (python-tests-look-at "def wrapped_f(*args):") (python-nav-end-of-defun) (point)) (save-excursion (python-tests-look-at "return wrapped_f") - (line-beginning-position)))) + (pos-bol)))) (should (= (save-excursion (python-tests-look-at "f(*args)") (python-nav-end-of-defun) (point)) (save-excursion (python-tests-look-at "return wrapped_f") - (line-beginning-position)))))) + (pos-bol)))))) + +(ert-deftest python-nav-end-of-defun-3 () + (python-tests-with-temp-buffer + " +def \\ + a(): + return 0 +" + (should (= (save-excursion + (python-tests-look-at "def \\") + (python-nav-end-of-defun) + (point)) + (save-excursion + (point-max)))))) + +(ert-deftest python-end-of-defun-1 () + (python-tests-with-temp-buffer + " +class C: + def a(self + ): + pass + + def b(self): + pass +" + (should (= (save-excursion + (python-tests-look-at "def a") + (end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def b") + (forward-line -1) + (point)))))) (ert-deftest python-nav-backward-defun-1 () (python-tests-with-temp-buffer @@ -1773,6 +2605,18 @@ class A(object): (should (not (python-nav-backward-defun))) (should (= point (point)))))) +(ert-deftest python-nav-backward-defun-4 () + (python-tests-with-temp-buffer + " +def \\ + a(): + return 0 +" + (goto-char (point-max)) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at "def \\" -1))) + (should (not (python-nav-backward-defun))))) + (ert-deftest python-nav-forward-defun-1 () (python-tests-with-temp-buffer " @@ -1871,6 +2715,18 @@ class A(object): (should (not (python-nav-forward-defun))) (should (= point (point)))))) +(ert-deftest python-nav-forward-defun-4 () + (python-tests-with-temp-buffer + " +def \\ + a(): + return 0 +" + (goto-char (point-min)) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "():"))) + (should (not (python-nav-forward-defun))))) + (ert-deftest python-nav-beginning-of-statement-1 () (python-tests-with-temp-buffer " @@ -1948,14 +2804,14 @@ string (point)) (save-excursion (python-tests-look-at "789") - (line-end-position)))) + (pos-eol)))) (python-tests-look-at "v2 =") (should (= (save-excursion (python-nav-end-of-statement) (point)) (save-excursion (python-tests-look-at "value4)") - (line-end-position)))) + (pos-eol)))) (python-tests-look-at "v3 =") (should (= (save-excursion (python-nav-end-of-statement) @@ -1963,7 +2819,7 @@ string (save-excursion (python-tests-look-at "'continue previous line')") - (line-end-position)))) + (pos-eol)))) (python-tests-look-at "v4 =") (should (= (save-excursion (python-nav-end-of-statement) @@ -1973,6 +2829,12 @@ string (python-util-forward-comment -1) (point)))))) +(ert-deftest python-nav-end-of-statement-2 () + "Test the string overlap assertion (Bug#30964)." + (python-tests-with-temp-buffer + "'\n''\n" + (python-nav-end-of-statement))) + (ert-deftest python-nav-forward-statement-1 () (python-tests-with-temp-buffer " @@ -2122,6 +2984,28 @@ def decoratorFunctionWithArguments(arg1, arg2, arg3): (point)) (python-tests-look-at "def wwrap(f):" -1))))) +(ert-deftest python-nav-beginning-of-block-2 () + (python-tests-with-temp-buffer + " +if True: + + pass +if False: + # comment + pass +" + (python-tests-look-at "if True:") + (forward-line) + (should (= (save-excursion + (python-nav-beginning-of-block) + (point)) + (python-tests-look-at "if True:" -1))) + (python-tests-look-at "# comment") + (should (= (save-excursion + (python-nav-beginning-of-block) + (point)) + (python-tests-look-at "if False:" -1))))) + (ert-deftest python-nav-end-of-block-1 () (python-tests-with-temp-buffer " @@ -2159,21 +3043,33 @@ def decoratorFunctionWithArguments(arg1, arg2, arg3): (point)) (save-excursion (python-tests-look-at "return wrapped_f") - (line-end-position)))) + (pos-eol)))) (end-of-line) (should (= (save-excursion (python-nav-end-of-block) (point)) (save-excursion (python-tests-look-at "return wrapped_f") - (line-end-position)))) + (pos-eol)))) (python-tests-look-at "f(*args)") (should (= (save-excursion (python-nav-end-of-block) (point)) (save-excursion (python-tests-look-at "print 'After f(*args)'") - (line-end-position)))))) + (pos-eol)))))) + +(ert-deftest python-nav-end-of-block-2 () + "Ensure that `python-nav-end-of-block' does not enter an infinite loop." + (python-tests-with-temp-buffer + "def + ='' + ' +\"\"\"\"\"\" + # +'' +" + (python-nav-end-of-block))) (ert-deftest python-nav-forward-block-1 () "This also accounts as a test for `python-nav-backward-block'." @@ -2217,6 +3113,22 @@ if request.user.is_authenticated(): (python-tests-look-at "if request.user.is_authenticated():" -1))))) +(ert-deftest python-nav-forward-block-2 () + (python-tests-with-temp-buffer + " +if True: + pass +" + (python-tests-look-at "if True:") + (should (not (save-excursion (python-nav-forward-block)))) + (should (not (save-excursion (python-nav-forward-block -1)))) + (forward-char) + (should (not (save-excursion (python-nav-forward-block)))) + (should (= (save-excursion (python-nav-forward-block -1)) + (progn + (end-of-line) + (python-tests-look-at "if True:" -1)))))) + (ert-deftest python-nav-forward-sexp-1 () (python-tests-with-temp-buffer " @@ -2435,11 +3347,11 @@ if x: \tabcdefg " (python-tests-look-at "abcdefg") - (goto-char (line-end-position)) + (goto-char (pos-eol)) (call-interactively #'python-indent-dedent-line-backspace) (should (string= (buffer-substring-no-properties - (line-beginning-position) (line-end-position)) + (pos-bol) (pos-eol)) "\tabcdef"))))) (ert-deftest python-indent-dedent-line-backspace-3 () @@ -2452,27 +3364,27 @@ if x: \t abcdefg " (python-tests-look-at "abcdefg") - (goto-char (line-end-position)) + (goto-char (pos-eol)) (call-interactively #'python-indent-dedent-line-backspace) (should (string= (buffer-substring-no-properties - (line-beginning-position) (line-end-position)) + (pos-bol) (pos-eol)) "\t abcdef")) (back-to-indentation) (call-interactively #'python-indent-dedent-line-backspace) (should (string= (buffer-substring-no-properties - (line-beginning-position) (line-end-position)) + (pos-bol) (pos-eol)) "\tabcdef")) (call-interactively #'python-indent-dedent-line-backspace) (should (string= (buffer-substring-no-properties - (line-beginning-position) (line-end-position)) + (pos-bol) (pos-eol)) " abcdef")) (call-interactively #'python-indent-dedent-line-backspace) (should (string= (buffer-substring-no-properties - (line-beginning-position) (line-end-position)) + (pos-bol) (pos-eol)) "abcdef"))))) (ert-deftest python-bob-infloop-avoid () @@ -2543,58 +3455,59 @@ if x: "Test `python-shell-process-environment' modification." (let* ((python-shell-process-environment '("TESTVAR1=value1" "TESTVAR2=value2")) - (process-environment (python-shell-calculate-process-environment))) - (should (equal (getenv "TESTVAR1") "value1")) - (should (equal (getenv "TESTVAR2") "value2")))) + (env (python-shell--calculate-process-environment))) + (should (equal (getenv-internal "TESTVAR1" env) "value1")) + (should (equal (getenv-internal "TESTVAR2" env) "value2")))) (ert-deftest python-shell-calculate-process-environment-2 () "Test `python-shell-extra-pythonpaths' modification." (let* ((process-environment process-environment) - (original-pythonpath (setenv "PYTHONPATH" "/path0")) + (_original-pythonpath (setenv "PYTHONPATH" "/path0")) (python-shell-extra-pythonpaths '("/path1" "/path2")) - (process-environment (python-shell-calculate-process-environment))) - (should (equal (getenv "PYTHONPATH") + (env (python-shell--calculate-process-environment))) + (should (equal (getenv-internal "PYTHONPATH" env) (concat "/path1" path-separator "/path2" path-separator "/path0"))))) (ert-deftest python-shell-calculate-process-environment-3 () "Test `python-shell-virtualenv-root' modification." (let* ((python-shell-virtualenv-root "/env") - (process-environment + (env (let ((process-environment process-environment)) (setenv "PYTHONHOME" "/home") (setenv "VIRTUAL_ENV") - (python-shell-calculate-process-environment)))) - (should (not (getenv "PYTHONHOME"))) - (should (string= (getenv "VIRTUAL_ENV") "/env")))) + (python-shell--calculate-process-environment)))) + (should (member "PYTHONHOME" env)) + (should (string= (getenv-internal "VIRTUAL_ENV" env) "/env")))) (ert-deftest python-shell-calculate-process-environment-4 () "Test PYTHONUNBUFFERED when `python-shell-unbuffered' is non-nil." (let* ((python-shell-unbuffered t) - (process-environment + (env (let ((process-environment process-environment)) (setenv "PYTHONUNBUFFERED") - (python-shell-calculate-process-environment)))) - (should (string= (getenv "PYTHONUNBUFFERED") "1")))) + (python-shell--calculate-process-environment)))) + (should (string= (getenv-internal "PYTHONUNBUFFERED" env) "1")))) (ert-deftest python-shell-calculate-process-environment-5 () "Test PYTHONUNBUFFERED when `python-shell-unbuffered' is nil." (let* ((python-shell-unbuffered nil) - (process-environment + (env (let ((process-environment process-environment)) (setenv "PYTHONUNBUFFERED") - (python-shell-calculate-process-environment)))) - (should (not (getenv "PYTHONUNBUFFERED"))))) + (python-shell--calculate-process-environment)))) + (should (not (getenv-internal "PYTHONUNBUFFERED" env))))) (ert-deftest python-shell-calculate-process-environment-6 () "Test PYTHONUNBUFFERED=1 when `python-shell-unbuffered' is nil." (let* ((python-shell-unbuffered nil) - (process-environment + (env (let ((process-environment process-environment)) (setenv "PYTHONUNBUFFERED" "1") - (python-shell-calculate-process-environment)))) + (append (python-shell--calculate-process-environment) + process-environment)))) ;; User default settings must remain untouched: - (should (string= (getenv "PYTHONUNBUFFERED") "1")))) + (should (string= (getenv-internal "PYTHONUNBUFFERED" env) "1")))) (ert-deftest python-shell-calculate-process-environment-7 () "Test no side-effects on `process-environment'." @@ -2604,7 +3517,7 @@ if x: (python-shell-unbuffered t) (python-shell-extra-pythonpaths'("/path1" "/path2")) (original-process-environment (copy-sequence process-environment))) - (python-shell-calculate-process-environment) + (python-shell--calculate-process-environment) (should (equal process-environment original-process-environment)))) (ert-deftest python-shell-calculate-process-environment-8 () @@ -2617,7 +3530,7 @@ if x: (python-shell-extra-pythonpaths'("/path1" "/path2")) (original-process-environment (copy-sequence tramp-remote-process-environment))) - (python-shell-calculate-process-environment) + (python-shell--calculate-process-environment) (should (equal tramp-remote-process-environment original-process-environment)))) (ert-deftest python-shell-calculate-exec-path-1 () @@ -2633,7 +3546,7 @@ if x: (python-shell-virtualenv-root "/env") (new-exec-path (python-shell-calculate-exec-path))) (should (equal new-exec-path - (list (expand-file-name "/env/bin") "/path0"))))) + (list (python-virt-bin) "/path0"))))) (ert-deftest python-shell-calculate-exec-path-3 () "Test complete `python-shell-virtualenv-root' modification." @@ -2642,7 +3555,7 @@ if x: (python-shell-virtualenv-root "/env") (new-exec-path (python-shell-calculate-exec-path))) (should (equal new-exec-path - (list (expand-file-name "/env/bin") + (list (python-virt-bin) "/path1" "/path2" "/path0"))))) (ert-deftest python-shell-calculate-exec-path-4 () @@ -2653,7 +3566,7 @@ if x: (python-shell-virtualenv-root "/env") (new-exec-path (python-shell-calculate-exec-path))) (should (equal new-exec-path - (list (expand-file-name "/env/bin") + (list (python-virt-bin) "/path1" "/path2" "/path0"))))) (ert-deftest python-shell-calculate-exec-path-5 () @@ -2683,29 +3596,49 @@ if x: (python-shell-virtualenv-root "/env")) (python-shell-with-environment (should (equal exec-path - (list (expand-file-name "/env/bin") + (list (python-virt-bin) "/path1" "/path2" "/path0"))) (should (not (getenv "PYTHONHOME"))) (should (string= (getenv "VIRTUAL_ENV") "/env"))) (should (equal exec-path original-exec-path)))) +(defun python--tests-process-env-canonical (pe) + ;; `process-environment' can contain various entries for the same + ;; var, and the first in the list hides the others. + (let ((process-environment '())) + (dolist (x (reverse pe)) + (if (string-match "=" x) + (setenv (substring x 0 (match-beginning 0)) + (substring x (match-end 0))) + (setenv x nil))) + process-environment)) + +(defun python--tests-process-env-eql (pe1 pe2) + (equal (python--tests-process-env-canonical pe1) + (python--tests-process-env-canonical pe2))) + (ert-deftest python-shell-with-environment-2 () "Test environment with remote `default-directory'." (let* ((default-directory "/ssh::/example/dir/") (python-shell-remote-exec-path '("/remote1" "/remote2")) (python-shell-exec-path '("/path1" "/path2")) (tramp-remote-process-environment '("EMACS=t")) - (original-process-environment (copy-sequence tramp-remote-process-environment)) + (original-process-environment + (copy-sequence tramp-remote-process-environment)) (python-shell-virtualenv-root "/env")) (python-shell-with-environment (should (equal (python-shell-calculate-exec-path) - (list (expand-file-name "/env/bin") + (list (python-virt-bin) "/path1" "/path2" "/remote1" "/remote2"))) - (let ((process-environment (python-shell-calculate-process-environment))) + (let ((process-environment + (append (python-shell--calculate-process-environment) + tramp-remote-process-environment))) (should (not (getenv "PYTHONHOME"))) (should (string= (getenv "VIRTUAL_ENV") "/env")) - (should (equal tramp-remote-process-environment process-environment)))) - (should (equal tramp-remote-process-environment original-process-environment)))) + (should (python--tests-process-env-eql + tramp-remote-process-environment process-environment)))) + (should (equal tramp-remote-process-environment + original-process-environment)))) (ert-deftest python-shell-with-environment-3 () "Test `python-shell-with-environment' is idempotent." @@ -2714,11 +3647,14 @@ if x: (python-shell-virtualenv-root "/home/user/env") (single-call (python-shell-with-environment - (list exec-path process-environment))) + (list exec-path + (python--tests-process-env-canonical process-environment)))) (nested-call (python-shell-with-environment (python-shell-with-environment - (list exec-path process-environment))))) + (list exec-path + (python--tests-process-env-canonical + process-environment)))))) (should (equal single-call nested-call)))) (ert-deftest python-shell-make-comint-1 () @@ -3388,10 +4324,7 @@ def foo(): (should (string= (python-shell-buffer-substring (python-tests-look-at "print ('a')") (point-max)) - "if True: - - print ('a') -")))) + "# -*- coding: utf-8 -*-\nif True:\n print ('a')\n\n")))) (ert-deftest python-shell-buffer-substring-11 () "Check substring from partial block and point within indentation." @@ -3406,10 +4339,7 @@ def foo(): (backward-char 1) (point)) (point-max)) - "if True: - - print ('a') -")))) + "# -*- coding: utf-8 -*-\nif True:\n print ('a')\n\n")))) (ert-deftest python-shell-buffer-substring-12 () "Check substring from partial block and point in whitespace." @@ -3424,13 +4354,7 @@ def foo(): (should (string= (python-shell-buffer-substring (python-tests-look-at "# Whitespace") (point-max)) - "if True: - - - # Whitespace - - print ('a') -")))) + "# -*- coding: utf-8 -*-\n\nif True:\n # Whitespace\n\n print ('a')\n\n")))) @@ -3462,7 +4386,7 @@ def foo(): ;;; Code check -;;; Eldoc +;;; ElDoc (ert-deftest python-eldoc--get-symbol-at-point-1 () "Test paren handling." @@ -3473,11 +4397,11 @@ map(codecs.open('somefile' " (python-tests-look-at "ap(xx") (should (string= (python-eldoc--get-symbol-at-point) "map")) - (goto-char (line-end-position)) + (goto-char (pos-eol)) (should (string= (python-eldoc--get-symbol-at-point) "map")) (python-tests-look-at "('somefile'") (should (string= (python-eldoc--get-symbol-at-point) "map")) - (goto-char (line-end-position)) + (goto-char (pos-eol)) (should (string= (python-eldoc--get-symbol-at-point) "codecs.open")))) (ert-deftest python-eldoc--get-symbol-at-point-2 () @@ -3990,7 +4914,7 @@ def long_function_name( (should (not (python-info-beginning-of-statement-p))) (python-tests-look-at "print (var_one)") (should (python-info-beginning-of-statement-p)) - (goto-char (line-beginning-position)) + (goto-char (pos-bol)) (should (not (python-info-beginning-of-statement-p))))) (ert-deftest python-info-beginning-of-statement-p-2 () @@ -4010,7 +4934,7 @@ if width == 0 and height == 0 and \\ (should (not (python-info-beginning-of-statement-p))) (python-tests-look-at "raise ValueError(") (should (python-info-beginning-of-statement-p)) - (goto-char (line-beginning-position)) + (goto-char (pos-bol)) (should (not (python-info-beginning-of-statement-p))))) (ert-deftest python-info-end-of-statement-p-1 () @@ -4828,6 +5752,69 @@ def decorat0r(deff): (python-tests-look-at "deff()") (should (not (python-info-looking-at-beginning-of-defun))))) +(ert-deftest python-info-looking-at-beginning-of-defun-2 () + (python-tests-with-temp-buffer + " +def \\ + foo(arg): + pass +" + (python-tests-look-at "def \\") + (should (python-info-looking-at-beginning-of-defun)) + (should (python-info-looking-at-beginning-of-defun nil t)) + (python-tests-look-at "foo(arg):") + (should (not (python-info-looking-at-beginning-of-defun))) + (should (python-info-looking-at-beginning-of-defun nil t)) + (python-tests-look-at "pass") + (should (not (python-info-looking-at-beginning-of-defun))) + (should (not (python-info-looking-at-beginning-of-defun nil t))))) + +(ert-deftest python-info-looking-at-beginning-of-defun-3 () + (python-tests-with-temp-buffer + " +def foo(arg=\"default\"): # Comment + pass +" + (python-tests-look-at "arg") + (should (python-info-looking-at-beginning-of-defun)) + (python-tests-look-at "default") + (should (python-info-looking-at-beginning-of-defun)) + (python-tests-look-at "Comment") + (should (python-info-looking-at-beginning-of-defun)))) + +(ert-deftest python-info-looking-at-beginning-of-block-1 () + (python-tests-with-temp-buffer + " +def f(): + if True: + pass + l = [x * 2 + for x in range(5) + if x < 3] +# if False: +\"\"\" +if 0: +\"\"\" +" + (python-tests-look-at "def f():") + (should (python-info-looking-at-beginning-of-block)) + (forward-char) + (should (not (python-info-looking-at-beginning-of-block))) + (python-tests-look-at "if True:") + (should (python-info-looking-at-beginning-of-block)) + (forward-char) + (should (not (python-info-looking-at-beginning-of-block))) + (beginning-of-line) + (should (python-info-looking-at-beginning-of-block)) + (python-tests-look-at "for x") + (should (not (python-info-looking-at-beginning-of-block))) + (python-tests-look-at "if x < 3") + (should (not (python-info-looking-at-beginning-of-block))) + (python-tests-look-at "if False:") + (should (not (python-info-looking-at-beginning-of-block))) + (python-tests-look-at "if 0:") + (should (not (python-info-looking-at-beginning-of-block))))) + (ert-deftest python-info-current-line-comment-p-1 () (python-tests-with-temp-buffer " @@ -5192,7 +6179,7 @@ urlpatterns = patterns('', (should (= (current-indentation) 23)))) (or eim (electric-indent-mode -1))))) -(ert-deftest python-triple-quote-pairing () +(ert-deftest python-triple-double-quote-pairing () (let ((epm electric-pair-mode)) (unwind-protect (progn @@ -5219,6 +6206,33 @@ urlpatterns = patterns('', "\"\n\"\"\"\n")))) (or epm (electric-pair-mode -1))))) +(ert-deftest python-triple-single-quote-pairing () + (let ((epm electric-pair-mode)) + (unwind-protect + (progn + (python-tests-with-temp-buffer + "''\n" + (or epm (electric-pair-mode 1)) + (goto-char (1- (point-max))) + (python-tests-self-insert ?') + (should (string= (buffer-string) + "''''''\n")) + (should (= (point) 4))) + (python-tests-with-temp-buffer + "\n" + (python-tests-self-insert (list ?' ?' ?')) + (should (string= (buffer-string) + "''''''\n")) + (should (= (point) 4))) + (python-tests-with-temp-buffer + "'\n''\n" + (goto-char (1- (point-max))) + (python-tests-self-insert ?') + (should (= (point) (1- (point-max)))) + (should (string= (buffer-string) + "'\n'''\n")))) + (or epm (electric-pair-mode -1))))) + ;;; Hideshow support @@ -5254,8 +6268,11 @@ class SomeClass: class SomeClass: def __init__(self, arg, kwarg=1): + def filter(self, nums): - def __str__(self):")))) + + def __str__(self): +")))) (or enabled (hs-minor-mode -1))))) (ert-deftest python-hideshow-hide-levels-2 () @@ -5301,17 +6318,186 @@ class SomeClass: ")))) (or enabled (hs-minor-mode -1))))) +(ert-deftest python-hideshow-hide-levels-3 () + "Should hide all blocks." + (python-tests-with-temp-buffer + " +def f(): + if 0: + l = [i for i in range(5) + if i < 3] + abc = o.match(1, 2, 3) + +def g(): + pass +" + (hs-minor-mode 1) + (hs-hide-level 1) + (should + (string= + (python-tests-visible-string) + " +def f(): + +def g(): +")))) + +(ert-deftest python-hideshow-hide-levels-4 () + "Should hide 2nd level block." + (python-tests-with-temp-buffer + " +def f(): + if 0: + l = [i for i in range(5) + if i < 3] + abc = o.match(1, 2, 3) + +def g(): + pass +" + (hs-minor-mode 1) + (hs-hide-level 2) + (should + (string= + (python-tests-visible-string) + " +def f(): + if 0: + +def g(): + pass +")))) + +(ert-deftest python-hideshow-hide-all-1 () + "Should hide all blocks." + (python-tests-with-temp-buffer + "if 0: + + aaa + l = [i for i in range(5) + if i < 3] + ccc + abc = o.match(1, 2, 3) + ddd + +def f(): + pass +" + (hs-minor-mode 1) + (hs-hide-all) + (should + (string= + (python-tests-visible-string) + "if 0: + +def f(): +")))) + +(ert-deftest python-hideshow-hide-all-2 () + "Should hide comments." + (python-tests-with-temp-buffer + " +# Multi line +# comment + +\"\"\" +# Multi line +# string +\"\"\" +" + (hs-minor-mode 1) + (hs-hide-all) + (should + (string= + (python-tests-visible-string) + " +# Multi line + +\"\"\" +# Multi line +# string +\"\"\" +")))) + +(ert-deftest python-hideshow-hide-all-3 () + "Should not hide comments when `hs-hide-comments-when-hiding-all' is nil." + (python-tests-with-temp-buffer + " +# Multi line +# comment + +\"\"\" +# Multi line +# string +\"\"\" +" + (hs-minor-mode 1) + (let ((hs-hide-comments-when-hiding-all nil)) + (hs-hide-all)) + (should + (string= + (python-tests-visible-string) + " +# Multi line +# comment + +\"\"\" +# Multi line +# string +\"\"\" +")))) + +(ert-deftest python-hideshow-hide-block-1 () + "Should hide current block." + (python-tests-with-temp-buffer + " +if 0: + + aaa + l = [i for i in range(5) + if i < 3] + ccc + abc = o.match(1, 2, 3) + ddd + +def f(): + pass +" + (hs-minor-mode 1) + (python-tests-look-at "ddd") + (forward-line) + (hs-hide-block) + (should + (string= + (python-tests-visible-string) + " +if 0: + +def f(): + pass +")))) + (ert-deftest python-tests--python-nav-end-of-statement--infloop () "Checks that `python-nav-end-of-statement' doesn't infloop in a buffer with overlapping strings." + ;; FIXME: The treatment of strings has changed in the mean time, and the + ;; test below now neither signals an error nor inf-loops. + ;; The description of the problem it's trying to catch is not clear enough + ;; to be able to see if the underlying problem is really fixed, sadly. + ;; E.g. I don't know what is meant by "overlap", really. + :tags '(:unstable) (python-tests-with-temp-buffer "''' '\n''' ' '\n" (syntax-propertize (point-max)) ;; Create a situation where strings nominally overlap. This ;; shouldn't happen in practice, but apparently it can happen when ;; a package calls `syntax-ppss' in a narrowed buffer during JIT ;; lock. + ;; FIXME: 4-5 is the SPC right after the opening triple quotes: why + ;; put a string-fence syntax on it? (put-text-property 4 5 'syntax-table (string-to-syntax "|")) + ;; FIXME: 8-9 is the middle quote in the closing triple quotes: + ;; it shouldn't have any syntax-table property to remove anyway! (remove-text-properties 8 9 '(syntax-table nil)) (goto-char 4) (setq-local syntax-propertize-function nil) @@ -5321,11 +6507,80 @@ buffer with overlapping strings." (python-nav-end-of-statement))) (should (eolp)))) +;; Interactively, `run-python' focuses the buffer running the +;; interpreter. +(ert-deftest python-tests--run-python-selects-window () + "Test for bug#31398. See also bug#44421 and bug#52380." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((buffer (process-buffer (run-python nil nil 'show))) + (window (get-buffer-window buffer))) + ;; We look at `selected-window' rather than `current-buffer' + ;; because as `(elisp)Current buffer' says, the latter will only + ;; be synchronized with the former when returning to the "command + ;; loop"; until then, `current-buffer' can change arbitrarily. + (should (eq window (selected-window))) + (pop-to-buffer (other-buffer)) + (run-python nil nil 'show) + (should (eq window (selected-window))))) + +(ert-deftest python-tests--fill-long-first-line () + (should + (equal + (with-temp-buffer + (insert "def asdf(): + \"\"\"123 123 123 123 123 123 123 123 123 123 123 123 123 SHOULDBEWRAPPED 123 123 123 123 -(provide 'python-tests) + \"\"\" + a = 1 +") + (python-mode) + (goto-char (point-min)) + (forward-line 1) + (end-of-line) + (fill-paragraph) + (buffer-substring-no-properties (point-min) (point-max))) + "def asdf(): + \"\"\"123 123 123 123 123 123 123 123 123 123 123 123 123 + SHOULDBEWRAPPED 123 123 123 123 + + \"\"\" + a = 1 +"))) -;; Local Variables: -;; indent-tabs-mode: nil -;; End: + +;;; Flymake + +(ert-deftest python-tests--flymake-command-output-pattern () + (pcase-let ((`(,patt ,line ,col ,type ,msg) + python-flymake-command-output-pattern)) + ;; Pyflakes output as of version 2.4.0 + (let ((output "<stdin>:12:34 'a.b.c as d' imported but unused")) + (string-match patt output) + (should (equal (match-string line output) "12")) + (when col (should (equal (match-string col output) "34"))) + (should (equal (match-string msg output) + "'a.b.c as d' imported but unused"))) + ;; Flake8 output as of version 4.0.1 + (let ((output "stdin:12:34: F401 'a.b.c as d' imported but unused")) + (string-match patt output) + (should (equal (match-string line output) "12")) + (when col (should (equal (match-string col output) "34"))) + (when type (should (equal (match-string type output) "F401"))) + (should (equal (match-string msg output) + (if type + "'a.b.c as d' imported but unused" + "F401 'a.b.c as d' imported but unused")))) + ;; Pylint output as of version 2.14.5 + (let ((output "stdin:12:34: W0611: Unused import a.b.c (unused-import)")) + (string-match patt output) + (should (equal (match-string line output) "12")) + (when col (should (equal (match-string col output) "34"))) + (when type (should (equal (match-string type output) "W0611"))) + (should (equal (match-string msg output) + (if type + "Unused import a.b.c (unused-import)" + "W0611: Unused import a.b.c (unused-import)")))))) + +(provide 'python-tests) ;;; python-tests.el ends here |