]> code.delx.au - gnu-emacs/blobdiff - test/automated/python-tests.el
* lisp/emacs-lisp/package.el (package-unpack): Load before compiling
[gnu-emacs] / test / automated / python-tests.el
index 35a7b99e4168da4021dd402131b4fbb7535ce8d7..9da6807c144b84d2bfe2614cd6153f21fe8295d1 100644 (file)
 (require 'ert)
 (require 'python)
 
+;; Dependencies for testing:
+(require 'electric)
+(require 'hideshow)
+(require 'tramp-sh)
+
+
 (defmacro python-tests-with-temp-buffer (contents &rest body)
   "Create a `python-mode' enabled temp buffer with CONTENTS.
 BODY is code to be executed within the temp buffer.  Point is
 always located at the beginning of buffer."
   (declare (indent 1) (debug t))
   `(with-temp-buffer
-     (python-mode)
-     (insert ,contents)
-     (goto-char (point-min))
-     ,@body))
+     (let ((python-indent-guess-indent-offset nil))
+       (python-mode)
+       (insert ,contents)
+       (goto-char (point-min))
+       ,@body)))
 
 (defmacro python-tests-with-temp-file (contents &rest body)
   "Create a `python-mode' enabled file with CONTENTS.
@@ -42,7 +49,8 @@ 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)))
+          (buffer (find-file-noselect temp-file))
+          (python-indent-guess-indent-offset nil))
      (unwind-protect
          (with-current-buffer buffer
            (python-mode)
@@ -104,6 +112,28 @@ STRING, it is skipped so the next STRING occurrence is selected."
          (call-interactively 'self-insert-command)))
      chars)))
 
+(defun python-tests-visible-string (&optional min max)
+  "Return the buffer string excluding invisible overlays.
+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)
+                (lambda (a b)
+                  (let ((overlay-end-a (overlay-end a))
+                        (overlay-end-b (overlay-end b)))
+                    (> overlay-end-a overlay-end-b))))))
+    (with-temp-buffer
+      (insert buffer-contents)
+      (dolist (overlay overlays)
+        (if (overlay-get overlay 'invisible)
+            (delete-region (overlay-start overlay)
+                           (overlay-end overlay))))
+      (buffer-substring-no-properties (point-min) (point-max)))))
+
 \f
 ;;; Tests for your tests, so you can test while you test.
 
@@ -174,13 +204,13 @@ aliqua."
 foo = long_function_name(var_one, var_two,
                          var_three, var_four)
 "
-   (should (eq (car (python-indent-context)) 'no-indent))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "foo = long_function_name(var_one, var_two,")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-comment))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "var_three, var_four)")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren))
    (should (= (python-indent-calculate-indentation) 25))))
 
 (ert-deftest python-indent-pep8-2 ()
@@ -192,19 +222,22 @@ def long_function_name(
         var_four):
     print (var_one)
 "
-   (should (eq (car (python-indent-context)) 'no-indent))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "def long_function_name(")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-comment))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "var_one, var_two, var_three,")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-newline-start-from-block))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "var_four):")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-newline-start-from-block))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "print (var_one)")
-   (should (eq (car (python-indent-context)) 'after-beginning-of-block))
+   (should (eq (car (python-indent-context))
+               :after-block-start))
    (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-pep8-3 ()
@@ -215,18 +248,34 @@ foo = long_function_name(
   var_one, var_two,
   var_three, var_four)
 "
-   (should (eq (car (python-indent-context)) 'no-indent))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "foo = long_function_name(")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-comment))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "var_one, var_two,")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "var_three, var_four)")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 4))))
 
+(ert-deftest python-indent-base-case ()
+  "Check base case does not trigger errors."
+  (python-tests-with-temp-buffer
+   "
+
+"
+   (goto-char (point-min))
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))))
+
 (ert-deftest python-indent-after-comment-1 ()
   "The most simple after-comment case that shouldn't fail."
   (python-tests-with-temp-buffer
@@ -240,23 +289,23 @@ class Blag(object):
 # with the exception with which the first child failed.
 "
    (python-tests-look-at "# We only complete")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-block-end))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "# terminal state")
-   (should (eq (car (python-indent-context)) 'after-comment))
+   (should (eq (car (python-indent-context)) :after-comment))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "# with the exception")
-   (should (eq (car (python-indent-context)) 'after-comment))
+   (should (eq (car (python-indent-context)) :after-comment))
    ;; This one indents relative to previous block, even given the fact
    ;; that it was under-indented.
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "# terminal state" -1)
    ;; It doesn't hurt to check again.
-   (should (eq (car (python-indent-context)) 'after-comment))
+   (should (eq (car (python-indent-context)) :after-comment))
    (python-indent-line)
    (should (= (current-indentation) 8))
    (python-tests-look-at "# with the exception")
-   (should (eq (car (python-indent-context)) 'after-comment))
+   (should (eq (car (python-indent-context)) :after-comment))
    ;; Now everything should be lined up.
    (should (= (python-indent-calculate-indentation) 8))))
 
@@ -267,43 +316,64 @@ class Blag(object):
 def func(arg):
     # I don't do much
     return arg
-    # This comment is badly indented just because.
-    # But we won't mess with the user in this line.
+    # This comment is badly indented because the user forced so.
+    # At this line python.el wont dedent, user is always right.
 
-now_we_do_mess_cause_this_is_not_a_comment = 1
+comment_wins_over_ender = True
 
 # yeah, that.
 "
    (python-tests-look-at "# I don't do much")
-   (should (eq (car (python-indent-context)) 'after-beginning-of-block))
+   (should (eq (car (python-indent-context)) :after-block-start))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "return arg")
    ;; Comment here just gets ignored, this line is not a comment so
    ;; the rules won't apply here.
-   (should (eq (car (python-indent-context)) 'after-beginning-of-block))
+   (should (eq (car (python-indent-context)) :after-block-start))
    (should (= (python-indent-calculate-indentation) 4))
-   (python-tests-look-at "# This comment is badly")
-   (should (eq (car (python-indent-context)) 'after-line))
-   ;; The return keyword moves indentation backwards 4 spaces, but
-   ;; let's assume this comment was placed there because the user
-   ;; wanted to (manually adding spaces or whatever).
+   (python-tests-look-at "# This comment is badly indented")
+   (should (eq (car (python-indent-context)) :after-block-end))
+   ;; The return keyword do make indentation lose a level...
    (should (= (python-indent-calculate-indentation) 0))
-   (python-tests-look-at "# but we won't mess")
-   (should (eq (car (python-indent-context)) 'after-comment))
+   ;; ...but the current indentation was forced by the user.
+   (python-tests-look-at "# At this line python.el wont dedent")
+   (should (eq (car (python-indent-context)) :after-comment))
    (should (= (python-indent-calculate-indentation) 4))
-   ;; Behave the same for blank lines: potentially a comment.
+   ;; Should behave the same for blank lines: potentially a comment.
    (forward-line 1)
-   (should (eq (car (python-indent-context)) 'after-comment))
+   (should (eq (car (python-indent-context)) :after-comment))
    (should (= (python-indent-calculate-indentation) 4))
-   (python-tests-look-at "now_we_do_mess")
-   ;; Here is where comment indentation starts to get ignored and
-   ;; where the user can't freely indent anymore.
-   (should (eq (car (python-indent-context)) 'after-line))
-   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at "comment_wins_over_ender")
+   ;; The comment won over the ender because the user said so.
+   (should (eq (car (python-indent-context)) :after-comment))
+   (should (= (python-indent-calculate-indentation) 4))
+   ;; The indentation calculated fine for the assignment, but the user
+   ;; choose to force it back to the first column.  Next line should
+   ;; be aware of that.
    (python-tests-look-at "# yeah, that.")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-line))
    (should (= (python-indent-calculate-indentation) 0))))
 
+(ert-deftest python-indent-after-comment-3 ()
+  "Test after-comment in buggy case."
+  (python-tests-with-temp-buffer
+   "
+class A(object):
+
+    def something(self, arg):
+        if True:
+            return arg
+
+    # A comment
+
+    @adecorator
+    def method(self, a, b):
+        pass
+"
+   (python-tests-look-at "@adecorator")
+   (should (eq (car (python-indent-context)) :after-comment))
+   (should (= (python-indent-calculate-indentation) 4))))
+
 (ert-deftest python-indent-inside-paren-1 ()
   "The most simple inside-paren case that shouldn't fail."
   (python-tests-with-temp-buffer
@@ -325,49 +395,53 @@ data = {
 }
 "
    (python-tests-look-at "data = {")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "'key':")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "{")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "'objlist': [")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "{")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 12))
    (python-tests-look-at "'pk': 1,")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 16))
    (python-tests-look-at "'name': 'first',")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 16))
    (python-tests-look-at "},")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-at-closing-nested-paren))
    (should (= (python-indent-calculate-indentation) 12))
    (python-tests-look-at "{")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 12))
    (python-tests-look-at "'pk': 2,")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 16))
    (python-tests-look-at "'name': 'second',")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 16))
    (python-tests-look-at "}")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-at-closing-nested-paren))
    (should (= (python-indent-calculate-indentation) 12))
    (python-tests-look-at "]")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-at-closing-nested-paren))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "}")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-at-closing-nested-paren))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "}")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
    (should (= (python-indent-calculate-indentation) 0))))
 
 (ert-deftest python-indent-inside-paren-2 ()
@@ -384,43 +458,129 @@ data = {'key': {
 }}
 "
    (python-tests-look-at "data = {")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "'objlist': [")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "{'pk': 1,")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "'name': 'first'},")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren))
    (should (= (python-indent-calculate-indentation) 9))
    (python-tests-look-at "{'pk': 2,")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 8))
    (python-tests-look-at "'name': 'second'}")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren))
    (should (= (python-indent-calculate-indentation) 9))
    (python-tests-look-at "]")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-at-closing-nested-paren))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "}}")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context))
+               :inside-paren-at-closing-nested-paren))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "}")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
    (should (= (python-indent-calculate-indentation) 0))))
 
+(ert-deftest python-indent-inside-paren-3 ()
+  "The simplest case possible."
+  (python-tests-with-temp-buffer
+   "
+data = ('these',
+        'are',
+        'the',
+        'tokens')
+"
+   (python-tests-look-at "data = ('these',")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 8))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 8))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 8))))
+
+(ert-deftest python-indent-inside-paren-4 ()
+  "Respect indentation of first column."
+  (python-tests-with-temp-buffer
+   "
+data = [ [ 'these', 'are'],
+         ['the', 'tokens' ] ]
+"
+   (python-tests-look-at "data = [ [ 'these', 'are'],")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 9))))
+
+(ert-deftest python-indent-inside-paren-5 ()
+  "Test when :inside-paren initial parens are skipped in context start."
+  (python-tests-with-temp-buffer
+   "
+while ((not some_condition) and
+       another_condition):
+    do_something_interesting(
+        with_some_arg)
+"
+   (python-tests-look-at "while ((not some_condition) and")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 7))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :after-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
+   (should (= (python-indent-calculate-indentation) 8))))
+
+(ert-deftest python-indent-inside-paren-6 ()
+  "This should be aligned.."
+  (python-tests-with-temp-buffer
+   "
+CHOICES = (('some', 'choice'),
+           ('another', 'choice'),
+           ('more', 'choices'))
+"
+   (python-tests-look-at "CHOICES = (('some', 'choice'),")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 11))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :inside-paren))
+   (should (= (python-indent-calculate-indentation) 11))))
+
+(ert-deftest python-indent-inside-paren-7 ()
+  "Test for Bug#21762."
+  (python-tests-with-temp-buffer
+   "import re as myre\nvar = [\n"
+   (goto-char (point-max))
+   ;; This signals an error if the test fails
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))))
+
 (ert-deftest python-indent-after-block-1 ()
   "The most simple after-block case that shouldn't fail."
   (python-tests-with-temp-buffer
    "
 def foo(a, b, c=True):
 "
-   (should (eq (car (python-indent-context)) 'no-indent))
+   (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-beginning-of-block))
+   (should (eq (car (python-indent-context)) :after-block-start))
    (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-after-block-2 ()
@@ -432,9 +592,28 @@ def foo(a, b, c={
 }):
 "
    (goto-char (point-max))
-   (should (eq (car (python-indent-context)) 'after-beginning-of-block))
+   (should (eq (car (python-indent-context)) :after-block-start))
    (should (= (python-indent-calculate-indentation) 4))))
 
+(ert-deftest python-indent-after-block-3 ()
+  "A weird (malformed) sample, usually found in python shells."
+  (python-tests-with-temp-buffer
+   "
+In [1]:
+def func():
+pass
+
+In [2]:
+something
+"
+   (python-tests-look-at "pass")
+   (should (eq (car (python-indent-context)) :after-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "something")
+   (end-of-line)
+   (should (eq (car (python-indent-context)) :after-line))
+   (should (= (python-indent-calculate-indentation) 0))))
+
 (ert-deftest python-indent-after-backslash-1 ()
   "The most common case."
   (python-tests-with-temp-buffer
@@ -444,16 +623,16 @@ from foo.bar.baz import something, something_1 \\\\
     something_4, something_5
 "
    (python-tests-look-at "from foo.bar.baz import something, something_1")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at "something_2 something_3,")
-   (should (eq (car (python-indent-context)) 'after-backslash))
+   (should (eq (car (python-indent-context)) :after-backslash-first-line))
    (should (= (python-indent-calculate-indentation) 4))
    (python-tests-look-at "something_4, something_5")
-   (should (eq (car (python-indent-context)) 'after-backslash))
+   (should (eq (car (python-indent-context)) :after-backslash))
    (should (= (python-indent-calculate-indentation) 4))
    (goto-char (point-max))
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-line))
    (should (= (python-indent-calculate-indentation) 0))))
 
 (ert-deftest python-indent-after-backslash-2 ()
@@ -471,40 +650,104 @@ objects = Thing.objects.all() \\\\
                        .values_list()
 "
    (python-tests-look-at "objects = Thing.objects.all()")
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :no-indent))
    (should (= (python-indent-calculate-indentation) 0))
    (python-tests-look-at ".filter(")
-   (should (eq (car (python-indent-context)) 'after-backslash))
+   (should (eq (car (python-indent-context))
+               :after-backslash-dotted-continuation))
    (should (= (python-indent-calculate-indentation) 23))
    (python-tests-look-at "type='toy',")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 27))
    (python-tests-look-at "status='bought'")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 27))
    (python-tests-look-at ") \\\\")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
    (should (= (python-indent-calculate-indentation) 23))
    (python-tests-look-at ".aggregate(")
-   (should (eq (car (python-indent-context)) 'after-backslash))
+   (should (eq (car (python-indent-context))
+               :after-backslash-dotted-continuation))
    (should (= (python-indent-calculate-indentation) 23))
    (python-tests-look-at "Sum('amount')")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-newline-start))
    (should (= (python-indent-calculate-indentation) 27))
    (python-tests-look-at ") \\\\")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren))
    (should (= (python-indent-calculate-indentation) 23))
    (python-tests-look-at ".values_list()")
-   (should (eq (car (python-indent-context)) 'after-backslash))
+   (should (eq (car (python-indent-context))
+               :after-backslash-dotted-continuation))
    (should (= (python-indent-calculate-indentation) 23))
    (forward-line 1)
-   (should (eq (car (python-indent-context)) 'after-line))
+   (should (eq (car (python-indent-context)) :after-line))
    (should (= (python-indent-calculate-indentation) 0))))
 
+(ert-deftest python-indent-after-backslash-3 ()
+  "Backslash continuation from block start."
+  (python-tests-with-temp-buffer
+   "
+with open('/path/to/some/file/you/want/to/read') as file_1, \\\\
+     open('/path/to/some/file/being/written', 'w') as file_2:
+    file_2.write(file_1.read())
+"
+   (python-tests-look-at
+    "with open('/path/to/some/file/you/want/to/read') as file_1, \\\\")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at
+    "open('/path/to/some/file/being/written', 'w') as file_2")
+   (should (eq (car (python-indent-context))
+               :after-backslash-block-continuation))
+   (should (= (python-indent-calculate-indentation) 5))
+   (python-tests-look-at "file_2.write(file_1.read())")
+   (should (eq (car (python-indent-context)) :after-block-start))
+   (should (= (python-indent-calculate-indentation) 4))))
+
+(ert-deftest python-indent-after-backslash-4 ()
+  "Backslash continuation from assignment."
+  (python-tests-with-temp-buffer
+   "
+super_awful_assignment = some_calculation() and \\\\
+                         another_calculation() and \\\\
+                         some_final_calculation()
+"
+   (python-tests-look-at
+    "super_awful_assignment = some_calculation() and \\\\")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at "another_calculation() and \\\\")
+   (should (eq (car (python-indent-context))
+               :after-backslash-assignment-continuation))
+   (should (= (python-indent-calculate-indentation) 25))
+   (python-tests-look-at "some_final_calculation()")
+   (should (eq (car (python-indent-context)) :after-backslash))
+   (should (= (python-indent-calculate-indentation) 25))))
+
+(ert-deftest python-indent-after-backslash-5 ()
+  "Dotted continuation bizarre example."
+  (python-tests-with-temp-buffer
+   "
+def delete_all_things():
+    Thing \\\\
+        .objects.all() \\\\
+                .delete()
+"
+   (python-tests-look-at "Thing \\\\")
+   (should (eq (car (python-indent-context)) :after-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at ".objects.all() \\\\")
+   (should (eq (car (python-indent-context)) :after-backslash-first-line))
+   (should (= (python-indent-calculate-indentation) 8))
+   (python-tests-look-at ".delete()")
+   (should (eq (car (python-indent-context))
+               :after-backslash-dotted-continuation))
+   (should (= (python-indent-calculate-indentation) 16))))
+
 (ert-deftest python-indent-block-enders-1 ()
   "Test de-indentation for pass keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 Class foo(object):
 
     def bar(self):
@@ -516,17 +759,18 @@ Class foo(object):
         else:
             pass
 "
-    (python-tests-look-at "3)")
-    (forward-line 1)
-    (should (= (python-indent-calculate-indentation) 8))
-    (python-tests-look-at "pass")
-    (forward-line 1)
-    (should (= (python-indent-calculate-indentation) 8))))
+   (python-tests-look-at "3)")
+   (forward-line 1)
+   (should (= (python-indent-calculate-indentation) 8))
+   (python-tests-look-at "pass")
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :after-block-end))
+   (should (= (python-indent-calculate-indentation) 8))))
 
 (ert-deftest python-indent-block-enders-2 ()
   "Test de-indentation for return keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 Class foo(object):
     '''raise lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
 
@@ -539,64 +783,68 @@ Class foo(object):
                     2,
                     3)
 "
-    (python-tests-look-at "def")
-    (should (= (python-indent-calculate-indentation) 4))
-    (python-tests-look-at "if")
-    (should (= (python-indent-calculate-indentation) 8))
-    (python-tests-look-at "return")
-    (should (= (python-indent-calculate-indentation) 12))
-    (goto-char (point-max))
-    (should (= (python-indent-calculate-indentation) 8))))
+   (python-tests-look-at "def")
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "if")
+   (should (= (python-indent-calculate-indentation) 8))
+   (python-tests-look-at "return")
+   (should (= (python-indent-calculate-indentation) 12))
+   (goto-char (point-max))
+   (should (eq (car (python-indent-context)) :after-block-end))
+   (should (= (python-indent-calculate-indentation) 8))))
 
 (ert-deftest python-indent-block-enders-3 ()
   "Test de-indentation for continue keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 for element in lst:
     if element is None:
         continue
 "
-    (python-tests-look-at "if")
-    (should (= (python-indent-calculate-indentation) 4))
-    (python-tests-look-at "continue")
-    (should (= (python-indent-calculate-indentation) 8))
-    (forward-line 1)
-    (should (= (python-indent-calculate-indentation) 4))))
+   (python-tests-look-at "if")
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "continue")
+   (should (= (python-indent-calculate-indentation) 8))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :after-block-end))
+   (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-block-enders-4 ()
   "Test de-indentation for break keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 for element in lst:
     if element is None:
         break
 "
-    (python-tests-look-at "if")
-    (should (= (python-indent-calculate-indentation) 4))
-    (python-tests-look-at "break")
-    (should (= (python-indent-calculate-indentation) 8))
-    (forward-line 1)
-    (should (= (python-indent-calculate-indentation) 4))))
+   (python-tests-look-at "if")
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "break")
+   (should (= (python-indent-calculate-indentation) 8))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :after-block-end))
+   (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-block-enders-5 ()
   "Test de-indentation for raise keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 for element in lst:
     if element is None:
         raise ValueError('Element cannot be None')
 "
-    (python-tests-look-at "if")
-    (should (= (python-indent-calculate-indentation) 4))
-    (python-tests-look-at "raise")
-    (should (= (python-indent-calculate-indentation) 8))
-    (forward-line 1)
-    (should (= (python-indent-calculate-indentation) 4))))
+   (python-tests-look-at "if")
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "raise")
+   (should (= (python-indent-calculate-indentation) 8))
+   (forward-line 1)
+   (should (eq (car (python-indent-context)) :after-block-end))
+   (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-dedenters-1 ()
   "Test de-indentation for the elif keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 if save:
     try:
         write_to_disk(data)
@@ -604,15 +852,15 @@ if save:
         cleanup()
         elif
 "
-    (python-tests-look-at "elif\n")
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 0))
-    (should (equal (python-indent-calculate-levels) '(0)))))
+   (python-tests-look-at "elif\n")
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 0))
+   (should (= (python-indent-calculate-indentation t) 0))))
 
 (ert-deftest python-indent-dedenters-2 ()
   "Test de-indentation for the else keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 if save:
     try:
         write_to_disk(data)
@@ -627,43 +875,50 @@ if save:
     finally:
         data.free()
 "
-    (python-tests-look-at "else\n")
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 8))
-    (should (equal (python-indent-calculate-levels) '(0 4 8)))))
+   (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
-      "
+   "
 if save:
     try:
         write_to_disk(data)
         except
 "
-    (python-tests-look-at "except\n")
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 4))
-    (should (equal (python-indent-calculate-levels) '(4)))))
+   (python-tests-look-at "except\n")
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-indent-line t)
+   (should (= (python-indent-calculate-indentation t) 4))))
 
 (ert-deftest python-indent-dedenters-4 ()
   "Test de-indentation for the finally keyword."
   (python-tests-with-temp-buffer
-      "
+   "
 if save:
     try:
         write_to_disk(data)
         finally
 "
-    (python-tests-look-at "finally\n")
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 4))
-    (should (equal (python-indent-calculate-levels) '(4)))))
+   (python-tests-look-at "finally\n")
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-indent-line t)
+   (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-dedenters-5 ()
   "Test invalid levels are skipped in a complex example."
   (python-tests-with-temp-buffer
-      "
+   "
 if save:
     try:
         write_to_disk(data)
@@ -676,29 +931,31 @@ if save:
             do_cleanup()
         else
 "
-    (python-tests-look-at "else\n")
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 8))
-    (should (equal (python-indent-calculate-levels) '(0 8)))))
+   (python-tests-look-at "else\n")
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 8))
+   (should (= (python-indent-calculate-indentation t) 0))
+   (python-indent-line t)
+   (should (= (python-indent-calculate-indentation t) 8))))
 
 (ert-deftest python-indent-dedenters-6 ()
   "Test indentation is zero when no opening block for dedenter."
   (python-tests-with-temp-buffer
-      "
+   "
 try:
     # if save:
         write_to_disk(data)
         else
 "
-    (python-tests-look-at "else\n")
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 0))
-    (should (equal (python-indent-calculate-levels) '(0)))))
+   (python-tests-look-at "else\n")
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 0))
+   (should (= (python-indent-calculate-indentation t) 0))))
 
 (ert-deftest python-indent-dedenters-7 ()
   "Test indentation case from Bug#15163."
   (python-tests-with-temp-buffer
-      "
+   "
 if a:
     if b:
         pass
@@ -706,10 +963,10 @@ if a:
         pass
         else:
 "
-    (python-tests-look-at "else:" 2)
-    (should (eq (car (python-indent-context)) 'dedenter-statement))
-    (should (= (python-indent-calculate-indentation) 0))
-    (should (equal (python-indent-calculate-levels) '(0)))))
+   (python-tests-look-at "else:" 2)
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 0))
+   (should (= (python-indent-calculate-indentation t) 0))))
 
 (ert-deftest python-indent-dedenters-8 ()
   "Test indentation for Bug#18432."
@@ -721,10 +978,100 @@ if (a == 1 or
 elif (a == 3 or
 a == 4):
 "
+   (python-tests-look-at "elif (a == 3 or")
+   (should (eq (car (python-indent-context)) :at-dedenter-block-start))
+   (should (= (python-indent-calculate-indentation) 0))
+   (should (= (python-indent-calculate-indentation t) 0))
    (python-tests-look-at "a == 4):\n")
-   (should (eq (car (python-indent-context)) 'inside-paren))
+   (should (eq (car (python-indent-context)) :inside-paren))
    (should (= (python-indent-calculate-indentation) 6))
-   (should (equal (python-indent-calculate-levels) '(0 4 6)))))
+   (python-indent-line)
+   (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) 6))))
+
+(ert-deftest python-indent-inside-string-1 ()
+  "Test indentation for strings."
+  (python-tests-with-temp-buffer
+   "
+multiline = '''
+bunch
+of
+lines
+'''
+"
+   (python-tests-look-at "multiline = '''")
+   (should (eq (car (python-indent-context)) :no-indent))
+   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at "bunch")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at "of")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at "lines")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 0))
+   (python-tests-look-at "'''")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 0))))
+
+(ert-deftest python-indent-inside-string-2 ()
+  "Test indentation for docstrings."
+  (python-tests-with-temp-buffer
+   "
+def fn(a, b, c=True):
+    '''docstring
+    bunch
+        of
+    lines
+    '''
+"
+   (python-tests-look-at "'''docstring")
+   (should (eq (car (python-indent-context)) :after-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "bunch")
+   (should (eq (car (python-indent-context)) :inside-docstring))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "of")
+   (should (eq (car (python-indent-context)) :inside-docstring))
+   ;; Any indentation deeper than the base-indent must remain unmodified.
+   (should (= (python-indent-calculate-indentation) 8))
+   (python-tests-look-at "lines")
+   (should (eq (car (python-indent-context)) :inside-docstring))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "'''")
+   (should (eq (car (python-indent-context)) :inside-docstring))
+   (should (= (python-indent-calculate-indentation) 4))))
+
+(ert-deftest python-indent-inside-string-3 ()
+  "Test indentation for nested strings."
+  (python-tests-with-temp-buffer
+   "
+def fn(a, b, c=True):
+    some_var = '''
+    bunch
+    of
+    lines
+    '''
+"
+   (python-tests-look-at "some_var = '''")
+   (should (eq (car (python-indent-context)) :after-block-start))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "bunch")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "of")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "lines")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 4))
+   (python-tests-look-at "'''")
+   (should (eq (car (python-indent-context)) :inside-string))
+   (should (= (python-indent-calculate-indentation) 4))))
 
 (ert-deftest python-indent-electric-colon-1 ()
   "Test indentation case from Bug#18228."
@@ -830,7 +1177,7 @@ def f():
                       expected)))))
 
 (ert-deftest python-indent-region-4 ()
-  "Test region indentation block starts, dedenders and enders."
+  "Test region indentation block starts, dedenters and enders."
   (let ((contents "
 def f():
     if True:
@@ -854,21 +1201,33 @@ def f():
                       expected)))))
 
 (ert-deftest python-indent-region-5 ()
-  "Test region indentation leaves strings untouched (start delimiter)."
+  "Test region indentation for docstrings."
   (let ((contents "
 def f():
 '''
 this is
-a multiline
+        a multiline
 string
+'''
+    x = \\
+        '''
+this is an arbitrarily
+    indented multiline
+ string
 '''
 ")
         (expected "
 def f():
     '''
-this is
-a multiline
-string
+    this is
+        a multiline
+    string
+    '''
+    x = \\
+        '''
+this is an arbitrarily
+    indented multiline
+ string
 '''
 "))
     (python-tests-with-temp-buffer
@@ -877,6 +1236,158 @@ string
      (should (string= (buffer-substring-no-properties (point-min) (point-max))
                       expected)))))
 
+\f
+;;; Mark
+
+(ert-deftest python-mark-defun-1 ()
+  """Test `python-mark-defun' with point at defun symbol start."""
+  (python-tests-with-temp-buffer
+   "
+def foo(x):
+    return x
+
+class A:
+   pass
+
+class B:
+
+    def __init__(self):
+       self.b = 'b'
+
+    def fun(self):
+       return self.b
+
+class C:
+   '''docstring'''
+"
+   (let ((expected-mark-beginning-position
+          (progn
+            (python-tests-look-at "class A:")
+            (1- (point))))
+         (expected-mark-end-position-1
+          (save-excursion
+            (python-tests-look-at "pass")
+            (forward-line)
+            (point)))
+         (expected-mark-end-position-2
+          (save-excursion
+            (python-tests-look-at "return self.b")
+            (forward-line)
+            (point)))
+         (expected-mark-end-position-3
+          (save-excursion
+            (python-tests-look-at "'''docstring'''")
+            (forward-line)
+            (point))))
+     ;; Select class A only, with point at bol.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position-1))
+     ;; expand to class B, start position should remain the same.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position-2))
+     ;; expand to class C, start position should remain the same.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position-3)))))
+
+(ert-deftest python-mark-defun-2 ()
+  """Test `python-mark-defun' with point at nested defun symbol start."""
+  (python-tests-with-temp-buffer
+   "
+def foo(x):
+    return x
+
+class A:
+   pass
+
+class B:
+
+    def __init__(self):
+       self.b = 'b'
+
+    def fun(self):
+       return self.b
+
+class C:
+   '''docstring'''
+"
+   (let ((expected-mark-beginning-position
+          (progn
+            (python-tests-look-at "def __init__(self):")
+            (1- (line-beginning-position))))
+         (expected-mark-end-position-1
+          (save-excursion
+            (python-tests-look-at "self.b = 'b'")
+            (forward-line)
+            (point)))
+         (expected-mark-end-position-2
+          (save-excursion
+            (python-tests-look-at "return self.b")
+            (forward-line)
+            (point)))
+         (expected-mark-end-position-3
+          (save-excursion
+            (python-tests-look-at "'''docstring'''")
+            (forward-line)
+            (point))))
+     ;; Select B.__init only, with point at its start.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position-1))
+     ;; expand to B.fun, start position should remain the same.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position-2))
+     ;; expand to class C, start position should remain the same.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position-3)))))
+
+(ert-deftest python-mark-defun-3 ()
+  """Test `python-mark-defun' with point inside defun symbol."""
+  (python-tests-with-temp-buffer
+   "
+def foo(x):
+    return x
+
+class A:
+   pass
+
+class B:
+
+    def __init__(self):
+       self.b = 'b'
+
+    def fun(self):
+       return self.b
+
+class C:
+   '''docstring'''
+"
+   (let ((expected-mark-beginning-position
+          (progn
+            (python-tests-look-at "def fun(self):")
+            (python-tests-look-at "(self):")
+            (1- (line-beginning-position))))
+         (expected-mark-end-position
+          (save-excursion
+            (python-tests-look-at "return self.b")
+            (forward-line)
+            (point))))
+     ;; Should select B.fun, despite point is inside the defun symbol.
+     (python-mark-defun 1)
+     (should (= (point) expected-mark-beginning-position))
+     (should (= (marker-position (mark-marker))
+                expected-mark-end-position)))))
+
 \f
 ;;; Navigation
 
@@ -1650,19 +2161,36 @@ c()
    (should (save-excursion
              (beginning-of-line)
              (looking-at "c()")))
-   ;; Movement next to a paren should do what lisp does and
-   ;; unfortunately It can't change, because otherwise
-   ;; `blink-matching-open' breaks.
+   ;; The default behavior when next to a paren should do what lisp
+   ;; does and, otherwise `blink-matching-open' breaks.
    (python-nav-forward-sexp -1)
    (should (looking-at "()"))
    (should (save-excursion
              (beginning-of-line)
              (looking-at "c()")))
-   (python-nav-forward-sexp -1)
+   (end-of-line)
+   ;; Skipping parens should jump to `bolp'
+   (python-nav-forward-sexp -1 nil t)
    (should (looking-at "c()"))
+   (forward-line -1)
+   (end-of-line)
+   ;; b()
+   (python-nav-forward-sexp -1)
+   (should (looking-at "()"))
    (python-nav-forward-sexp -1)
    (should (looking-at "b()"))
+   (end-of-line)
+   (python-nav-forward-sexp -1 nil t)
+   (should (looking-at "b()"))
+   (forward-line -1)
+   (end-of-line)
+   ;; a()
+   (python-nav-forward-sexp -1)
+   (should (looking-at "()"))
    (python-nav-forward-sexp -1)
+   (should (looking-at "a()"))
+   (end-of-line)
+   (python-nav-forward-sexp -1 nil t)
    (should (looking-at "a()"))))
 
 (ert-deftest python-nav-forward-sexp-2 ()
@@ -1802,58 +2330,113 @@ def f():
    (python-nav-backward-up-list)
    (should (looking-at "def f():"))))
 
+(ert-deftest python-indent-dedent-line-backspace-1 ()
+  "Check de-indentation on first call.  Bug#18319."
+  (python-tests-with-temp-buffer
+   "
+if True:
+    x ()
+    if False:
+"
+   (python-tests-look-at "if False:")
+   (call-interactively #'python-indent-dedent-line-backspace)
+   (should (zerop (current-indentation)))
+   ;; XXX: This should be a call to `undo' but it's triggering errors.
+   (insert "    ")
+   (should (= (current-indentation) 4))
+   (call-interactively #'python-indent-dedent-line-backspace)
+   (should (zerop (current-indentation)))))
+
+(ert-deftest python-indent-dedent-line-backspace-2 ()
+  "Check de-indentation with tabs.  Bug#19730."
+  (let ((tab-width 8))
+    (python-tests-with-temp-buffer
+     "
+if x:
+\tabcdefg
+"
+     (python-tests-look-at "abcdefg")
+     (goto-char (line-end-position))
+     (call-interactively #'python-indent-dedent-line-backspace)
+     (should
+      (string= (buffer-substring-no-properties
+                (line-beginning-position) (line-end-position))
+               "\tabcdef")))))
+
+(ert-deftest python-indent-dedent-line-backspace-3 ()
+  "Paranoid check of de-indentation with tabs.  Bug#19730."
+  (let ((tab-width 8))
+    (python-tests-with-temp-buffer
+     "
+if x:
+\tif y:
+\t abcdefg
+"
+     (python-tests-look-at "abcdefg")
+     (goto-char (line-end-position))
+     (call-interactively #'python-indent-dedent-line-backspace)
+     (should
+      (string= (buffer-substring-no-properties
+                (line-beginning-position) (line-end-position))
+               "\t abcdef"))
+     (back-to-indentation)
+     (call-interactively #'python-indent-dedent-line-backspace)
+     (should
+      (string= (buffer-substring-no-properties
+                (line-beginning-position) (line-end-position))
+               "\tabcdef"))
+     (call-interactively #'python-indent-dedent-line-backspace)
+     (should
+      (string= (buffer-substring-no-properties
+                (line-beginning-position) (line-end-position))
+               "    abcdef"))
+     (call-interactively #'python-indent-dedent-line-backspace)
+     (should
+      (string= (buffer-substring-no-properties
+                (line-beginning-position) (line-end-position))
+               "abcdef")))))
+
 \f
 ;;; Shell integration
 
 (defvar python-tests-shell-interpreter "python")
 
 (ert-deftest python-shell-get-process-name-1 ()
-  "Check process name calculation on different scenarios."
+  "Check process name calculation sans `buffer-file-name'."
   (python-tests-with-temp-buffer
-      ""
-    (should (string= (python-shell-get-process-name nil)
-                     python-shell-buffer-name))
-    ;; When the `current-buffer' doesn't have `buffer-file-name', even
-    ;; if dedicated flag is non-nil should not include its name.
-    (should (string= (python-shell-get-process-name t)
-                     python-shell-buffer-name)))
+   ""
+   (should (string= (python-shell-get-process-name nil)
+                    python-shell-buffer-name))
+   (should (string= (python-shell-get-process-name t)
+                    (format "%s[%s]" python-shell-buffer-name (buffer-name))))))
+
+(ert-deftest python-shell-get-process-name-2 ()
+  "Check process name calculation with `buffer-file-name'."
   (python-tests-with-temp-file
-      ""
-    ;; `buffer-file-name' is non-nil but the dedicated flag is nil and
-    ;; should be respected.
-    (should (string= (python-shell-get-process-name nil)
-                     python-shell-buffer-name))
-    (should (string=
-             (python-shell-get-process-name t)
-             (format "%s[%s]" python-shell-buffer-name buffer-file-name)))))
+   ""
+   ;; `buffer-file-name' is non-nil but the dedicated flag is nil and
+   ;; should be respected.
+   (should (string= (python-shell-get-process-name nil)
+                    python-shell-buffer-name))
+   (should (string=
+            (python-shell-get-process-name t)
+            (format "%s[%s]" python-shell-buffer-name (buffer-name))))))
 
 (ert-deftest python-shell-internal-get-process-name-1 ()
-  "Check the internal process name is config-unique."
-  (let* ((python-shell-interpreter python-tests-shell-interpreter)
-         (python-shell-interpreter-args "")
-         (python-shell-prompt-regexp ">>> ")
-         (python-shell-prompt-block-regexp "[.][.][.] ")
-         (python-shell-setup-codes "")
-         (python-shell-process-environment "")
-         (python-shell-extra-pythonpaths "")
-         (python-shell-exec-path "")
-         (python-shell-virtualenv-path "")
-         (expected (python-tests-with-temp-buffer
-                       "" (python-shell-internal-get-process-name))))
-    ;; Same configurations should match.
-    (should
-     (string= expected
-              (python-tests-with-temp-buffer
-                  "" (python-shell-internal-get-process-name))))
-    (let ((python-shell-interpreter-args "-B"))
-      ;; A minimal change should generate different names.
-      (should
-       (not (string=
-             expected
-             (python-tests-with-temp-buffer
-                 "" (python-shell-internal-get-process-name))))))))
-
-(ert-deftest python-shell-parse-command-1 ()
+  "Check the internal process name is buffer-unique sans `buffer-file-name'."
+  (python-tests-with-temp-buffer
+   ""
+   (should (string= (python-shell-internal-get-process-name)
+                    (format "%s[%s]" python-shell-internal-buffer-name (buffer-name))))))
+
+(ert-deftest python-shell-internal-get-process-name-2 ()
+  "Check the internal process name is buffer-unique with `buffer-file-name'."
+  (python-tests-with-temp-file
+   ""
+   (should (string= (python-shell-internal-get-process-name)
+                    (format "%s[%s]" python-shell-internal-buffer-name (buffer-name))))))
+
+(ert-deftest python-shell-calculate-command-1 ()
   "Check the command to execute is calculated correctly.
 Using `python-shell-interpreter' and
 `python-shell-interpreter-args'."
@@ -1863,86 +2446,208 @@ Using `python-shell-interpreter' and
         (python-shell-interpreter-args "-B"))
     (should (string=
              (format "%s %s"
-                     python-shell-interpreter
+                     (shell-quote-argument python-shell-interpreter)
                      python-shell-interpreter-args)
-             (python-shell-parse-command)))))
+             (python-shell-calculate-command)))))
+
+(ert-deftest python-shell-calculate-pythonpath-1 ()
+  "Test PYTHONPATH calculation."
+  (let ((process-environment '("PYTHONPATH=/path0"))
+        (python-shell-extra-pythonpaths '("/path1" "/path2")))
+    (should (string= (python-shell-calculate-pythonpath)
+                     (concat "/path1" path-separator
+                             "/path2" path-separator "/path0")))))
+
+(ert-deftest python-shell-calculate-pythonpath-2 ()
+  "Test existing paths are moved to front."
+  (let ((process-environment
+         (list (concat "PYTHONPATH=/path0" path-separator "/path1")))
+        (python-shell-extra-pythonpaths '("/path1" "/path2")))
+    (should (string= (python-shell-calculate-pythonpath)
+                     (concat "/path1" path-separator
+                             "/path2" path-separator "/path0")))))
 
 (ert-deftest python-shell-calculate-process-environment-1 ()
   "Test `python-shell-process-environment' modification."
-  (let* ((original-process-environment process-environment)
-         (python-shell-process-environment
+  (let* ((python-shell-process-environment
           '("TESTVAR1=value1" "TESTVAR2=value2"))
-         (process-environment
-          (python-shell-calculate-process-environment)))
+         (process-environment (python-shell-calculate-process-environment)))
     (should (equal (getenv "TESTVAR1") "value1"))
     (should (equal (getenv "TESTVAR2") "value2"))))
 
 (ert-deftest python-shell-calculate-process-environment-2 ()
   "Test `python-shell-extra-pythonpaths' modification."
-  (let* ((original-process-environment process-environment)
-         (original-pythonpath (getenv "PYTHONPATH"))
-         (paths '("path1" "path2"))
-         (python-shell-extra-pythonpaths paths)
-         (process-environment
-          (python-shell-calculate-process-environment)))
+  (let* ((process-environment process-environment)
+         (original-pythonpath (setenv "PYTHONPATH" "/path0"))
+         (python-shell-extra-pythonpaths '("/path1" "/path2"))
+         (process-environment (python-shell-calculate-process-environment)))
     (should (equal (getenv "PYTHONPATH")
-                   (concat
-                    (mapconcat 'identity paths path-separator)
-                    path-separator original-pythonpath)))))
+                   (concat "/path1" path-separator
+                           "/path2" path-separator "/path0")))))
 
 (ert-deftest python-shell-calculate-process-environment-3 ()
-  "Test `python-shell-virtualenv-path' modification."
-  (let* ((original-process-environment process-environment)
-         (original-path (or (getenv "PATH") ""))
-         (python-shell-virtualenv-path
-          (directory-file-name user-emacs-directory))
+  "Test `python-shell-virtualenv-root' modification."
+  (let* ((python-shell-virtualenv-root "/env")
          (process-environment
-          (python-shell-calculate-process-environment)))
+          (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") python-shell-virtualenv-path))
-    (should (equal (getenv "PATH")
-                   (format "%s/bin%s%s"
-                           python-shell-virtualenv-path
-                           path-separator original-path)))))
+    (should (string= (getenv "VIRTUAL_ENV") "/env"))))
 
 (ert-deftest python-shell-calculate-process-environment-4 ()
-  "Test `python-shell-unbuffered' modification."
-  (setenv "PYTHONUNBUFFERED")
-  (let* ((process-environment
-          (python-shell-calculate-process-environment)))
-    ;; Defaults to t
-    (should python-shell-unbuffered)
+  "Test PYTHONUNBUFFERED when `python-shell-unbuffered' is non-nil."
+  (let* ((python-shell-unbuffered t)
+         (process-environment
+          (let ((process-environment process-environment))
+            (setenv "PYTHONUNBUFFERED")
+            (python-shell-calculate-process-environment))))
     (should (string= (getenv "PYTHONUNBUFFERED") "1"))))
 
 (ert-deftest python-shell-calculate-process-environment-5 ()
-  (setenv "PYTHONUNBUFFERED")
-  "Test `python-shell-unbuffered' modification."
+  "Test PYTHONUNBUFFERED when `python-shell-unbuffered' is nil."
   (let* ((python-shell-unbuffered nil)
          (process-environment
-          (python-shell-calculate-process-environment)))
+          (let ((process-environment process-environment))
+            (setenv "PYTHONUNBUFFERED")
+            (python-shell-calculate-process-environment))))
     (should (not (getenv "PYTHONUNBUFFERED")))))
 
+(ert-deftest python-shell-calculate-process-environment-6 ()
+  "Test PYTHONUNBUFFERED=1 when `python-shell-unbuffered' is nil."
+  (let* ((python-shell-unbuffered nil)
+         (process-environment
+          (let ((process-environment process-environment))
+            (setenv "PYTHONUNBUFFERED" "1")
+            (python-shell-calculate-process-environment))))
+    ;; User default settings must remain untouched:
+    (should (string= (getenv "PYTHONUNBUFFERED") "1"))))
+
+(ert-deftest python-shell-calculate-process-environment-7 ()
+  "Test no side-effects on `process-environment'."
+  (let* ((python-shell-process-environment
+          '("TESTVAR1=value1" "TESTVAR2=value2"))
+         (python-shell-virtualenv-root "/env")
+         (python-shell-unbuffered t)
+         (python-shell-extra-pythonpaths'("/path1" "/path2"))
+         (original-process-environment (copy-sequence process-environment)))
+    (python-shell-calculate-process-environment)
+    (should (equal process-environment original-process-environment))))
+
+(ert-deftest python-shell-calculate-process-environment-8 ()
+  "Test no side-effects on `tramp-remote-process-environment'."
+  (let* ((default-directory "/ssh::/example/dir/")
+         (python-shell-process-environment
+          '("TESTVAR1=value1" "TESTVAR2=value2"))
+         (python-shell-virtualenv-root "/env")
+         (python-shell-unbuffered t)
+         (python-shell-extra-pythonpaths'("/path1" "/path2"))
+         (original-process-environment
+          (copy-sequence tramp-remote-process-environment)))
+    (python-shell-calculate-process-environment)
+    (should (equal tramp-remote-process-environment original-process-environment))))
+
 (ert-deftest python-shell-calculate-exec-path-1 ()
   "Test `python-shell-exec-path' modification."
-  (let* ((original-exec-path exec-path)
-         (python-shell-exec-path '("path1" "path2"))
-         (exec-path (python-shell-calculate-exec-path)))
-    (should (equal
-             exec-path
-             (append python-shell-exec-path
-                     original-exec-path)))))
+  (let* ((exec-path '("/path0"))
+         (python-shell-exec-path '("/path1" "/path2"))
+         (new-exec-path (python-shell-calculate-exec-path)))
+    (should (equal new-exec-path '("/path1" "/path2" "/path0")))))
 
 (ert-deftest python-shell-calculate-exec-path-2 ()
-  "Test `python-shell-exec-path' modification."
-  (let* ((original-exec-path exec-path)
-         (python-shell-virtualenv-path
-          (directory-file-name (expand-file-name user-emacs-directory)))
-         (exec-path (python-shell-calculate-exec-path)))
-    (should (equal
-             exec-path
-             (append (cons
-                      (format "%s/bin" python-shell-virtualenv-path)
-                      original-exec-path))))))
+  "Test `python-shell-virtualenv-root' modification."
+  (let* ((exec-path '("/path0"))
+         (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")))))
+
+(ert-deftest python-shell-calculate-exec-path-3 ()
+  "Test complete `python-shell-virtualenv-root' modification."
+  (let* ((exec-path '("/path0"))
+         (python-shell-exec-path '("/path1" "/path2"))
+         (python-shell-virtualenv-root "/env")
+         (new-exec-path (python-shell-calculate-exec-path)))
+    (should (equal new-exec-path
+                   (list (expand-file-name "/env/bin")
+                         "/path1" "/path2" "/path0")))))
+
+(ert-deftest python-shell-calculate-exec-path-4 ()
+  "Test complete `python-shell-virtualenv-root' with remote."
+  (let* ((default-directory "/ssh::/example/dir/")
+         (python-shell-remote-exec-path '("/path0"))
+         (python-shell-exec-path '("/path1" "/path2"))
+         (python-shell-virtualenv-root "/env")
+         (new-exec-path (python-shell-calculate-exec-path)))
+    (should (equal new-exec-path
+                   (list (expand-file-name "/env/bin")
+                         "/path1" "/path2" "/path0")))))
+
+(ert-deftest python-shell-calculate-exec-path-5 ()
+  "Test no side-effects on `exec-path'."
+  (let* ((exec-path '("/path0"))
+         (python-shell-exec-path '("/path1" "/path2"))
+         (python-shell-virtualenv-root "/env")
+         (original-exec-path (copy-sequence exec-path)))
+    (python-shell-calculate-exec-path)
+    (should (equal exec-path original-exec-path))))
+
+(ert-deftest python-shell-calculate-exec-path-6 ()
+  "Test no side-effects on `python-shell-remote-exec-path'."
+  (let* ((default-directory "/ssh::/example/dir/")
+         (python-shell-remote-exec-path '("/path0"))
+         (python-shell-exec-path '("/path1" "/path2"))
+         (python-shell-virtualenv-root "/env")
+         (original-exec-path (copy-sequence python-shell-remote-exec-path)))
+    (python-shell-calculate-exec-path)
+    (should (equal python-shell-remote-exec-path original-exec-path))))
+
+(ert-deftest python-shell-with-environment-1 ()
+  "Test environment with local `default-directory'."
+  (let* ((exec-path '("/path0"))
+         (python-shell-exec-path '("/path1" "/path2"))
+         (original-exec-path exec-path)
+         (python-shell-virtualenv-root "/env"))
+    (python-shell-with-environment
+     (should (equal exec-path
+                    (list (expand-file-name "/env/bin")
+                          "/path1" "/path2" "/path0")))
+      (should (not (getenv "PYTHONHOME")))
+      (should (string= (getenv "VIRTUAL_ENV") "/env")))
+    (should (equal exec-path original-exec-path))))
+
+(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))
+         (python-shell-virtualenv-root "/env"))
+    (python-shell-with-environment
+      (should (equal (python-shell-calculate-exec-path)
+                     (list (expand-file-name "/env/bin")
+                           "/path1" "/path2" "/remote1" "/remote2")))
+      (let ((process-environment (python-shell-calculate-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))))
+
+(ert-deftest python-shell-with-environment-3 ()
+  "Test `python-shell-with-environment' is idempotent."
+  (let* ((python-shell-extra-pythonpaths '("/example/dir/"))
+         (python-shell-exec-path '("path1" "path2"))
+         (python-shell-virtualenv-root "/home/user/env")
+         (single-call
+          (python-shell-with-environment
+            (list exec-path process-environment)))
+         (nested-call
+          (python-shell-with-environment
+            (python-shell-with-environment
+              (list exec-path process-environment)))))
+    (should (equal single-call nested-call))))
 
 (ert-deftest python-shell-make-comint-1 ()
   "Check comint creation for global shell buffer."
@@ -1957,7 +2662,7 @@ Using `python-shell-interpreter' and
          (shell-buffer
           (python-tests-with-temp-buffer
            "" (python-shell-make-comint
-               (python-shell-parse-command) proc-name)))
+               (python-shell-calculate-command) proc-name)))
          (process (get-buffer-process shell-buffer)))
     (unwind-protect
         (progn
@@ -1978,7 +2683,7 @@ Using `python-shell-interpreter' and
          (shell-buffer
           (python-tests-with-temp-buffer
            "" (python-shell-make-comint
-               (python-shell-parse-command) proc-name nil t)))
+               (python-shell-calculate-command) proc-name nil t)))
          (process (get-buffer-process shell-buffer)))
     (unwind-protect
         (progn
@@ -2045,7 +2750,7 @@ and `python-shell-interpreter-args' in the new shell buffer."
             (setenv "PYTHONSTARTUP" startup-file)
             (python-tests-with-temp-buffer
              "" (python-shell-make-comint
-                 (python-shell-parse-command) proc-name nil))))
+                 (python-shell-calculate-command) proc-name nil))))
          (process (get-buffer-process shell-buffer)))
     (unwind-protect
         (progn
@@ -2075,10 +2780,10 @@ and `python-shell-interpreter-args' in the new shell buffer."
            (dedicated-proc-name (python-shell-get-process-name t))
            (global-shell-buffer
             (python-shell-make-comint
-             (python-shell-parse-command) global-proc-name))
+             (python-shell-calculate-command) global-proc-name))
            (dedicated-shell-buffer
             (python-shell-make-comint
-             (python-shell-parse-command) dedicated-proc-name))
+             (python-shell-calculate-command) dedicated-proc-name))
            (global-process (get-buffer-process global-shell-buffer))
            (dedicated-process (get-buffer-process dedicated-shell-buffer)))
       (unwind-protect
@@ -2096,84 +2801,6 @@ and `python-shell-interpreter-args' in the new shell buffer."
         (ignore-errors (kill-buffer global-shell-buffer))
         (ignore-errors (kill-buffer dedicated-shell-buffer))))))
 
-(ert-deftest python-shell-get-or-create-process-1 ()
-  "Check shell dedicated process creation."
-  (skip-unless (executable-find python-tests-shell-interpreter))
-  (python-tests-with-temp-file
-   ""
-   (let* ((cmd
-           (concat (executable-find python-tests-shell-interpreter) " -i"))
-          (use-dialog-box)
-          (dedicated-process-name (python-shell-get-process-name t))
-          (dedicated-process (python-shell-get-or-create-process cmd t))
-          (dedicated-shell-buffer (process-buffer dedicated-process)))
-     (unwind-protect
-         (progn
-           (set-process-query-on-exit-flag dedicated-process nil)
-           ;; should be dedicated.
-           (should (equal (process-name dedicated-process)
-                          dedicated-process-name))
-           (kill-buffer dedicated-shell-buffer)
-           ;; Check there are no processes for current buffer.
-           (should (not (python-shell-get-process))))
-       (ignore-errors (kill-buffer dedicated-shell-buffer))))))
-
-(ert-deftest python-shell-get-or-create-process-2 ()
-  "Check shell global process creation."
-  (skip-unless (executable-find python-tests-shell-interpreter))
-  (python-tests-with-temp-file
-   ""
-   (let* ((cmd
-           (concat (executable-find python-tests-shell-interpreter) " -i"))
-          (use-dialog-box)
-          (process-name (python-shell-get-process-name nil))
-          (process (python-shell-get-or-create-process cmd))
-          (shell-buffer (process-buffer process)))
-     (unwind-protect
-         (progn
-           (set-process-query-on-exit-flag process nil)
-           ;; should be global.
-           (should (equal (process-name process) process-name))
-           (kill-buffer shell-buffer)
-           ;; Check there are no processes for current buffer.
-           (should (not (python-shell-get-process))))
-       (ignore-errors (kill-buffer shell-buffer))))))
-
-(ert-deftest python-shell-get-or-create-process-3 ()
-  "Check shell dedicated/global process preference."
-  (skip-unless (executable-find python-tests-shell-interpreter))
-  (python-tests-with-temp-file
-   ""
-   (let* ((cmd
-           (concat (executable-find python-tests-shell-interpreter) " -i"))
-          (python-shell-interpreter python-tests-shell-interpreter)
-          (use-dialog-box)
-          (dedicated-process-name (python-shell-get-process-name t))
-          (global-process)
-          (dedicated-process))
-     (progn
-       ;; Create global process
-       (run-python cmd nil)
-       (setq global-process (get-buffer-process "*Python*"))
-       (should global-process)
-       (set-process-query-on-exit-flag global-process nil)
-       ;; Create dedicated process
-       (run-python cmd t)
-       (setq dedicated-process (get-process dedicated-process-name))
-       (should dedicated-process)
-       (set-process-query-on-exit-flag dedicated-process nil)
-       ;; Prefer dedicated.
-       (should (equal (python-shell-get-or-create-process)
-                      dedicated-process))
-       ;; Kill the dedicated so the global takes over.
-       (kill-buffer (process-buffer dedicated-process))
-       ;; Detect global.
-       (should (equal (python-shell-get-or-create-process) global-process))
-       ;; Kill the global.
-       (kill-buffer (process-buffer global-process))
-       ;; Check there are no processes for current buffer.
-       (should (not (python-shell-get-process)))))))
-
 (ert-deftest python-shell-internal-get-or-create-process-1 ()
   "Check internal shell process creation fallback."
   (skip-unless (executable-find python-tests-shell-interpreter))
@@ -2303,7 +2930,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-input-regexps'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-input-regexps'")))))
 
 (ert-deftest python-shell-prompt-validate-regexps-2 ()
   "Check `python-shell-prompt-output-regexps' are validated."
@@ -2312,7 +2940,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-output-regexps'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-output-regexps'")))))
 
 (ert-deftest python-shell-prompt-validate-regexps-3 ()
   "Check `python-shell-prompt-regexp' is validated."
@@ -2321,7 +2950,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-regexp'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-regexp'")))))
 
 (ert-deftest python-shell-prompt-validate-regexps-4 ()
   "Check `python-shell-prompt-block-regexp' is validated."
@@ -2330,7 +2960,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-block-regexp'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-block-regexp'")))))
 
 (ert-deftest python-shell-prompt-validate-regexps-5 ()
   "Check `python-shell-prompt-pdb-regexp' is validated."
@@ -2339,7 +2970,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-pdb-regexp'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-pdb-regexp'")))))
 
 (ert-deftest python-shell-prompt-validate-regexps-6 ()
   "Check `python-shell-prompt-output-regexp' is validated."
@@ -2348,7 +2980,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-output-regexp'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-output-regexp'")))))
 
 (ert-deftest python-shell-prompt-validate-regexps-7 ()
   "Check default regexps are valid."
@@ -2365,7 +2998,8 @@ and `python-shell-interpreter-args' in the new shell buffer."
                                    :type 'user-error)))
     (should
      (string= (cadr error-data)
-              "Invalid regexp \\( in `python-shell-prompt-output-regexp'"))))
+              (format-message
+               "Invalid regexp \\( in `python-shell-prompt-output-regexp'")))))
 
 (ert-deftest python-shell-prompt-set-calculated-regexps-2 ()
   "Check `python-shell-prompt-input-regexps' are set."
@@ -2672,9 +3306,71 @@ class Foo(models.Model):
 
 "))))
 
+(ert-deftest python-shell-buffer-substring-10 ()
+  "Check substring from partial block."
+  (python-tests-with-temp-buffer
+   "
+def foo():
+    print ('a')
+"
+   (should (string= (python-shell-buffer-substring
+                     (python-tests-look-at "print ('a')")
+                     (point-max))
+                    "if True:
+
+    print ('a')
+"))))
+
+(ert-deftest python-shell-buffer-substring-11 ()
+  "Check substring from partial block and point within indentation."
+  (python-tests-with-temp-buffer
+   "
+def foo():
+    print ('a')
+"
+   (should (string= (python-shell-buffer-substring
+                     (progn
+                       (python-tests-look-at "print ('a')")
+                       (backward-char 1)
+                       (point))
+                     (point-max))
+                    "if True:
+
+    print ('a')
+"))))
+
+(ert-deftest python-shell-buffer-substring-12 ()
+  "Check substring from partial block and point in whitespace."
+  (python-tests-with-temp-buffer
+   "
+def foo():
+
+        # Whitespace
+
+    print ('a')
+"
+   (should (string= (python-shell-buffer-substring
+                     (python-tests-look-at "# Whitespace")
+                     (point-max))
+                    "if True:
+
+
+        # Whitespace
+
+    print ('a')
+"))))
+
+
 \f
 ;;; Shell completion
 
+(ert-deftest python-shell-completion-native-interpreter-disabled-p-1 ()
+  (let* ((python-shell-completion-native-disabled-interpreters (list "pypy"))
+         (python-shell-interpreter "/some/path/to/bin/pypy"))
+    (should (python-shell-completion-native-interpreter-disabled-p))))
+
+
+
 \f
 ;;; PDB Track integration
 
@@ -2696,6 +3392,63 @@ class Foo(models.Model):
 \f
 ;;; Eldoc
 
+(ert-deftest python-eldoc--get-symbol-at-point-1 ()
+  "Test paren handling."
+  (python-tests-with-temp-buffer
+   "
+map(xx
+map(codecs.open('somefile'
+"
+   (python-tests-look-at "ap(xx")
+   (should (string= (python-eldoc--get-symbol-at-point) "map"))
+   (goto-char (line-end-position))
+   (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))
+   (should (string= (python-eldoc--get-symbol-at-point) "codecs.open"))))
+
+(ert-deftest python-eldoc--get-symbol-at-point-2 ()
+  "Ensure self is replaced with the class name."
+  (python-tests-with-temp-buffer
+   "
+class TheClass:
+
+    def some_method(self, n):
+        return n
+
+    def other(self):
+        return self.some_method(1234)
+
+"
+   (python-tests-look-at "self.some_method")
+   (should (string= (python-eldoc--get-symbol-at-point)
+                    "TheClass.some_method"))
+   (python-tests-look-at "1234)")
+   (should (string= (python-eldoc--get-symbol-at-point)
+                    "TheClass.some_method"))))
+
+(ert-deftest python-eldoc--get-symbol-at-point-3 ()
+  "Ensure symbol is found when point is at end of buffer."
+  (python-tests-with-temp-buffer
+   "
+some_symbol
+
+"
+   (goto-char (point-max))
+   (should (string= (python-eldoc--get-symbol-at-point)
+                    "some_symbol"))))
+
+(ert-deftest python-eldoc--get-symbol-at-point-4 ()
+  "Ensure symbol is found when point is at whitespace."
+  (python-tests-with-temp-buffer
+   "
+some_symbol   some_other_symbol
+"
+   (python-tests-look-at "  some_other_symbol")
+   (should (string= (python-eldoc--get-symbol-at-point)
+                    "some_symbol"))))
+
 \f
 ;;; Imenu
 
@@ -3899,6 +4652,49 @@ def foo(a,
    (python-tests-look-at "c):")
    (should (not (python-info-block-continuation-line-p)))))
 
+(ert-deftest python-info-assignment-statement-p-1 ()
+  (python-tests-with-temp-buffer
+   "
+data = foo(), bar() \\\\
+       baz(), 4 \\\\
+       5, 6
+"
+   (python-tests-look-at "data = foo(), bar()")
+   (should (python-info-assignment-statement-p))
+   (should (python-info-assignment-statement-p t))
+   (python-tests-look-at "baz(), 4")
+   (should (python-info-assignment-statement-p))
+   (should (not (python-info-assignment-statement-p t)))
+   (python-tests-look-at "5, 6")
+   (should (python-info-assignment-statement-p))
+   (should (not (python-info-assignment-statement-p t)))))
+
+(ert-deftest python-info-assignment-statement-p-2 ()
+  (python-tests-with-temp-buffer
+   "
+data = (foo(), bar()
+        baz(), 4
+        5, 6)
+"
+   (python-tests-look-at "data = (foo(), bar()")
+   (should (python-info-assignment-statement-p))
+   (should (python-info-assignment-statement-p t))
+   (python-tests-look-at "baz(), 4")
+   (should (python-info-assignment-statement-p))
+   (should (not (python-info-assignment-statement-p t)))
+   (python-tests-look-at "5, 6)")
+   (should (python-info-assignment-statement-p))
+   (should (not (python-info-assignment-statement-p t)))))
+
+(ert-deftest python-info-assignment-statement-p-3 ()
+  (python-tests-with-temp-buffer
+   "
+data '=' 42
+"
+   (python-tests-look-at "data '=' 42")
+   (should (not (python-info-assignment-statement-p)))
+   (should (not (python-info-assignment-statement-p t)))))
+
 (ert-deftest python-info-assignment-continuation-line-p-1 ()
   (python-tests-with-temp-buffer
    "
@@ -3986,6 +4782,136 @@ foo = True  # another comment
    (forward-line 1)
    (should (python-info-current-line-empty-p))))
 
+(ert-deftest python-info-docstring-p-1 ()
+  "Test module docstring detection."
+  (python-tests-with-temp-buffer
+   "# -*- coding: utf-8 -*-
+#!/usr/bin/python
+
+'''
+Module Docstring Django style.
+'''
+u'''Additional module docstring.'''
+'''Not a module docstring.'''
+"
+   (python-tests-look-at "Module Docstring Django style.")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "u'''Additional module docstring.'''")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Not a module docstring.'''")
+   (should (not (python-info-docstring-p)))))
+
+(ert-deftest python-info-docstring-p-2 ()
+  "Test variable docstring detection."
+  (python-tests-with-temp-buffer
+   "
+variable = 42
+U'''Variable docstring.'''
+'''Additional variable docstring.'''
+'''Not a variable docstring.'''
+"
+   (python-tests-look-at "Variable docstring.")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "u'''Additional variable docstring.'''")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Not a variable docstring.'''")
+   (should (not (python-info-docstring-p)))))
+
+(ert-deftest python-info-docstring-p-3 ()
+  "Test function docstring detection."
+  (python-tests-with-temp-buffer
+   "
+def func(a, b):
+    r'''
+    Function docstring.
+
+    onetwo style.
+    '''
+    R'''Additional function docstring.'''
+    '''Not a function docstring.'''
+    return a + b
+"
+   (python-tests-look-at "Function docstring.")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "R'''Additional function docstring.'''")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Not a function docstring.'''")
+   (should (not (python-info-docstring-p)))))
+
+(ert-deftest python-info-docstring-p-4 ()
+  "Test class docstring detection."
+  (python-tests-with-temp-buffer
+   "
+class Class:
+    ur'''
+    Class docstring.
+
+    symmetric style.
+    '''
+    uR'''
+    Additional class docstring.
+    '''
+    '''Not a class docstring.'''
+    pass
+"
+   (python-tests-look-at "Class docstring.")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "uR'''")  ;; Additional class docstring
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Not a class docstring.'''")
+   (should (not (python-info-docstring-p)))))
+
+(ert-deftest python-info-docstring-p-5 ()
+  "Test class attribute docstring detection."
+  (python-tests-with-temp-buffer
+   "
+class Class:
+    attribute = 42
+    Ur'''
+    Class attribute docstring.
+
+    pep-257 style.
+
+    '''
+    UR'''
+    Additional class attribute docstring.
+    '''
+    '''Not a class attribute docstring.'''
+    pass
+"
+   (python-tests-look-at "Class attribute docstring.")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "UR'''")  ;; Additional class attr docstring
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Not a class attribute docstring.'''")
+   (should (not (python-info-docstring-p)))))
+
+(ert-deftest python-info-docstring-p-6 ()
+  "Test class method docstring detection."
+  (python-tests-with-temp-buffer
+   "
+class Class:
+
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    def __call__(self):
+        '''Method docstring.
+
+        pep-257-nn style.
+        '''
+        '''Additional method docstring.'''
+        '''Not a method docstring.'''
+        return self.a + self.b
+"
+   (python-tests-look-at "Method docstring.")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Additional method docstring.'''")
+   (should (python-info-docstring-p))
+   (python-tests-look-at "'''Not a method docstring.'''")
+   (should (not (python-info-docstring-p)))))
+
 (ert-deftest python-info-encoding-from-cookie-1 ()
   "Should detect it on first line."
   (python-tests-with-temp-buffer
@@ -4094,7 +5020,7 @@ def foo(a, b, c):
             . "from IPython.core.completerlib import module_completion")
            (python-shell-completion-string-code
             . "';'.join(get_ipython().Completer.all_completions('''%s'''))\n")
-           (python-shell-virtualenv-path
+           (python-shell-virtualenv-root
             . "/home/user/.virtualenvs/project"))))
     (with-current-buffer buffer
       (kill-all-local-variables)
@@ -4138,12 +5064,11 @@ def foo(a, b, c):
 ;;; Electricity
 
 (ert-deftest python-parens-electric-indent-1 ()
-  (require 'electric)
   (let ((eim electric-indent-mode))
     (unwind-protect
         (progn
           (python-tests-with-temp-buffer
-              "
+           "
 from django.conf.urls import patterns, include, url
 
 from django.contrib import admin
@@ -4155,71 +5080,152 @@ urlpatterns = patterns('',
     url(r'^$', views.index
 )
 "
-            (electric-indent-mode 1)
-            (python-tests-look-at "views.index")
-            (end-of-line)
-
-            ;; Inserting commas within the same line should leave
-            ;; indentation unchanged.
-            (python-tests-self-insert ",")
-            (should (= (current-indentation) 4))
-
-            ;; As well as any other input happening within the same
-            ;; set of parens.
-            (python-tests-self-insert " name='index')")
-            (should (= (current-indentation) 4))
-
-            ;; But a comma outside it, should trigger indentation.
-            (python-tests-self-insert ",")
-            (should (= (current-indentation) 23))
-
-            ;; Newline indents to the first argument column
-            (python-tests-self-insert "\n")
-            (should (= (current-indentation) 23))
-
-            ;; All this input must not change indentation
-            (indent-line-to 4)
-            (python-tests-self-insert "url(r'^/login$', views.login)")
-            (should (= (current-indentation) 4))
-
-            ;; But this comma does
-            (python-tests-self-insert ",")
-            (should (= (current-indentation) 23))))
+           (electric-indent-mode 1)
+           (python-tests-look-at "views.index")
+           (end-of-line)
+
+           ;; Inserting commas within the same line should leave
+           ;; indentation unchanged.
+           (python-tests-self-insert ",")
+           (should (= (current-indentation) 4))
+
+           ;; As well as any other input happening within the same
+           ;; set of parens.
+           (python-tests-self-insert " name='index')")
+           (should (= (current-indentation) 4))
+
+           ;; But a comma outside it, should trigger indentation.
+           (python-tests-self-insert ",")
+           (should (= (current-indentation) 23))
+
+           ;; Newline indents to the first argument column
+           (python-tests-self-insert "\n")
+           (should (= (current-indentation) 23))
+
+           ;; All this input must not change indentation
+           (indent-line-to 4)
+           (python-tests-self-insert "url(r'^/login$', views.login)")
+           (should (= (current-indentation) 4))
+
+           ;; But this comma does
+           (python-tests-self-insert ",")
+           (should (= (current-indentation) 23))))
       (or eim (electric-indent-mode -1)))))
 
 (ert-deftest python-triple-quote-pairing ()
-  (require 'electric)
   (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)))
+           "\"\"\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)))
+           "\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"))))
+           "\"\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)))))
 
+\f
+;;; Hideshow support
+
+(ert-deftest python-hideshow-hide-levels-1 ()
+  "Should hide all methods when called after class start."
+  (let ((enabled hs-minor-mode))
+    (unwind-protect
+        (progn
+          (python-tests-with-temp-buffer
+           "
+class SomeClass:
+
+    def __init__(self, arg, kwarg=1):
+        self.arg = arg
+        self.kwarg = kwarg
+
+    def filter(self, nums):
+        def fn(item):
+            return item in [self.arg, self.kwarg]
+        return filter(fn, nums)
+
+    def __str__(self):
+        return '%s-%s' % (self.arg, self.kwarg)
+"
+           (hs-minor-mode 1)
+           (python-tests-look-at "class SomeClass:")
+           (forward-line)
+           (hs-hide-level 1)
+           (should
+            (string=
+             (python-tests-visible-string)
+             "
+class SomeClass:
+
+    def __init__(self, arg, kwarg=1):
+    def filter(self, nums):
+    def __str__(self):"))))
+      (or enabled (hs-minor-mode -1)))))
+
+(ert-deftest python-hideshow-hide-levels-2 ()
+  "Should hide nested methods and parens at end of defun."
+  (let ((enabled hs-minor-mode))
+    (unwind-protect
+        (progn
+          (python-tests-with-temp-buffer
+           "
+class SomeClass:
+
+    def __init__(self, arg, kwarg=1):
+        self.arg = arg
+        self.kwarg = kwarg
+
+    def filter(self, nums):
+        def fn(item):
+            return item in [self.arg, self.kwarg]
+        return filter(fn, nums)
+
+    def __str__(self):
+        return '%s-%s' % (self.arg, self.kwarg)
+"
+           (hs-minor-mode 1)
+           (python-tests-look-at "def fn(item):")
+           (hs-hide-block)
+           (should
+            (string=
+             (python-tests-visible-string)
+             "
+class SomeClass:
+
+    def __init__(self, arg, kwarg=1):
+        self.arg = arg
+        self.kwarg = kwarg
+
+    def filter(self, nums):
+        def fn(item):
+        return filter(fn, nums)
+
+    def __str__(self):
+        return '%s-%s' % (self.arg, self.kwarg)
+"))))
+      (or enabled (hs-minor-mode -1)))))
+
+
 
 (provide 'python-tests)
 
 ;; Local Variables:
-;; coding: utf-8
 ;; indent-tabs-mode: nil
 ;; End: