diff options
Diffstat (limited to 'test/lisp/progmodes')
-rw-r--r-- | test/lisp/progmodes/cc-mode-tests.el | 72 | ||||
-rw-r--r-- | test/lisp/progmodes/compile-tests.el | 382 | ||||
-rw-r--r-- | test/lisp/progmodes/elisp-mode-tests.el | 676 | ||||
-rw-r--r-- | test/lisp/progmodes/etags-tests.el | 91 | ||||
-rw-r--r-- | test/lisp/progmodes/f90.el | 276 | ||||
-rw-r--r-- | test/lisp/progmodes/flymake-resources/Makefile | 13 | ||||
-rw-r--r-- | test/lisp/progmodes/flymake-resources/test.c | 5 | ||||
-rw-r--r-- | test/lisp/progmodes/flymake-resources/test.pl | 2 | ||||
-rw-r--r-- | test/lisp/progmodes/flymake-tests.el | 80 | ||||
-rw-r--r-- | test/lisp/progmodes/python-tests.el | 5295 | ||||
-rw-r--r-- | test/lisp/progmodes/ruby-mode-tests.el | 752 | ||||
-rw-r--r-- | test/lisp/progmodes/sql-tests.el | 47 | ||||
-rw-r--r-- | test/lisp/progmodes/subword-tests.el | 81 | ||||
-rw-r--r-- | test/lisp/progmodes/xref-tests.el | 91 |
14 files changed, 7863 insertions, 0 deletions
diff --git a/test/lisp/progmodes/cc-mode-tests.el b/test/lisp/progmodes/cc-mode-tests.el new file mode 100644 index 00000000000..62e0a738fbd --- /dev/null +++ b/test/lisp/progmodes/cc-mode-tests.el @@ -0,0 +1,72 @@ +;;; cc-mode-tests.el --- Test suite for cc-mode. -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Michal Nazarewicz <mina86@mina86.com> +;; Keywords: internal +;; Human-Keywords: internal + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + + +;;; Commentary: + +;; Unit tests for cc-mode.el. + +;;; Code: + +(require 'ert) +(require 'ert-x) +(require 'cc-mode) + +(ert-deftest c-or-c++-mode () + "Test c-or-c++-mode language detection." + (cl-letf* ((mode nil) + (do-test (lambda (content expected) + (delete-region (point-min) (point-max)) + (insert content) + (setq mode nil) + (c-or-c++-mode) + (unless(eq expected mode) + (ert-fail + (format "expected %s but got %s when testing '%s'" + expected mode content))))) + ((symbol-function 'c-mode) (lambda () (setq mode 'c-mode))) + ((symbol-function 'c++-mode) (lambda () (setq mode 'c++-mode)))) + (with-temp-buffer + (mapc (lambda (content) + (funcall do-test content 'c++-mode) + (funcall do-test (concat "// " content) 'c-mode) + (funcall do-test (concat " * " content) 'c-mode)) + '("using \t namespace \t std;" + "using \t std::string;" + "namespace \t {" + "namespace \t foo \t {" + "class \t Blah_42 \t {" + "class \t Blah_42 \t \n" + "class \t _42_Blah:public Foo {" + "template \t < class T >" + "template< class T >" + "#include <string>" + "#include<iostream>" + "#include \t <map>")) + + (mapc (lambda (content) (funcall do-test content 'c-mode)) + '("struct \t Blah_42 \t {" + "struct template {" + "#include <string.h>"))))) + +;;; cc-mode-tests.el ends here diff --git a/test/lisp/progmodes/compile-tests.el b/test/lisp/progmodes/compile-tests.el new file mode 100644 index 00000000000..9f61c20fd5e --- /dev/null +++ b/test/lisp/progmodes/compile-tests.el @@ -0,0 +1,382 @@ +;;; compile-tests.el --- Test suite for compile.el. -*- lexical-binding: t; -*- + +;; Copyright (C) 2011-2016 Free Software Foundation, Inc. + +;; Author: Chong Yidong <cyd@stupidchicken.com> +;; Keywords: internal +;; Human-Keywords: internal + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Unit tests for lisp/progmodes/compile.el. + +;;; Code: + +(require 'ert) +(require 'compile) + +(defvar compile-tests--test-regexps-data + ;; The computed column numbers are zero-indexed, so subtract 1 from + ;; what's reported in the string. The end column numbers are for + ;; the character after, so it matches what's reported in the string. + '(;; absoft + ("Error on line 3 of t.f: Execution error unclassifiable statement" + 1 nil 3 "t.f") + ("Line 45 of \"foo.c\": bloofle undefined" + 1 nil 45 "foo.c") + ("error on line 19 of fplot.f: spelling error?" + 1 nil 19 "fplot.f") + ("warning on line 17 of fplot.f: data type is undefined for variable d" + 1 nil 17 "fplot.f") + ;; Ada & Mpatrol + ("foo.adb:61:11: [...] in call to size declared at foo.ads:11" + 1 11 61 "foo.adb") + ("foo.adb:61:11: [...] in call to size declared at foo.ads:11" + 52 nil 11 "foo.ads") + (" 0x8008621 main+16 at error.c:17" + 23 nil 17 "error.c") + ;; aix + ("****** Error number 140 in line 8 of file errors.c ******" + 25 nil 8 "errors.c") + ;; ant + ("[javac] /src/DataBaseTestCase.java:27: unreported exception ..." + 13 nil 27 "/src/DataBaseTestCase.java") + ("[javac] /src/DataBaseTestCase.java:49: warning: finally clause cannot complete normally" + 13 nil 49 "/src/DataBaseTestCase.java") + ("[jikes] foo.java:3:5:7:9: blah blah" + 14 (5 . 10) (3 . 7) "foo.java") + ;; bash + ("a.sh: line 1: ls-l: command not found" + 1 nil 1 "a.sh") + ;; borland + ("Error ping.c 15: Unable to open include file 'sys/types.h'" + 1 nil 15 "ping.c") + ("Warning pong.c 68: Call to function 'func' with no prototype" + 1 nil 68 "pong.c") + ("Error E2010 ping.c 15: Unable to open include file 'sys/types.h'" + 1 nil 15 "ping.c") + ("Warning W1022 pong.c 68: Call to function 'func' with no prototype" + 1 nil 68 "pong.c") + ;; caml + ("File \"foobar.ml\", lines 5-8, characters 20-155: blah blah" + 1 (20 . 156) (5 . 8) "foobar.ml") + ("File \"F:\\ocaml\\sorting.ml\", line 65, characters 2-145:\nWarning 26: unused variable equ." + 1 (2 . 146) 65 "F:\\ocaml\\sorting.ml") + ("File \"/usr/share/gdesklets/display/TargetGauge.py\", line 41, in add_children" + 1 nil 41 "/usr/share/gdesklets/display/TargetGauge.py") + ("File \\lib\\python\\Products\\PythonScripts\\PythonScript.py, line 302, in _exec" + 1 nil 302 "\\lib\\python\\Products\\PythonScripts\\PythonScript.py") + ("File \"/tmp/foo.py\", line 10" + 1 nil 10 "/tmp/foo.py") + ;; clang-include + ("In file included from foo.cpp:2:" + 1 nil 2 "foo.cpp" 0) + ;; cmake cmake-info + ("CMake Error at CMakeLists.txt:23 (hurz):" + 1 nil 23 "CMakeLists.txt") + ("CMake Warning at cmake/modules/UseUG.cmake:73 (find_package):" + 1 nil 73 "cmake/modules/UseUG.cmake") + (" cmake/modules/DuneGridMacros.cmake:19 (include)" + 1 nil 19 "cmake/modules/DuneGridMacros.cmake") + ;; comma + ("\"foo.f\", line 3: Error: syntax error near end of statement" + 1 nil 3 "foo.f") + ("\"vvouch.c\", line 19.5: 1506-046 (S) Syntax error." + 1 5 19 "vvouch.c") + ("\"foo.c\", line 32 pos 1; (E) syntax error; unexpected symbol: \"lossage\"" + 1 1 32 "foo.c") + ("\"foo.adb\", line 2(11): warning: file name does not match ..." + 1 11 2 "foo.adb") + ("\"src/swapping.c\", line 30.34: 1506-342 (W) \"/*\" detected in comment." + 1 34 30 "src/swapping.c") + ;; cucumber + ("Scenario: undefined step # features/cucumber.feature:3" + 29 nil 3 "features/cucumber.feature") + (" /home/gusev/.rvm/foo/bar.rb:500:in `_wrap_assertion'" + 1 nil 500 "/home/gusev/.rvm/foo/bar.rb") + ;; edg-1 edg-2 + ("build/intel/debug/../../../struct.cpp(42): error: identifier \"foo\" is undefined" + 1 nil 42 "build/intel/debug/../../../struct.cpp") + ("build/intel/debug/struct.cpp(44): warning #1011: missing return statement at end of" + 1 nil 44 "build/intel/debug/struct.cpp") + ("build/intel/debug/iptr.h(302): remark #981: operands are evaluated in unspecified order" + 1 nil 302 "build/intel/debug/iptr.h") + (" detected during ... at line 62 of \"build/intel/debug/../../../trace.h\"" + 31 nil 62 "build/intel/debug/../../../trace.h") + ;; epc + ("Error 24 at (2:progran.f90) : syntax error" + 1 nil 2 "progran.f90") + ;; ftnchek + (" Dummy arg W in module SUBA line 8 file arrayclash.f is array" + 32 nil 8 "arrayclash.f") + (" L4 used at line 55 file test/assign.f; never set" + 16 nil 55 "test/assign.f") + ("Warning near line 10 file arrayclash.f: Module contains no executable" + 1 nil 10 "arrayclash.f") + ("Nonportable usage near line 31 col 9 file assign.f: mixed default and explicit" + 24 9 31 "assign.f") + ;; iar + ("\"foo.c\",3 Error[32]: Error message" + 1 nil 3 "foo.c") + ("\"foo.c\",3 Warning[32]: Error message" + 1 nil 3 "foo.c") + ;; ibm + ("foo.c(2:0) : informational EDC0804: Function foo is not referenced." + 1 0 2 "foo.c") + ("foo.c(3:8) : warning EDC0833: Implicit return statement encountered." + 1 8 3 "foo.c") + ("foo.c(5:5) : error EDC0350: Syntax error." + 1 5 5 "foo.c") + ;; irix + ("ccom: Error: foo.c, line 2: syntax error" + 1 nil 2 "foo.c") + ("cc: Severe: /src/Python-2.3.3/Modules/_curses_panel.c, line 17: Cannot find file <panel.h> ..." + 1 nil 17 "/src/Python-2.3.3/Modules/_curses_panel.c") + ("cc: Info: foo.c, line 27: ..." + 1 nil 27 "foo.c") + ("cfe: Warning 712: foo.c, line 2: illegal combination of pointer and ..." + 1 nil 2 "foo.c") + ("cfe: Warning 600: xfe.c: 170: Not in a conditional directive while ..." + 1 nil 170 "xfe.c") + ("/usr/lib/cmplrs/cc/cfe: Error: foo.c: 1: blah blah" + 1 nil 1 "foo.c") + ("/usr/lib/cmplrs/cc/cfe: warning: foo.c: 1: blah blah" + 1 nil 1 "foo.c") + ("foo bar: baz.f, line 27: ..." + 1 nil 27 "baz.f") + ;; java + ("\tat org.foo.ComponentGateway.doGet(ComponentGateway.java:172)" + 5 nil 172 "ComponentGateway.java") + ("\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:740)" + 5 nil 740 "HttpServlet.java") + ("==1332== at 0x4040743C: System::getErrorString() (../src/Lib/System.cpp:217)" + 13 nil 217 "../src/Lib/System.cpp") + ("==1332== by 0x8008621: main (vtest.c:180)" + 13 nil 180 "vtest.c") + ;; jikes-file jikes-line + ("Found 2 semantic errors compiling \"../javax/swing/BorderFactory.java\":" + 1 nil nil "../javax/swing/BorderFactory.java") + ("Issued 1 semantic warning compiling \"java/awt/Toolkit.java\":" + 1 nil nil "java/awt/Toolkit.java") + ;; gcc-include + ("In file included from /usr/include/c++/3.3/backward/warn.h:4," + 1 nil 4 "/usr/include/c++/3.3/backward/warn.h") + (" from /usr/include/c++/3.3/backward/iostream.h:31:0," + 1 0 31 "/usr/include/c++/3.3/backward/iostream.h") + (" from test_clt.cc:1:" + 1 nil 1 "test_clt.cc") + ;; gnu + ("foo.c:8: message" 1 nil 8 "foo.c") + ("../foo.c:8: W: message" 1 nil 8 "../foo.c") + ("/tmp/foo.c:8:warning message" 1 nil 8 "/tmp/foo.c") + ("foo/bar.py:8: FutureWarning message" 1 nil 8 "foo/bar.py") + ("foo.py:8: RuntimeWarning message" 1 nil 8 "foo.py") + ("foo.c:8:I: message" 1 nil 8 "foo.c") + ("foo.c:8.23: note: message" 1 23 8 "foo.c") + ("foo.c:8.23: info: message" 1 23 8 "foo.c") + ("foo.c:8:23:information: message" 1 23 8 "foo.c") + ("foo.c:8.23-45: Informational: message" 1 (23 . 46) (8 . nil) "foo.c") + ("foo.c:8-23: message" 1 nil (8 . 23) "foo.c") + ;; The next one is not in the GNU standards AFAICS. + ;; Here we seem to interpret it as LINE1-LINE2.COL2. + ("foo.c:8-45.3: message" 1 (nil . 4) (8 . 45) "foo.c") + ("foo.c:8.23-9.1: message" 1 (23 . 2) (8 . 9) "foo.c") + ("jade:dbcommon.dsl:133:17:E: missing argument for function call" + 1 17 133 "dbcommon.dsl") + ("G:/cygwin/dev/build-myproj.xml:54: Compiler Adapter 'javac' can't be found." + 1 nil 54 "G:/cygwin/dev/build-myproj.xml") + ("file:G:/cygwin/dev/build-myproj.xml:54: Compiler Adapter 'javac' can't be found." + 1 nil 54 "G:/cygwin/dev/build-myproj.xml") + ("{standard input}:27041: Warning: end of file not at end of a line; newline inserted" + 1 nil 27041 "{standard input}") + ;; Guile + ("In foo.scm:\n" 1 nil nil "foo.scm") + (" 63:4 [call-with-prompt prompt0 ...]" 1 4 63 nil) + ("1038: 1 [main (\"gud-break.scm\")]" 1 1 1038 nil) + ;; lcc + ("E, file.cc(35,52) Illegal operation on pointers" 1 52 35 "file.cc") + ("W, file.cc(36,52) blah blah" 1 52 36 "file.cc") + ;; makepp + ("makepp: Scanning `/foo/bar.c'" 19 nil nil "/foo/bar.c") + ("makepp: warning: bla bla `/foo/bar.c' and `/foo/bar.h'" 27 nil nil "/foo/bar.c") + ("makepp: bla bla `/foo/Makeppfile:12' bla" 18 nil 12 "/foo/Makeppfile") + ("makepp: bla bla `/foo/bar.c' and `/foo/bar.h'" 35 nil nil "/foo/bar.h") + ;; maven + ("FooBar.java:[111,53] no interface expected here" + 1 53 111 "FooBar.java" 2) + (" [ERROR] /Users/cinsk/hello.java:[651,96] ';' expected" + 15 96 651 "/Users/cinsk/hello.java" 2) ;Bug#11517. + ("[WARNING] /foo/bar/Test.java:[27,43] unchecked conversion" + 11 43 27 "/foo/bar/Test.java" 1) ;Bug#20556 + ;; mips-1 mips-2 + ("TrimMask (255) in solomon.c may be indistinguishable from TrimMasks (93) in solomo.c due to truncation" + 11 nil 255 "solomon.c") + ("TrimMask (255) in solomon.c may be indistinguishable from TrimMasks (93) in solomo.c due to truncation" + 70 nil 93 "solomo.c") + ("name defined but never used: LinInt in cmap_calc.c(199)" + 40 nil 199 "cmap_calc.c") + ;; msft + ("keyboard handler.c(537) : warning C4005: 'min' : macro redefinition" + 1 nil 537 "keyboard handler.c") + ("d:\\tmp\\test.c(23) : error C2143: syntax error : missing ';' before 'if'" + 1 nil 23 "d:\\tmp\\test.c") + ("d:\\tmp\\test.c(1145) : see declaration of 'nsRefPtr'" + 1 nil 1145 "d:\\tmp\\test.c") + ("1>test_main.cpp(29): error C2144: syntax error : 'int' should be preceded by ';'" + 3 nil 29 "test_main.cpp") + ("1>test_main.cpp(29): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int" + 3 nil 29 "test_main.cpp") + ;; watcom + ("..\\src\\ctrl\\lister.c(109): Error! E1009: Expecting ';' but found '{'" + 1 nil 109 "..\\src\\ctrl\\lister.c") + ("..\\src\\ctrl\\lister.c(120): Warning! W201: Unreachable code" + 1 nil 120 "..\\src\\ctrl\\lister.c") + ;; oracle + ("Semantic error at line 528, column 5, file erosacqdb.pc:" + 1 5 528 "erosacqdb.pc") + ("Error at line 41, column 10 in file /usr/src/sb/ODBI_BHP.hpp" + 1 10 41 "/usr/src/sb/ODBI_BHP.hpp") + ("PCC-02150: error at line 49, column 27 in file /usr/src/sb/ODBI_dxfgh.pc" + 1 27 49 "/usr/src/sb/ODBI_dxfgh.pc") + ("PCC-00003: invalid SQL Identifier at column name in line 12 of file /usr/src/sb/ODBI_BHP.hpp" + 1 nil 12 "/usr/src/sb/ODBI_BHP.hpp") + ("PCC-00004: mismatched IF/ELSE/ENDIF block at line 27 in file /usr/src/sb/ODBI_BHP.hpp" + 1 nil 27 "/usr/src/sb/ODBI_BHP.hpp") + ("PCC-02151: line 21 column 40 file /usr/src/sb/ODBI_BHP.hpp:" + 1 40 21 "/usr/src/sb/ODBI_BHP.hpp") + ;; perl + ("syntax error at automake line 922, near \"':'\"" + 14 nil 922 "automake") + ("Died at test.pl line 27." + 6 nil 27 "test.pl") + ("store::odrecall('File_A', 'x2') called at store.pm line 90" + 40 nil 90 "store.pm") + ("\t(in cleanup) something bad at foo.pl line 3 during global destruction." + 29 nil 3 "foo.pl") + ("GLib-GObject-WARNING **: /build/buildd/glib2.0-2.14.5/gobject/gsignal.c:1741: instance `0x8206790' has no handler with id `1234' at t-compilation-perl-gtk.pl line 3." + 130 nil 3 "t-compilation-perl-gtk.pl") + ;; php + ("Parse error: parse error, unexpected $ in main.php on line 59" + 1 nil 59 "main.php") + ("Fatal error: Call to undefined function: mysql_pconnect() in db.inc on line 66" + 1 nil 66 "db.inc") + ;; ruby + ("plain-exception.rb:7:in `fun': unhandled exception" + 1 nil 7 "plain-exception.rb") + ("\tfrom plain-exception.rb:3:in `proxy'" 2 nil 3 "plain-exception.rb") + ("\tfrom plain-exception.rb:12" 2 nil 12 "plain-exception.rb") + ;; ruby-Test::Unit + ;; FIXME + (" [examples/test-unit.rb:28:in `here_is_a_deep_assert'" + 5 nil 28 "examples/test-unit.rb") + (" examples/test-unit.rb:19:in `test_a_deep_assert']:" + 6 nil 19 "examples/test-unit.rb") + ("examples/test-unit.rb:10:in `test_assert_raise'" + 1 nil 10 "examples/test-unit.rb") + ;; rxp + ("Error: Mismatched end tag: expected </geroup>, got </group>\nin unnamed entity at line 71 char 8 of file:///home/reto/test/group.xml" + 1 8 71 "/home/reto/test/group.xml") + ("Warning: Start tag for undeclared element geroup\nin unnamed entity at line 4 char 8 of file:///home/reto/test/group.xml" + 1 8 4 "/home/reto/test/group.xml") + ;; sparc-pascal-file sparc-pascal-line sparc-pascal-example + ("Thu May 14 10:46:12 1992 mom3.p:" + 1 nil nil "mom3.p") + ;; sun + ("cc-1020 CC: REMARK File = CUI_App.h, Line = 735" + 13 nil 735 "CUI_App.h") + ("cc-1070 cc: WARNING File = linkl.c, Line = 38" + 13 nil 38 "linkl.c") + ("cf90-113 f90comp: ERROR NSE, File = Hoved.f90, Line = 16, Column = 3" + 18 3 16 "Hoved.f90") + ;; sun-ada + ("/home3/xdhar/rcds_rc/main.a, line 361, char 6:syntax error: \",\" inserted" + 1 6 361 "/home3/xdhar/rcds_rc/main.a") + ;; 4bsd + ("/usr/src/foo/foo.c(8): warning: w may be used before set" + 1 nil 8 "/usr/src/foo/foo.c") + ("/usr/src/foo/foo.c(9): error: w is used before set" + 1 nil 9 "/usr/src/foo/foo.c") + ("strcmp: variable # of args. llib-lc(359) :: /usr/src/foo/foo.c(8)" + 44 nil 8 "/usr/src/foo/foo.c") + ("bloofle defined( /users/wolfgang/foo.c(4) ), but never used" + 18 nil 4 "/users/wolfgang/foo.c") + ;; perl--Pod::Checker + ;; FIXME + ;; *** ERROR: Spurious text after =cut at line 193 in file foo.pm + ;; *** ERROR: =over on line 37 without closing =back at line EOF in file bar.pm + ;; *** ERROR: =over on line 1 without closing =back (at head1) at line 3 in file x.pod + ;; perl--Test + ("# Failed test 1 in foo.t at line 6" + 1 nil 6 "foo.t") + ;; perl--Test::Harness + ("NOK 1# Test 1 got: \"1234\" (t/foo.t at line 46)" + 1 nil 46 "t/foo.t") + ;; weblint + ("index.html (13:1) Unknown element <fdjsk>" + 1 1 13 "index.html")) + "List of tests for `compilation-error-regexp-alist'. +Each element has the form (STR POS COLUMN LINE FILENAME [TYPE]), +where STR is an error string, POS is the position of the error in +STR, COLUMN and LINE are the reported column and line numbers (or +nil) for that error, FILENAME is the reported filename, and TYPE +is 0 for an information message, 1 for a warning, and 2 for an +error. + +LINE can also be of the form (LINE . END-LINE) meaning a range of +lines. COLUMN can also be of the form (COLUMN . END-COLUMN) +meaning a range of columns starting on LINE and ending on +END-LINE, if that matched. TYPE can be left out, in which case +any message type is accepted.") + +(defun compile--test-error-line (test) + (erase-buffer) + (setq compilation-locs (make-hash-table)) + (insert (car test)) + (compilation-parse-errors (point-min) (point-max)) + (let ((msg (get-text-property (nth 1 test) 'compilation-message))) + (should msg) + (let ((loc (compilation--message->loc msg)) + (col (nth 2 test)) + (line (nth 3 test)) + (file (nth 4 test)) + (type (nth 5 test)) + end-col end-line) + (if (consp col) + (setq end-col (cdr col) col (car col))) + (if (consp line) + (setq end-line (cdr line) line (car line))) + (should (equal (compilation--loc->col loc) col)) + (should (equal (compilation--loc->line loc) line)) + (when file + (should (equal (caar (compilation--loc->file-struct loc)) file))) + (when end-col + (should (equal (car (cadr (nth 2 (compilation--loc->file-struct loc)))) + end-col))) + (should (equal (car (nth 2 (compilation--loc->file-struct loc))) + (or end-line line))) + (when type + (should (equal type (compilation--message->type msg))))))) + +(ert-deftest compile-test-error-regexps () + "Test the `compilation-error-regexp-alist' regexps. +The test data is in `compile-tests--test-regexps-data'." + (with-temp-buffer + (font-lock-mode -1) + (mapc #'compile--test-error-line compile-tests--test-regexps-data))) + +;;; compile-tests.el ends here diff --git a/test/lisp/progmodes/elisp-mode-tests.el b/test/lisp/progmodes/elisp-mode-tests.el new file mode 100644 index 00000000000..12e61cf8d18 --- /dev/null +++ b/test/lisp/progmodes/elisp-mode-tests.el @@ -0,0 +1,676 @@ +;;; elisp-mode-tests.el --- Tests for emacs-lisp-mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2015-2016 Free Software Foundation, Inc. + +;; Author: Dmitry Gutov <dgutov@yandex.ru> +;; Author: Stephen Leake <stephen_leake@member.fsf.org> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'xref) + +;;; Completion + +(defun elisp--test-completions () + (let ((data (elisp-completion-at-point))) + (all-completions (buffer-substring (nth 0 data) (nth 1 data)) + (nth 2 data) + (plist-get (nthcdr 3 data) :predicate)))) + +(ert-deftest elisp-completes-functions () + (with-temp-buffer + (emacs-lisp-mode) + (insert "(ba") + (let ((comps (elisp--test-completions))) + (should (member "backup-buffer" comps)) + (should-not (member "backup-inhibited" comps))))) + +(ert-deftest elisp-completes-variables () + (with-temp-buffer + (emacs-lisp-mode) + (insert "(foo ba") + (let ((comps (elisp--test-completions))) + (should (member "backup-inhibited" comps)) + (should-not (member "backup-buffer" comps))))) + +(ert-deftest elisp-completes-anything-quoted () + (dolist (text '("`(foo ba" "(foo 'ba" + "`(,foo ba" "`,(foo `ba" + "'(foo (ba")) + (with-temp-buffer + (emacs-lisp-mode) + (insert text) + (let ((comps (elisp--test-completions))) + (should (member "backup-inhibited" comps)) + (should (member "backup-buffer" comps)) + (should (member "backup" comps)))))) + +(ert-deftest elisp-completes-variables-unquoted () + (dolist (text '("`(foo ,ba" "`(,(foo ba" "`(,ba")) + (with-temp-buffer + (emacs-lisp-mode) + (insert text) + (let ((comps (elisp--test-completions))) + (should (member "backup-inhibited" comps)) + (should-not (member "backup-buffer" comps)))))) + +(ert-deftest elisp-completes-functions-in-special-macros () + (dolist (text '("(declare-function ba" "(cl-callf2 ba")) + (with-temp-buffer + (emacs-lisp-mode) + (insert text) + (let ((comps (elisp--test-completions))) + (should (member "backup-buffer" comps)) + (should-not (member "backup-inhibited" comps)))))) + +(ert-deftest elisp-completes-functions-after-hash-quote () + (ert-deftest elisp-completes-functions-after-let-bindings () + (with-temp-buffer + (emacs-lisp-mode) + (insert "#'ba") + (let ((comps (elisp--test-completions))) + (should (member "backup-buffer" comps)) + (should-not (member "backup-inhibited" comps)))))) + +(ert-deftest elisp-completes-local-variables () + (with-temp-buffer + (emacs-lisp-mode) + (insert "(let ((bar 1) baz) (foo ba") + (let ((comps (elisp--test-completions))) + (should (member "backup-inhibited" comps)) + (should (member "bar" comps)) + (should (member "baz" comps))))) + +(ert-deftest elisp-completest-variables-in-let-bindings () + (dolist (text '("(let (ba" "(let* ((ba")) + (with-temp-buffer + (emacs-lisp-mode) + (insert text) + (let ((comps (elisp--test-completions))) + (should (member "backup-inhibited" comps)) + (should-not (member "backup-buffer" comps)))))) + +(ert-deftest elisp-completes-functions-after-let-bindings () + (with-temp-buffer + (emacs-lisp-mode) + (insert "(let ((bar 1) (baz 2)) (ba") + (let ((comps (elisp--test-completions))) + (should (member "backup-buffer" comps)) + (should-not (member "backup-inhibited" comps))))) + +;;; xref + +(defun xref-elisp-test-descr-to-target (xref) + "Return an appropriate `looking-at' match string for XREF." + (let* ((loc (xref-item-location xref)) + (type (or (xref-elisp-location-type loc) + 'defun))) + + (cl-case type + (defalias + ;; summary: "(defalias xref)" + ;; target : "(defalias 'xref" + (concat "(defalias '" (substring (xref-item-summary xref) 10 -1))) + + (defun + (let ((summary (xref-item-summary xref)) + (file (xref-elisp-location-file loc))) + (cond + ((string= "c" (file-name-extension file)) + ;; summary: "(defun buffer-live-p)" + ;; target : "DEFUN (buffer-live-p" + (concat + (upcase (substring summary 1 6)) + " (\"" + (substring summary 7 -1) + "\"")) + + (t + (substring summary 0 -1)) + ))) + + (defvar + (let ((summary (xref-item-summary xref)) + (file (xref-elisp-location-file loc))) + (cond + ((string= "c" (file-name-extension file)) + ;; summary: "(defvar system-name)" + ;; target : "DEFVAR_LISP ("system-name", " + ;; summary: "(defvar abbrev-mode)" + ;; target : DEFVAR_PER_BUFFER ("abbrev-mode" + (concat + (upcase (substring summary 1 7)) + (if (bufferp (variable-binding-locus (xref-elisp-location-symbol loc))) + "_PER_BUFFER (\"" + "_LISP (\"") + (substring summary 8 -1) + "\"")) + + (t + (substring summary 0 -1)) + ))) + + (feature + ;; summary: "(feature xref)" + ;; target : "(provide 'xref)" + (concat "(provide '" (substring (xref-item-summary xref) 9 -1))) + + (otherwise + (substring (xref-item-summary xref) 0 -1)) + ))) + + +(defun xref-elisp-test-run (xrefs expected-xrefs) + (should (= (length xrefs) (length expected-xrefs))) + (while xrefs + (let* ((xref (pop xrefs)) + (expected (pop expected-xrefs)) + (expected-xref (or (when (consp expected) (car expected)) expected)) + (expected-source (when (consp expected) (cdr expected)))) + + ;; Downcase the filenames for case-insensitive file systems. + (setf (xref-elisp-location-file (oref xref location)) + (downcase (xref-elisp-location-file (oref xref location)))) + + (setf (xref-elisp-location-file (oref expected-xref location)) + (downcase (xref-elisp-location-file (oref expected-xref location)))) + + (should (equal xref expected-xref)) + + (xref--goto-location (xref-item-location xref)) + (back-to-indentation) + (should (looking-at (or expected-source + (xref-elisp-test-descr-to-target expected))))) + )) + +(defmacro xref-elisp-deftest (name computed-xrefs expected-xrefs) + "Define an ert test for an xref-elisp feature. +COMPUTED-XREFS and EXPECTED-XREFS are lists of xrefs, except if +an element of EXPECTED-XREFS is a cons (XREF . TARGET), TARGET is +matched to the found location; otherwise, match +to (xref-elisp-test-descr-to-target xref)." + (declare (indent defun) + (debug (symbolp "name"))) + `(ert-deftest ,(intern (concat "xref-elisp-test-" (symbol-name name))) () + (let ((find-file-suppress-same-file-warnings t)) + (xref-elisp-test-run ,computed-xrefs ,expected-xrefs) + ))) + +;; When tests are run from the Makefile, 'default-directory' is $HOME, +;; so we must provide this dir to expand-file-name in the expected +;; results. This also allows running these tests from other +;; directories. +;; +;; We add 'downcase' here to deliberately cause a potential problem on +;; case-insensitive file systems. On such systems, `load-file-name' +;; may not have the same case as the real file system, since the user +;; can set `load-path' to have the wrong case (on my Windows system, +;; `load-path' has the correct case, so this causes the expected test +;; values to have the wrong case). This is handled in +;; `xref-elisp-test-run'. +(defconst emacs-test-dir (downcase (file-name-directory (or load-file-name (buffer-file-name))))) + + +;; alphabetical by test name + +;; Autoloads require no special support; they are handled as functions. + +;; FIXME: defalias-defun-c cmpl-prefix-entry-head +;; FIXME: defalias-defvar-el allout-mode-map + +(xref-elisp-deftest find-defs-constructor + (elisp--xref-find-definitions 'xref-make-elisp-location) + ;; 'xref-make-elisp-location' is just a name for the default + ;; constructor created by the cl-defstruct, so the location is the + ;; cl-defstruct location. + (list + (cons + (xref-make "(cl-defstruct (xref-elisp-location (:constructor xref-make-elisp-location)))" + (xref-make-elisp-location + 'xref-elisp-location 'define-type + (expand-file-name "../../../lisp/progmodes/elisp-mode.el" emacs-test-dir))) + ;; It's not worth adding another special case to `xref-elisp-test-descr-to-target' for this + "(cl-defstruct (xref-elisp-location") + )) + +(xref-elisp-deftest find-defs-defalias-defun-el + (elisp--xref-find-definitions 'Buffer-menu-sort) + (list + (xref-make "(defalias Buffer-menu-sort)" + (xref-make-elisp-location + 'Buffer-menu-sort 'defalias + (expand-file-name "../../../lisp/buff-menu.elc" emacs-test-dir))) + (xref-make "(defun tabulated-list-sort)" + (xref-make-elisp-location + 'tabulated-list-sort nil + (expand-file-name "../../../lisp/emacs-lisp/tabulated-list.el" emacs-test-dir))) + )) + +;; FIXME: defconst + +;; FIXME: eieio defclass + +;; Possible ways of defining the default method implementation for a +;; generic function. We declare these here, so we know we cover all +;; cases, and we don't rely on other code not changing. +;; +;; When the generic and default method are declared in the same place, +;; elisp--xref-find-definitions only returns one. + +(cl-defstruct (xref-elisp-root-type) + slot-1) + +(cl-defgeneric xref-elisp-generic-no-methods (arg1 arg2) + "doc string generic no-methods" + ;; No default implementation, no methods, but fboundp is true for + ;; this symbol; it calls cl-no-applicable-method + ) + +;; WORKAROUND: ‘this’ is unused, and the byte compiler complains, so +;; it should be spelled ‘_this’. But for some unknown reason, that +;; causes the batch mode test to fail; the symbol shows up as +;; ‘this’. It passes in interactive tests, so I haven't been able to +;; track down the problem. +(cl-defmethod xref-elisp-generic-no-default ((this xref-elisp-root-type) arg2) + "doc string generic no-default xref-elisp-root-type" + "non-default for no-default") + +;; defgeneric after defmethod in file to ensure the fallback search +;; method of just looking for the function name will fail. +(cl-defgeneric xref-elisp-generic-no-default (arg1 arg2) + "doc string generic no-default generic" + ;; No default implementation; this function calls the cl-generic + ;; dispatching code. + ) + +(cl-defgeneric xref-elisp-generic-co-located-default (arg1 arg2) + "doc string generic co-located-default" + "co-located default") + +(cl-defmethod xref-elisp-generic-co-located-default ((this xref-elisp-root-type) arg2) + "doc string generic co-located-default xref-elisp-root-type" + "non-default for co-located-default") + +(cl-defgeneric xref-elisp-generic-separate-default (arg1 arg2) + "doc string generic separate-default" + ;; default implementation provided separately + ) + +(cl-defmethod xref-elisp-generic-separate-default (arg1 arg2) + "doc string generic separate-default default" + "separate default") + +(cl-defmethod xref-elisp-generic-separate-default ((this xref-elisp-root-type) arg2) + "doc string generic separate-default xref-elisp-root-type" + "non-default for separate-default") + +(cl-defmethod xref-elisp-generic-implicit-generic (arg1 arg2) + "doc string generic implicit-generic default" + "default for implicit generic") + +(cl-defmethod xref-elisp-generic-implicit-generic ((this xref-elisp-root-type) arg2) + "doc string generic implicit-generic xref-elisp-root-type" + "non-default for implicit generic") + + +(xref-elisp-deftest find-defs-defgeneric-no-methods + (elisp--xref-find-definitions 'xref-elisp-generic-no-methods) + (list + (xref-make "(cl-defgeneric xref-elisp-generic-no-methods)" + (xref-make-elisp-location + 'xref-elisp-generic-no-methods 'cl-defgeneric + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defgeneric-no-default + (elisp--xref-find-definitions 'xref-elisp-generic-no-default) + (list + (xref-make "(cl-defgeneric xref-elisp-generic-no-default)" + (xref-make-elisp-location + 'xref-elisp-generic-no-default 'cl-defgeneric + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-elisp-generic-no-default ((this xref-elisp-root-type) arg2))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-elisp-generic-no-default nil '(xref-elisp-root-type t)) + 'cl-defmethod + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defgeneric-co-located-default + (elisp--xref-find-definitions 'xref-elisp-generic-co-located-default) + (list + (xref-make "(cl-defgeneric xref-elisp-generic-co-located-default)" + (xref-make-elisp-location + 'xref-elisp-generic-co-located-default 'cl-defgeneric + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-elisp-generic-co-located-default ((this xref-elisp-root-type) arg2))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-elisp-generic-co-located-default nil + '(xref-elisp-root-type t)) + 'cl-defmethod + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defgeneric-separate-default + (elisp--xref-find-definitions 'xref-elisp-generic-separate-default) + (list + (xref-make "(cl-defgeneric xref-elisp-generic-separate-default)" + (xref-make-elisp-location + 'xref-elisp-generic-separate-default 'cl-defgeneric + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-elisp-generic-separate-default (arg1 arg2))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-elisp-generic-separate-default nil '(t t)) + 'cl-defmethod + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-elisp-generic-separate-default ((this xref-elisp-root-type) arg2))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-elisp-generic-separate-default nil + '(xref-elisp-root-type t)) + 'cl-defmethod + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defgeneric-implicit-generic + (elisp--xref-find-definitions 'xref-elisp-generic-implicit-generic) + (list + (xref-make "(cl-defmethod xref-elisp-generic-implicit-generic (arg1 arg2))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-elisp-generic-implicit-generic nil '(t t)) + 'cl-defmethod + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-elisp-generic-implicit-generic ((this xref-elisp-root-type) arg2))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-elisp-generic-implicit-generic nil + '(xref-elisp-root-type t)) + 'cl-defmethod + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +;; Test that we handle more than one method + +;; When run from the Makefile, etags is not loaded at compile time, +;; but it is by the time this test is run. interactively; don't fail +;; for that. +(require 'etags) +(xref-elisp-deftest find-defs-defgeneric-el + (elisp--xref-find-definitions 'xref-location-marker) + (list + (xref-make "(cl-defgeneric xref-location-marker)" + (xref-make-elisp-location + 'xref-location-marker 'cl-defgeneric + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-location-marker ((l xref-elisp-location)))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-location-marker nil '(xref-elisp-location)) + 'cl-defmethod + (expand-file-name "../../../lisp/progmodes/elisp-mode.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-location-marker ((l xref-file-location)))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-location-marker nil '(xref-file-location)) + 'cl-defmethod + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-location-marker ((l xref-buffer-location)))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-location-marker nil '(xref-buffer-location)) + 'cl-defmethod + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-location-marker ((l xref-bogus-location)))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-location-marker nil '(xref-bogus-location)) + 'cl-defmethod + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))) + (xref-make "(cl-defmethod xref-location-marker ((l xref-etags-location)))" + (xref-make-elisp-location + (cl--generic-load-hist-format + 'xref-location-marker nil '(xref-etags-location)) + 'cl-defmethod + (expand-file-name "../../../lisp/progmodes/etags.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defgeneric-eval + (elisp--xref-find-definitions (eval '(cl-defgeneric stephe-leake-cl-defgeneric ()))) + nil) + +;; Define some mode-local overloadable/overridden functions for xref to find +(require 'mode-local) + +(define-overloadable-function xref-elisp-overloadable-no-methods () + "doc string overloadable no-methods") + +(define-overloadable-function xref-elisp-overloadable-no-default () + "doc string overloadable no-default") + +;; FIXME: byte compiler complains about unused lexical arguments +;; generated by this macro. +(define-mode-local-override xref-elisp-overloadable-no-default c-mode + (start end &optional nonterminal depth returnonerror) + "doc string overloadable no-default c-mode." + "result overloadable no-default c-mode.") + +(define-overloadable-function xref-elisp-overloadable-co-located-default () + "doc string overloadable co-located-default" + "result overloadable co-located-default.") + +(define-mode-local-override xref-elisp-overloadable-co-located-default c-mode + (start end &optional nonterminal depth returnonerror) + "doc string overloadable co-located-default c-mode." + "result overloadable co-located-default c-mode.") + +(define-overloadable-function xref-elisp-overloadable-separate-default () + "doc string overloadable separate-default.") + +(defun xref-elisp-overloadable-separate-default-default () + "doc string overloadable separate-default default" + "result overloadable separate-default.") + +(define-mode-local-override xref-elisp-overloadable-separate-default c-mode + (start end &optional nonterminal depth returnonerror) + "doc string overloadable separate-default c-mode." + "result overloadable separate-default c-mode.") + +(xref-elisp-deftest find-defs-define-overload-no-methods + (elisp--xref-find-definitions 'xref-elisp-overloadable-no-methods) + (list + (xref-make "(define-overloadable-function xref-elisp-overloadable-no-methods)" + (xref-make-elisp-location + 'xref-elisp-overloadable-no-methods 'define-overloadable-function + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-define-overload-no-default + (elisp--xref-find-definitions 'xref-elisp-overloadable-no-default) + (list + (xref-make "(define-overloadable-function xref-elisp-overloadable-no-default)" + (xref-make-elisp-location + 'xref-elisp-overloadable-no-default 'define-overloadable-function + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(define-mode-local-override xref-elisp-overloadable-no-default c-mode)" + (xref-make-elisp-location + '(xref-elisp-overloadable-no-default-c-mode . c-mode) 'define-mode-local-override + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-define-overload-co-located-default + (elisp--xref-find-definitions 'xref-elisp-overloadable-co-located-default) + (list + (xref-make "(define-overloadable-function xref-elisp-overloadable-co-located-default)" + (xref-make-elisp-location + 'xref-elisp-overloadable-co-located-default 'define-overloadable-function + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(define-mode-local-override xref-elisp-overloadable-co-located-default c-mode)" + (xref-make-elisp-location + '(xref-elisp-overloadable-co-located-default-c-mode . c-mode) 'define-mode-local-override + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-define-overload-separate-default + (elisp--xref-find-definitions 'xref-elisp-overloadable-separate-default) + (list + (xref-make "(define-overloadable-function xref-elisp-overloadable-separate-default)" + (xref-make-elisp-location + 'xref-elisp-overloadable-separate-default 'define-overloadable-function + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(defun xref-elisp-overloadable-separate-default-default)" + (xref-make-elisp-location + 'xref-elisp-overloadable-separate-default-default nil + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + (xref-make "(define-mode-local-override xref-elisp-overloadable-separate-default c-mode)" + (xref-make-elisp-location + '(xref-elisp-overloadable-separate-default-c-mode . c-mode) 'define-mode-local-override + (expand-file-name "elisp-mode-tests.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defun-el + (elisp--xref-find-definitions 'xref-find-definitions) + (list + (xref-make "(defun xref-find-definitions)" + (xref-make-elisp-location + 'xref-find-definitions nil + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))))) + +(xref-elisp-deftest find-defs-defun-eval + (elisp--xref-find-definitions (eval '(defun stephe-leake-defun ()))) + nil) + +(xref-elisp-deftest find-defs-defun-c + (elisp--xref-find-definitions 'buffer-live-p) + (list + (xref-make "(defun buffer-live-p)" + (xref-make-elisp-location 'buffer-live-p nil "src/buffer.c")))) + +;; FIXME: deftype + +(xref-elisp-deftest find-defs-defun-c-defvar-c + (xref-backend-definitions 'elisp "system-name") + (list + (xref-make "(defvar system-name)" + (xref-make-elisp-location 'system-name 'defvar "src/editfns.c")) + (xref-make "(defun system-name)" + (xref-make-elisp-location 'system-name nil "src/editfns.c"))) + ) + +(xref-elisp-deftest find-defs-defun-el-defvar-c + (xref-backend-definitions 'elisp "abbrev-mode") + ;; It's a minor mode, but the variable is defined in buffer.c + (list + (xref-make "(defvar abbrev-mode)" + (xref-make-elisp-location 'abbrev-mode 'defvar "src/buffer.c")) + (cons + (xref-make "(defun abbrev-mode)" + (xref-make-elisp-location + 'abbrev-mode nil + (expand-file-name "../../../lisp/abbrev.el" emacs-test-dir))) + "(define-minor-mode abbrev-mode")) + ) + +;; Source for both variable and defun is "(define-minor-mode +;; compilation-minor-mode". There is no way to tell that directly from +;; the symbol, but we can use (memq sym minor-mode-list) to detect +;; that the symbol is a minor mode. See `elisp--xref-find-definitions' +;; for more comments. +;; +;; IMPROVEME: return defvar instead of defun if source near starting +;; point indicates the user is searching for a variable, not a +;; function. +(require 'compile) ;; not loaded by default at test time +(xref-elisp-deftest find-defs-defun-defvar-el + (elisp--xref-find-definitions 'compilation-minor-mode) + (list + (cons + (xref-make "(defun compilation-minor-mode)" + (xref-make-elisp-location + 'compilation-minor-mode nil + (expand-file-name "../../../lisp/progmodes/compile.el" emacs-test-dir))) + "(define-minor-mode compilation-minor-mode") + )) + +(xref-elisp-deftest find-defs-defvar-el + (elisp--xref-find-definitions 'xref--marker-ring) + (list + (xref-make "(defvar xref--marker-ring)" + (xref-make-elisp-location + 'xref--marker-ring 'defvar + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-defvar-c + (elisp--xref-find-definitions 'default-directory) + (list + (cons + (xref-make "(defvar default-directory)" + (xref-make-elisp-location 'default-directory 'defvar "src/buffer.c")) + ;; IMPROVEME: we might be able to compute this target + "DEFVAR_PER_BUFFER (\"default-directory\""))) + +(xref-elisp-deftest find-defs-defvar-eval + (elisp--xref-find-definitions (eval '(defvar stephe-leake-defvar nil))) + nil) + +(xref-elisp-deftest find-defs-face-el + (elisp--xref-find-definitions 'font-lock-keyword-face) + ;; 'font-lock-keyword-face is both a face and a var + (list + (xref-make "(defvar font-lock-keyword-face)" + (xref-make-elisp-location + 'font-lock-keyword-face 'defvar + (expand-file-name "../../../lisp/font-lock.el" emacs-test-dir))) + (xref-make "(defface font-lock-keyword-face)" + (xref-make-elisp-location + 'font-lock-keyword-face 'defface + (expand-file-name "../../../lisp/font-lock.el" emacs-test-dir))) + )) + +(xref-elisp-deftest find-defs-face-eval + (elisp--xref-find-definitions (eval '(defface stephe-leake-defface nil ""))) + nil) + +(xref-elisp-deftest find-defs-feature-el + (elisp--xref-find-definitions 'xref) + (list + (cons + (xref-make "(feature xref)" + (xref-make-elisp-location + 'xref 'feature + (expand-file-name "../../../lisp/progmodes/xref.el" emacs-test-dir))) + ";;; Code:") + )) + +(xref-elisp-deftest find-defs-feature-eval + (elisp--xref-find-definitions (eval '(provide 'stephe-leake-feature))) + nil) + +(ert-deftest elisp--preceding-sexp--char-name () + (with-temp-buffer + (emacs-lisp-mode) + (insert "?\\N{HEAVY CHECK MARK}") + (should (equal (elisp--preceding-sexp) ?\N{HEAVY CHECK MARK})))) + +(provide 'elisp-mode-tests) +;;; elisp-mode-tests.el ends here diff --git a/test/lisp/progmodes/etags-tests.el b/test/lisp/progmodes/etags-tests.el new file mode 100644 index 00000000000..a992a17dc46 --- /dev/null +++ b/test/lisp/progmodes/etags-tests.el @@ -0,0 +1,91 @@ +;;; etags-tests.el --- Test suite for etags.el. + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Eli Zaretskii <eliz@gnu.org> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Code: + +(require 'ert) +(require 'etags) + +(defvar his-masters-voice t) + +(defun y-or-n-p (_prompt) + "Replacement for `y-or-n-p' that returns what we tell it to." + his-masters-voice) + +(ert-deftest etags-bug-158 () + "Test finding tags with local and global tags tables." + (let ((buf-with-global-tags (get-buffer-create "*buf-global*")) + (buf-with-local-tags (get-buffer-create "*buf-local*")) + xref-buf) + (set-buffer buf-with-global-tags) + (setq default-directory (expand-file-name ".")) + (visit-tags-table + (expand-file-name "manual/etags/ETAGS.good_1" + (getenv "EMACS_TEST_DIRECTORY"))) + ;; Check that tags in ETAGS.good_1 are recognized. + (setq xref-buf (xref-find-definitions "LL_Task_Procedure_Access/t")) + (should (bufferp xref-buf)) + (kill-buffer xref-buf) + (setq xref-buf (xref-find-definitions "PrintAdd")) + (should (bufferp xref-buf)) + (kill-buffer xref-buf) + ;; Check that tags not in ETAGS.good_1, but in ETAGS.good_3, are + ;; NOT recognized. + (should-error (xref-find-definitions "intNumber") :type 'user-error) + (kill-buffer xref-buf) + (set-buffer buf-with-local-tags) + (setq default-directory (expand-file-name ".")) + (let (his-masters-voice) + (visit-tags-table + (expand-file-name "manual/etags/ETAGS.good_3" + (getenv "EMACS_TEST_DIRECTORY")) + t)) + ;; Check that tags in ETAGS.good_1 are recognized. + (setq xref-buf (xref-find-definitions "LL_Task_Procedure_Access/t")) + (should (bufferp xref-buf)) + (kill-buffer xref-buf) + (setq xref-buf (xref-find-definitions "PrintAdd")) + (should (bufferp xref-buf)) + (kill-buffer xref-buf) + ;; Check that tags in ETAGS.good_3 are recognized. This is a test + ;; for bug#158. + (setq xref-buf (xref-find-definitions "intNumber")) + (should (or (null xref-buf) + (bufferp xref-buf))) + ;; Bug #17326 + (should (string= (file-name-nondirectory + (buffer-local-value 'tags-file-name buf-with-local-tags)) + "ETAGS.good_3")) + (should (string= (file-name-nondirectory + (default-value 'tags-file-name)) + "ETAGS.good_1")) + (if (bufferp xref-buf) (kill-buffer xref-buf)))) + +(ert-deftest etags-bug-23164 () + "Test that setting a local value of tags table doesn't signal errors." + (set-buffer (get-buffer-create "*foobar*")) + (fundamental-mode) + (visit-tags-table + (expand-file-name "manual/etags/ETAGS.good_3" + (getenv "EMACS_TEST_DIRECTORY")) + t) + (should (equal (should-error (xref-find-definitions "foobar123")) + '(user-error "No definitions found for: foobar123")))) diff --git a/test/lisp/progmodes/f90.el b/test/lisp/progmodes/f90.el new file mode 100644 index 00000000000..29c608847f1 --- /dev/null +++ b/test/lisp/progmodes/f90.el @@ -0,0 +1,276 @@ +;;; f90.el --- tests for progmodes/f90.el + +;; Copyright (C) 2011-2016 Free Software Foundation, Inc. + +;; Author: Glenn Morris <rgm@gnu.org> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This file does not have "test" in the name, because it lives under +;; a test/ directory, so that would be superfluous. + +;;; Code: + +(require 'ert) +(require 'f90) + +(defconst f90-test-indent "\ +!! Comment before code. +!!! Comments before code. +#preprocessor before code + +program progname + + implicit none + + integer :: i + + !! Comment. + + do i = 1, 10 + +#preprocessor + + !! Comment. + if ( i % 2 == 0 ) then + !! Comment. + cycle + else + write(*,*) i + end if + end do + +!!! Comment. + +end program progname +" + "Test string for F90 indentation.") + +(ert-deftest f90-test-indent () + "Test F90 indentation." + (with-temp-buffer + (f90-mode) + (insert f90-test-indent) + (indent-rigidly (point-min) (point-max) -999) + (f90-indent-region (point-min) (point-max)) + (should (string-equal (buffer-string) f90-test-indent)))) + +(ert-deftest f90-test-bug3729 () + "Test for http://debbugs.gnu.org/3729 ." + :expected-result :failed + (with-temp-buffer + (f90-mode) + (insert "!! Comment + +include \"file.f90\" + +subroutine test (x) + real x + x = x+1. + return +end subroutine test") + (goto-char (point-min)) + (forward-line 2) + (f90-indent-subprogram) + (should (= 0 (current-indentation))))) + +(ert-deftest f90-test-bug3730 () + "Test for http://debbugs.gnu.org/3730 ." + (with-temp-buffer + (f90-mode) + (insert "a" ) + (move-to-column 68 t) + (insert "(/ x /)") + (f90-do-auto-fill) + (beginning-of-line) + (skip-chars-forward "[ \t]") + (should (equal "&(/" (buffer-substring (point) (+ 3 (point))))))) + +;; TODO bug#5593 + +(ert-deftest f90-test-bug8691 () + "Test for http://debbugs.gnu.org/8691 ." + (with-temp-buffer + (f90-mode) + (insert "module modname +type, bind(c) :: type1 +integer :: part1 +end type type1 +end module modname") + (f90-indent-subprogram) + (forward-line -1) + (should (= 2 (current-indentation))))) + +;; TODO bug#8812 + +(ert-deftest f90-test-bug8820 () + "Test for http://debbugs.gnu.org/8820 ." + (with-temp-buffer + (f90-mode) + (should (eq (char-syntax ?%) (string-to-char "."))))) + +(ert-deftest f90-test-bug9553a () + "Test for http://debbugs.gnu.org/9553 ." + (with-temp-buffer + (f90-mode) + (insert "!!!") + (dotimes (_i 20) (insert " aaaa")) + (f90-do-auto-fill) + (beginning-of-line) + ;; This gives a more informative failure than looking-at. + (should (equal "!!! a" (buffer-substring (point) (+ 5 (point))))))) + +(ert-deftest f90-test-bug9553b () + "Test for http://debbugs.gnu.org/9553 ." + (with-temp-buffer + (f90-mode) + (insert "!!!") + (dotimes (_i 13) (insert " aaaa")) + (insert "a, aaaa") + (f90-do-auto-fill) + (beginning-of-line) + (should (equal "!!! a" (buffer-substring (point) (+ 5 (point))))))) + +(ert-deftest f90-test-bug9690 () + "Test for http://debbugs.gnu.org/9690 ." + (with-temp-buffer + (f90-mode) + (insert "#include \"foo.h\"") + (f90-indent-line) + (should (= 0 (current-indentation))))) + +(ert-deftest f90-test-bug13138 () + "Test for http://debbugs.gnu.org/13138 ." + (with-temp-buffer + (f90-mode) + (insert "program prog + integer :: i = & +#ifdef foo + & 1 +#else + & 2 +#endif + + write(*,*) i +end program prog") + (goto-char (point-min)) + (forward-line 2) + (f90-indent-subprogram) + (should (= 0 (current-indentation))))) + +(ert-deftest f90-test-bug-19809 () + "Test for http://debbugs.gnu.org/19809 ." + (with-temp-buffer + (f90-mode) + ;; The Fortran standard says that continued strings should have + ;; '&' at the start of continuation lines, but it seems gfortran + ;; allows them to be absent (albeit with a warning). + (insert "program prog + write (*,*), '& +end program prog' +end program prog") + (goto-char (point-min)) + (f90-end-of-subprogram) + (should (= (point) (point-max))))) + +(ert-deftest f90-test-bug20680 () + "Test for http://debbugs.gnu.org/20680 ." + (with-temp-buffer + (f90-mode) + (insert "module modname +type, extends ( sometype ) :: type1 +integer :: part1 +end type type1 +end module modname") + (f90-indent-subprogram) + (forward-line -1) + (should (= 2 (current-indentation))))) + +(ert-deftest f90-test-bug20680b () + "Test for http://debbugs.gnu.org/20680 ." + (with-temp-buffer + (f90-mode) + (insert "module modname +enum, bind(c) +enumerator :: e1 = 0 +end enum +end module modname") + (f90-indent-subprogram) + (forward-line -1) + (should (= 2 (current-indentation))))) + +(ert-deftest f90-test-bug20969 () + "Test for http://debbugs.gnu.org/20969 ." + (with-temp-buffer + (f90-mode) + (insert "module modname +type, extends ( sometype ), private :: type1 +integer :: part1 +end type type1 +end module modname") + (f90-indent-subprogram) + (forward-line -1) + (should (= 2 (current-indentation))))) + +(ert-deftest f90-test-bug20969b () + "Test for http://debbugs.gnu.org/20969 ." + (with-temp-buffer + (f90-mode) + (insert "module modname +type, private, extends ( sometype ) :: type1 +integer :: part1 +end type type1 +end module modname") + (f90-indent-subprogram) + (forward-line -1) + (should (= 2 (current-indentation))))) + +(ert-deftest f90-test-bug21794 () + "Test for http://debbugs.gnu.org/21794 ." + (with-temp-buffer + (f90-mode) + (insert "program prog +do i=1,10 +associate (x => xa(i), y => ya(i)) +a(x,y,i) = fun(x,y,i) +end associate +end do +end program prog") + (f90-indent-subprogram) + (forward-line -2) + (should (= 5 (current-indentation))))) + +(ert-deftest f90-test-bug25039 () + "Test for http://debbugs.gnu.org/25039 ." + (with-temp-buffer + (f90-mode) + (insert "program prog +select type (a) +class is (c1) +x = 1 +type is (t1) +x = 2 +end select +end program prog") + (f90-indent-subprogram) + (forward-line -3) + (should (= 2 (current-indentation))) ; type is + (forward-line -2) + (should (= 2 (current-indentation))))) ; class is + +;;; f90.el ends here diff --git a/test/lisp/progmodes/flymake-resources/Makefile b/test/lisp/progmodes/flymake-resources/Makefile new file mode 100644 index 00000000000..0f3f39791c8 --- /dev/null +++ b/test/lisp/progmodes/flymake-resources/Makefile @@ -0,0 +1,13 @@ +# Makefile for flymake tests + +CC_OPTS = -Wall + +## Recent gcc (e.g. 4.8.2 on RHEL7) can automatically colorize their output, +## which can confuse flymake. Set GCC_COLORS to disable that. +## This only seems to be an issue in batch mode, where you would not +## normally use flymake, so it seems like just avoiding the issue +## in this test is fine. Set flymake-log-level to 3 to investigate. +check-syntax: + GCC_COLORS= $(CC) $(CC_OPTS) ${CHK_SOURCES} + +# eof diff --git a/test/lisp/progmodes/flymake-resources/test.c b/test/lisp/progmodes/flymake-resources/test.c new file mode 100644 index 00000000000..3a3926131f5 --- /dev/null +++ b/test/lisp/progmodes/flymake-resources/test.c @@ -0,0 +1,5 @@ +int main() +{ + char c = 1000; + return c; +} diff --git a/test/lisp/progmodes/flymake-resources/test.pl b/test/lisp/progmodes/flymake-resources/test.pl new file mode 100644 index 00000000000..d5abcb47e7f --- /dev/null +++ b/test/lisp/progmodes/flymake-resources/test.pl @@ -0,0 +1,2 @@ +@arr = [1,2,3,4]; +my $b = @arr[1]; diff --git a/test/lisp/progmodes/flymake-tests.el b/test/lisp/progmodes/flymake-tests.el new file mode 100644 index 00000000000..386516190bb --- /dev/null +++ b/test/lisp/progmodes/flymake-tests.el @@ -0,0 +1,80 @@ +;;; flymake-tests.el --- Test suite for flymake + +;; Copyright (C) 2011-2016 Free Software Foundation, Inc. + +;; Author: Eduard Wiebe <usenet@pusto.de> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: +(require 'ert) +(require 'flymake) + +(defvar flymake-tests-data-directory + (expand-file-name "lisp/progmodes/flymake-resources" (getenv "EMACS_TEST_DIRECTORY")) + "Directory containing flymake test data.") + + +;; Warning predicate +(defun flymake-tests--current-face (file predicate) + (let ((buffer (find-file-noselect + (expand-file-name file flymake-tests-data-directory))) + (process-environment (cons "LC_ALL=C" process-environment)) + (i 0)) + (unwind-protect + (with-current-buffer buffer + (setq-local flymake-warning-predicate predicate) + (goto-char (point-min)) + (flymake-mode 1) + ;; Weirdness here... http://debbugs.gnu.org/17647#25 + (while (and flymake-is-running (< (setq i (1+ i)) 10)) + (sleep-for (+ 0.5 flymake-no-changes-timeout))) + (flymake-goto-next-error) + (face-at-point)) + (and buffer (let (kill-buffer-query-functions) (kill-buffer buffer)))))) + +(ert-deftest warning-predicate-rx-gcc () + "Test GCC warning via regexp predicate." + (skip-unless (and (executable-find "gcc") (executable-find "make"))) + (should (eq 'flymake-warnline + (flymake-tests--current-face "test.c" "^[Ww]arning")))) + +(ert-deftest warning-predicate-function-gcc () + "Test GCC warning via function predicate." + (skip-unless (and (executable-find "gcc") (executable-find "make"))) + (should (eq 'flymake-warnline + (flymake-tests--current-face "test.c" + (lambda (msg) (string-match "^[Ww]arning" msg)))))) + +(ert-deftest warning-predicate-rx-perl () + "Test perl warning via regular expression predicate." + (skip-unless (executable-find "perl")) + (should (eq 'flymake-warnline + (flymake-tests--current-face "test.pl" "^Scalar value")))) + +(ert-deftest warning-predicate-function-perl () + "Test perl warning via function predicate." + (skip-unless (executable-find "perl")) + (should (eq 'flymake-warnline + (flymake-tests--current-face + "test.pl" + (lambda (msg) (string-match "^Scalar value" msg)))))) + +(provide 'flymake-tests) + +;;; flymake.el ends here diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el new file mode 100644 index 00000000000..f6564dd58cc --- /dev/null +++ b/test/lisp/progmodes/python-tests.el @@ -0,0 +1,5295 @@ +;;; python-tests.el --- Test suite for python.el + +;; Copyright (C) 2013-2016 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(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 + (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. +BODY is code to be executed within the temp buffer. Point is +always located at the beginning of buffer." + (declare (indent 1) (debug t)) + ;; temp-file never actually used for anything? + `(let* ((temp-file (make-temp-file "python-tests" nil ".py")) + (buffer (find-file-noselect temp-file)) + (python-indent-guess-indent-offset nil)) + (unwind-protect + (with-current-buffer buffer + (python-mode) + (insert ,contents) + (goto-char (point-min)) + ,@body) + (and buffer (kill-buffer buffer)) + (delete-file temp-file)))) + +(defun python-tests-look-at (string &optional num restore-point) + "Move point at beginning of STRING in the current buffer. +Optional argument NUM defaults to 1 and is an integer indicating +how many occurrences must be found, when positive the search is +done forwards, otherwise backwards. When RESTORE-POINT is +non-nil the point is not moved but the position found is still +returned. When searching forward and point is already looking at +STRING, it is skipped so the next STRING occurrence is selected." + (let* ((num (or num 1)) + (starting-point (point)) + (string (regexp-quote string)) + (search-fn (if (> num 0) #'re-search-forward #'re-search-backward)) + (deinc-fn (if (> num 0) #'1- #'1+)) + (found-point)) + (prog2 + (catch 'exit + (while (not (= num 0)) + (when (and (> num 0) + (looking-at string)) + ;; Moving forward and already looking at STRING, skip it. + (forward-char (length (match-string-no-properties 0)))) + (and (not (funcall search-fn string nil t)) + (throw 'exit t)) + (when (> num 0) + ;; `re-search-forward' leaves point at the end of the + ;; occurrence, move back so point is at the beginning + ;; instead. + (forward-char (- (length (match-string-no-properties 0))))) + (setq + num (funcall deinc-fn num) + found-point (point)))) + found-point + (and restore-point (goto-char starting-point))))) + +(defun python-tests-self-insert (char-or-str) + "Call `self-insert-command' for chars in CHAR-OR-STR." + (let ((chars + (cond + ((characterp char-or-str) + (list char-or-str)) + ((stringp char-or-str) + (string-to-list char-or-str)) + ((not + (cl-remove-if #'characterp char-or-str)) + char-or-str) + (t (error "CHAR-OR-STR must be a char, string, or list of char"))))) + (mapc + (lambda (char) + (let ((last-command-event char)) + (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))))) + + +;;; Tests for your tests, so you can test while you test. + +(ert-deftest python-tests-look-at-1 () + "Test forward movement." + (python-tests-with-temp-buffer + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua." + (let ((expected (save-excursion + (dotimes (i 3) + (re-search-forward "et" nil t)) + (forward-char -2) + (point)))) + (should (= (python-tests-look-at "et" 3 t) expected)) + ;; Even if NUM is bigger than found occurrences the point of last + ;; one should be returned. + (should (= (python-tests-look-at "et" 6 t) expected)) + ;; If already looking at STRING, it should skip it. + (dotimes (i 2) (re-search-forward "et")) + (forward-char -2) + (should (= (python-tests-look-at "et") expected))))) + +(ert-deftest python-tests-look-at-2 () + "Test backward movement." + (python-tests-with-temp-buffer + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua." + (let ((expected + (save-excursion + (re-search-forward "et" nil t) + (forward-char -2) + (point)))) + (dotimes (i 3) + (re-search-forward "et" nil t)) + (should (= (python-tests-look-at "et" -3 t) expected)) + (should (= (python-tests-look-at "et" -6 t) expected))))) + + +;;; Bindings + + +;;; Python specialized rx + + +;;; Font-lock and syntax + +(ert-deftest python-syntax-after-python-backspace () + ;; `python-indent-dedent-line-backspace' garbles syntax + :expected-result :failed + (python-tests-with-temp-buffer + "\"\"\"" + (goto-char (point-max)) + (python-indent-dedent-line-backspace 1) + (should (string= (buffer-string) "\"\"")) + (should (null (nth 3 (syntax-ppss)))))) + + +;;; Indentation + +;; See: http://www.python.org/dev/peps/pep-0008/#indentation + +(ert-deftest python-indent-pep8-1 () + "First pep8 case." + (python-tests-with-temp-buffer + "# Aligned with opening delimiter +foo = long_function_name(var_one, var_two, + var_three, var_four) +" + (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-comment)) + (should (= (python-indent-calculate-indentation) 0)) + (python-tests-look-at "var_three, var_four)") + (should (eq (car (python-indent-context)) :inside-paren)) + (should (= (python-indent-calculate-indentation) 25)))) + +(ert-deftest python-indent-pep8-2 () + "Second pep8 case." + (python-tests-with-temp-buffer + "# More indentation included to distinguish this from the rest. +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (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-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-newline-start-from-block)) + (should (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "var_four):") + (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-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-pep8-3 () + "Third pep8 case." + (python-tests-with-temp-buffer + "# Extra indentation is not necessary. +foo = long_function_name( + var_one, var_two, + var_three, var_four) +" + (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-comment)) + (should (= (python-indent-calculate-indentation) 0)) + (python-tests-look-at "var_one, var_two,") + (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-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 + "# Contents will be modified to correct indentation +class Blag(object): + def _on_child_complete(self, child_future): + if self.in_terminal_state(): + pass + # We only complete when all our async children have entered a + # terminal state. At that point, if any child failed, we fail +# with the exception with which the first child failed. +" + (python-tests-look-at "# We only complete") + (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 (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "# with the exception") + (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)) + (python-indent-line) + (should (= (current-indentation) 8)) + (python-tests-look-at "# with the exception") + (should (eq (car (python-indent-context)) :after-comment)) + ;; Now everything should be lined up. + (should (= (python-indent-calculate-indentation) 8)))) + +(ert-deftest python-indent-after-comment-2 () + "Test after-comment in weird cases." + (python-tests-with-temp-buffer + "# Contents will be modified to correct indentation +def func(arg): + # I don't do much + return arg + # This comment is badly indented because the user forced so. + # At this line python.el wont dedent, user is always right. + +comment_wins_over_ender = True + +# yeah, that. +" + (python-tests-look-at "# I don't do much") + (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-block-start)) + (should (= (python-indent-calculate-indentation) 4)) + (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)) + ;; ...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)) + ;; Should behave the same for blank lines: potentially a comment. + (forward-line 1) + (should (eq (car (python-indent-context)) :after-comment)) + (should (= (python-indent-calculate-indentation) 4)) + (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 (= (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 + " +data = { + 'key': + { + 'objlist': [ + { + 'pk': 1, + 'name': 'first', + }, + { + 'pk': 2, + 'name': 'second', + } + ] + } +} +" + (python-tests-look-at "data = {") + (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-newline-start)) + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "{") + (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-newline-start)) + (should (= (python-indent-calculate-indentation) 8)) + (python-tests-look-at "{") + (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-newline-start)) + (should (= (python-indent-calculate-indentation) 16)) + (python-tests-look-at "'name': 'first',") + (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-at-closing-nested-paren)) + (should (= (python-indent-calculate-indentation) 12)) + (python-tests-look-at "{") + (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-newline-start)) + (should (= (python-indent-calculate-indentation) 16)) + (python-tests-look-at "'name': 'second',") + (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-at-closing-nested-paren)) + (should (= (python-indent-calculate-indentation) 12)) + (python-tests-look-at "]") + (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-at-closing-nested-paren)) + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "}") + (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren)) + (should (= (python-indent-calculate-indentation) 0)))) + +(ert-deftest python-indent-inside-paren-2 () + "Another more compact paren group style." + (python-tests-with-temp-buffer + " +data = {'key': { + 'objlist': [ + {'pk': 1, + 'name': 'first'}, + {'pk': 2, + 'name': 'second'} + ] +}} +" + (python-tests-look-at "data = {") + (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-newline-start)) + (should (= (python-indent-calculate-indentation) 4)) + (python-tests-look-at "{'pk': 1,") + (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 (= (python-indent-calculate-indentation) 9)) + (python-tests-look-at "{'pk': 2,") + (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 (= (python-indent-calculate-indentation) 9)) + (python-tests-look-at "]") + (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-at-closing-nested-paren)) + (should (= (python-indent-calculate-indentation) 0)) + (python-tests-look-at "}") + (should (eq (car (python-indent-context)) :inside-paren-at-closing-paren)) + (should (= (python-indent-calculate-indentation) 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 (= (python-indent-calculate-indentation) 0)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-after-block-2 () + "A weird (malformed) multiline block statement." + (python-tests-with-temp-buffer + " +def foo(a, b, c={ + 'a': +}): +" + (goto-char (point-max)) + (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-async-block-1 () + "Test PEP492 async def." + (python-tests-with-temp-buffer + " +async def foo(a, b, c=True): +" + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-after-async-block-2 () + "Test PEP492 async with." + (python-tests-with-temp-buffer + " +async with foo(a) as mgr: +" + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-after-async-block-3 () + "Test PEP492 async for." + (python-tests-with-temp-buffer + " +async for a in sequencer(): +" + (should (eq (car (python-indent-context)) :no-indent)) + (should (= (python-indent-calculate-indentation) 0)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-block-start)) + (should (= (python-indent-calculate-indentation) 4)))) + +(ert-deftest python-indent-after-backslash-1 () + "The most common case." + (python-tests-with-temp-buffer + " +from foo.bar.baz import something, something_1 \\\\ + something_2 something_3, \\\\ + something_4, something_5 +" + (python-tests-look-at "from foo.bar.baz import something, something_1") + (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-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 (= (python-indent-calculate-indentation) 4)) + (goto-char (point-max)) + (should (eq (car (python-indent-context)) :after-line)) + (should (= (python-indent-calculate-indentation) 0)))) + +(ert-deftest python-indent-after-backslash-2 () + "A pretty extreme complicated case." + (python-tests-with-temp-buffer + " +objects = Thing.objects.all() \\\\ + .filter( + type='toy', + status='bought' + ) \\\\ + .aggregate( + Sum('amount') + ) \\\\ + .values_list() +" + (python-tests-look-at "objects = Thing.objects.all()") + (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-dotted-continuation)) + (should (= (python-indent-calculate-indentation) 23)) + (python-tests-look-at "type='toy',") + (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-newline-start)) + (should (= (python-indent-calculate-indentation) 27)) + (python-tests-look-at ") \\\\") + (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-dotted-continuation)) + (should (= (python-indent-calculate-indentation) 23)) + (python-tests-look-at "Sum('amount')") + (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-at-closing-paren)) + (should (= (python-indent-calculate-indentation) 23)) + (python-tests-look-at ".values_list()") + (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 (= (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): + if self.baz: + return (1, + 2, + 3) + + 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 (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 + + eiusmod tempor incididunt ut labore et dolore magna aliqua. + ''' + def bar(self): + \"return (1, 2, 3).\" + if self.baz: + return (1, + 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 (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 (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 (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 (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) + finally: + cleanup() + elif +" + (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) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + else + finally: + data.free() +" + (python-tests-look-at "else\n") + (should (eq (car (python-indent-context)) :at-dedenter-block-start)) + (should (= (python-indent-calculate-indentation) 8)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 4)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 0)) + (python-indent-line t) + (should (= (python-indent-calculate-indentation t) 8)))) + +(ert-deftest python-indent-dedenters-3 () + "Test de-indentation for the except keyword." + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except +" + (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)) :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) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + finally: + if cleanup: + do_cleanup() + else +" + (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)) :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 + else: + pass + else: +" + (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." + (python-tests-with-temp-buffer + " +if (a == 1 or + a == 2): + pass +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 (= (python-indent-calculate-indentation) 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." + (python-tests-with-temp-buffer + " +def a(): + pass + +def b() +" + (python-tests-look-at "def b()") + (goto-char (line-end-position)) + (python-tests-self-insert ":") + (should (= (current-indentation) 0)))) + +(ert-deftest python-indent-electric-colon-2 () + "Test indentation case for dedenter." + (python-tests-with-temp-buffer + " +if do: + something() + else +" + (python-tests-look-at "else") + (goto-char (line-end-position)) + (python-tests-self-insert ":") + (should (= (current-indentation) 0)))) + +(ert-deftest python-indent-electric-colon-3 () + "Test indentation case for multi-line dedenter." + (python-tests-with-temp-buffer + " +if do: + something() + elif (this + and + that) +" + (python-tests-look-at "that)") + (goto-char (line-end-position)) + (python-tests-self-insert ":") + (python-tests-look-at "elif" -1) + (should (= (current-indentation) 0)) + (python-tests-look-at "and") + (should (= (current-indentation) 6)) + (python-tests-look-at "that)") + (should (= (current-indentation) 6)))) + +(ert-deftest python-indent-region-1 () + "Test indentation case from Bug#18843." + (let ((contents " +def foo (): + try: + pass + except: + pass +")) + (python-tests-with-temp-buffer + contents + (python-indent-region (point-min) (point-max)) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + contents))))) + +(ert-deftest python-indent-region-2 () + "Test region indentation on comments." + (let ((contents " +def f(): + if True: + pass + +# This is +# some multiline +# comment +")) + (python-tests-with-temp-buffer + contents + (python-indent-region (point-min) (point-max)) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + contents))))) + +(ert-deftest python-indent-region-3 () + "Test region indentation on comments." + (let ((contents " +def f(): + if True: + pass +# This is +# some multiline +# comment +") + (expected " +def f(): + if True: + pass + # This is + # some multiline + # comment +")) + (python-tests-with-temp-buffer + contents + (python-indent-region (point-min) (point-max)) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + expected))))) + +(ert-deftest python-indent-region-4 () + "Test region indentation block starts, dedenters and enders." + (let ((contents " +def f(): + if True: +a = 5 + else: + a = 10 + return a +") + (expected " +def f(): + if True: + a = 5 + else: + a = 10 + return a +")) + (python-tests-with-temp-buffer + contents + (python-indent-region (point-min) (point-max)) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + expected))))) + +(ert-deftest python-indent-region-5 () + "Test region indentation for docstrings." + (let ((contents " +def f(): +''' +this is + a multiline +string +''' + x = \\ + ''' +this is an arbitrarily + indented multiline + string +''' +") + (expected " +def f(): + ''' + this is + a multiline + string + ''' + x = \\ + ''' +this is an arbitrarily + indented multiline + string +''' +")) + (python-tests-with-temp-buffer + contents + (python-indent-region (point-min) (point-max)) + (should (string= (buffer-substring-no-properties (point-min) (point-max)) + expected))))) + + +;;; 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))))) + + +;;; Navigation + +(ert-deftest python-nav-beginning-of-defun-1 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (python-tests-look-at "return wrap") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def wrapped_f(*args):" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "def wrapped_f(*args):" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def wwrap(f):" -1) + (beginning-of-line) + (point)))) + (python-tests-look-at "def wwrap(f):" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def decoratorFunctionWithArguments" -1) + (beginning-of-line) + (point)))))) + +(ert-deftest python-nav-beginning-of-defun-2 () + (python-tests-with-temp-buffer + " +class C(object): + + def m(self): + self.c() + + def b(): + pass + + def a(): + pass + + def c(self): + pass +" + ;; Nested defuns, are handled with care. + (python-tests-look-at "def c(self):") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def m(self):" -1) + (beginning-of-line) + (point)))) + ;; Defuns on same levels should be respected. + (python-tests-look-at "def a():" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def b():" -1) + (beginning-of-line) + (point)))) + ;; Jump to a top level defun. + (python-tests-look-at "def b():" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def m(self):" -1) + (beginning-of-line) + (point)))) + ;; Jump to a top level defun again. + (python-tests-look-at "def m(self):" -1) + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "class C(object):" -1) + (beginning-of-line) + (point)))))) + +(ert-deftest python-nav-beginning-of-defun-3 () + (python-tests-with-temp-buffer + " +class C(object): + + async def m(self): + return await self.c() + + async def c(self): + pass +" + (python-tests-look-at "self.c()") + (should (= (save-excursion + (python-nav-beginning-of-defun) + (point)) + (save-excursion + (python-tests-look-at "async def m" -1) + (beginning-of-line) + (point)))))) + +(ert-deftest python-nav-end-of-defun-1 () + (python-tests-with-temp-buffer + " +class C(object): + + def m(self): + self.c() + + def b(): + pass + + def a(): + pass + + def c(self): + pass +" + (should (= (save-excursion + (python-tests-look-at "class C(object):") + (python-nav-end-of-defun) + (point)) + (save-excursion + (point-max)))) + (should (= (save-excursion + (python-tests-look-at "def m(self):") + (python-nav-end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def c(self):") + (forward-line -1) + (point)))) + (should (= (save-excursion + (python-tests-look-at "def b():") + (python-nav-end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "def b():") + (forward-line 2) + (point)))) + (should (= (save-excursion + (python-tests-look-at "def c(self):") + (python-nav-end-of-defun) + (point)) + (save-excursion + (point-max)))))) + +(ert-deftest python-nav-end-of-defun-2 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (should (= (save-excursion + (python-tests-look-at "def decoratorFunctionWithArguments") + (python-nav-end-of-defun) + (point)) + (save-excursion + (point-max)))) + (should (= (save-excursion + (python-tests-look-at "@decoratorFunctionWithArguments") + (python-nav-end-of-defun) + (point)) + (save-excursion + (point-max)))) + (should (= (save-excursion + (python-tests-look-at "def wwrap(f):") + (python-nav-end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "return wwrap") + (line-beginning-position)))) + (should (= (save-excursion + (python-tests-look-at "def wrapped_f(*args):") + (python-nav-end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "return wrapped_f") + (line-beginning-position)))) + (should (= (save-excursion + (python-tests-look-at "f(*args)") + (python-nav-end-of-defun) + (point)) + (save-excursion + (python-tests-look-at "return wrapped_f") + (line-beginning-position)))))) + +(ert-deftest python-nav-backward-defun-1 () + (python-tests-with-temp-buffer + " +class A(object): # A + + def a(self): # a + pass + + def b(self): # b + pass + + class B(object): # B + + class C(object): # C + + def d(self): # d + pass + + # def e(self): # e + # pass + + def c(self): # c + pass + + # def d(self): # d + # pass +" + (goto-char (point-max)) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " def c(self): # c" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " def d(self): # d" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " class C(object): # C" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " class B(object): # B" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " def b(self): # b" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " def a(self): # a" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at "class A(object): # A" -1))) + (should (not (python-nav-backward-defun))))) + +(ert-deftest python-nav-backward-defun-2 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (goto-char (point-max)) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " def wrapped_f(*args):" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at " def wwrap(f):" -1))) + (should (= (save-excursion (python-nav-backward-defun)) + (python-tests-look-at "def decoratorFunctionWithArguments(arg1, arg2, arg3):" -1))) + (should (not (python-nav-backward-defun))))) + +(ert-deftest python-nav-backward-defun-3 () + (python-tests-with-temp-buffer + " +''' + def u(self): + pass + + def v(self): + pass + + def w(self): + pass +''' + +class A(object): + pass +" + (goto-char (point-min)) + (let ((point (python-tests-look-at "class A(object):"))) + (should (not (python-nav-backward-defun))) + (should (= point (point)))))) + +(ert-deftest python-nav-forward-defun-1 () + (python-tests-with-temp-buffer + " +class A(object): # A + + def a(self): # a + pass + + def b(self): # b + pass + + class B(object): # B + + class C(object): # C + + def d(self): # d + pass + + # def e(self): # e + # pass + + def c(self): # c + pass + + # def d(self): # d + # pass +" + (goto-char (point-min)) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(object): # A"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(self): # a"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(self): # b"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(object): # B"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(object): # C"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(self): # d"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(self): # c"))) + (should (not (python-nav-forward-defun))))) + +(ert-deftest python-nav-forward-defun-2 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (goto-char (point-min)) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(arg1, arg2, arg3):"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(f):"))) + (should (= (save-excursion (python-nav-forward-defun)) + (python-tests-look-at "(*args):"))) + (should (not (python-nav-forward-defun))))) + +(ert-deftest python-nav-forward-defun-3 () + (python-tests-with-temp-buffer + " +class A(object): + pass + +''' + def u(self): + pass + + def v(self): + pass + + def w(self): + pass +''' +" + (goto-char (point-min)) + (let ((point (python-tests-look-at "(object):"))) + (should (not (python-nav-forward-defun))) + (should (= point (point)))))) + +(ert-deftest python-nav-beginning-of-statement-1 () + (python-tests-with-temp-buffer + " +v1 = 123 + \ + 456 + \ + 789 +v2 = (value1, + value2, + + value3, + value4) +v3 = ('this is a string' + + 'that is continued' + 'between lines' + 'within a paren', + # this is a comment, yo + 'continue previous line') +v4 = ''' +a very long +string +''' +" + (python-tests-look-at "v2 =") + (python-util-forward-comment -1) + (should (= (save-excursion + (python-nav-beginning-of-statement) + (point)) + (python-tests-look-at "v1 =" -1 t))) + (python-tests-look-at "v3 =") + (python-util-forward-comment -1) + (should (= (save-excursion + (python-nav-beginning-of-statement) + (point)) + (python-tests-look-at "v2 =" -1 t))) + (python-tests-look-at "v4 =") + (python-util-forward-comment -1) + (should (= (save-excursion + (python-nav-beginning-of-statement) + (point)) + (python-tests-look-at "v3 =" -1 t))) + (goto-char (point-max)) + (python-util-forward-comment -1) + (should (= (save-excursion + (python-nav-beginning-of-statement) + (point)) + (python-tests-look-at "v4 =" -1 t))))) + +(ert-deftest python-nav-end-of-statement-1 () + (python-tests-with-temp-buffer + " +v1 = 123 + \ + 456 + \ + 789 +v2 = (value1, + value2, + + value3, + value4) +v3 = ('this is a string' + + 'that is continued' + 'between lines' + 'within a paren', + # this is a comment, yo + 'continue previous line') +v4 = ''' +a very long +string +''' +" + (python-tests-look-at "v1 =") + (should (= (save-excursion + (python-nav-end-of-statement) + (point)) + (save-excursion + (python-tests-look-at "789") + (line-end-position)))) + (python-tests-look-at "v2 =") + (should (= (save-excursion + (python-nav-end-of-statement) + (point)) + (save-excursion + (python-tests-look-at "value4)") + (line-end-position)))) + (python-tests-look-at "v3 =") + (should (= (save-excursion + (python-nav-end-of-statement) + (point)) + (save-excursion + (python-tests-look-at + "'continue previous line')") + (line-end-position)))) + (python-tests-look-at "v4 =") + (should (= (save-excursion + (python-nav-end-of-statement) + (point)) + (save-excursion + (goto-char (point-max)) + (python-util-forward-comment -1) + (point)))))) + +(ert-deftest python-nav-forward-statement-1 () + (python-tests-with-temp-buffer + " +v1 = 123 + \ + 456 + \ + 789 +v2 = (value1, + value2, + + value3, + value4) +v3 = ('this is a string' + + 'that is continued' + 'between lines' + 'within a paren', + # this is a comment, yo + 'continue previous line') +v4 = ''' +a very long +string +''' +" + (python-tests-look-at "v1 =") + (should (= (save-excursion + (python-nav-forward-statement) + (point)) + (python-tests-look-at "v2 ="))) + (should (= (save-excursion + (python-nav-forward-statement) + (point)) + (python-tests-look-at "v3 ="))) + (should (= (save-excursion + (python-nav-forward-statement) + (point)) + (python-tests-look-at "v4 ="))) + (should (= (save-excursion + (python-nav-forward-statement) + (point)) + (point-max))))) + +(ert-deftest python-nav-backward-statement-1 () + (python-tests-with-temp-buffer + " +v1 = 123 + \ + 456 + \ + 789 +v2 = (value1, + value2, + + value3, + value4) +v3 = ('this is a string' + + 'that is continued' + 'between lines' + 'within a paren', + # this is a comment, yo + 'continue previous line') +v4 = ''' +a very long +string +''' +" + (goto-char (point-max)) + (should (= (save-excursion + (python-nav-backward-statement) + (point)) + (python-tests-look-at "v4 =" -1))) + (should (= (save-excursion + (python-nav-backward-statement) + (point)) + (python-tests-look-at "v3 =" -1))) + (should (= (save-excursion + (python-nav-backward-statement) + (point)) + (python-tests-look-at "v2 =" -1))) + (should (= (save-excursion + (python-nav-backward-statement) + (point)) + (python-tests-look-at "v1 =" -1))))) + +(ert-deftest python-nav-backward-statement-2 () + :expected-result :failed + (python-tests-with-temp-buffer + " +v1 = 123 + \ + 456 + \ + 789 +v2 = (value1, + value2, + + value3, + value4) +" + ;; FIXME: For some reason `python-nav-backward-statement' is moving + ;; back two sentences when starting from 'value4)'. + (goto-char (point-max)) + (python-util-forward-comment -1) + (should (= (save-excursion + (python-nav-backward-statement) + (point)) + (python-tests-look-at "v2 =" -1 t))))) + +(ert-deftest python-nav-beginning-of-block-1 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (python-tests-look-at "return wwrap") + (should (= (save-excursion + (python-nav-beginning-of-block) + (point)) + (python-tests-look-at "def decoratorFunctionWithArguments" -1))) + (python-tests-look-at "print 'Inside wwrap()'") + (should (= (save-excursion + (python-nav-beginning-of-block) + (point)) + (python-tests-look-at "def wwrap(f):" -1))) + (python-tests-look-at "print 'After f(*args)'") + (end-of-line) + (should (= (save-excursion + (python-nav-beginning-of-block) + (point)) + (python-tests-look-at "def wrapped_f(*args):" -1))) + (python-tests-look-at "return wrapped_f") + (should (= (save-excursion + (python-nav-beginning-of-block) + (point)) + (python-tests-look-at "def wwrap(f):" -1))))) + +(ert-deftest python-nav-end-of-block-1 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (python-tests-look-at "def decoratorFunctionWithArguments") + (should (= (save-excursion + (python-nav-end-of-block) + (point)) + (save-excursion + (goto-char (point-max)) + (python-util-forward-comment -1) + (point)))) + (python-tests-look-at "def wwrap(f):") + (should (= (save-excursion + (python-nav-end-of-block) + (point)) + (save-excursion + (python-tests-look-at "return wrapped_f") + (line-end-position)))) + (end-of-line) + (should (= (save-excursion + (python-nav-end-of-block) + (point)) + (save-excursion + (python-tests-look-at "return wrapped_f") + (line-end-position)))) + (python-tests-look-at "f(*args)") + (should (= (save-excursion + (python-nav-end-of-block) + (point)) + (save-excursion + (python-tests-look-at "print 'After f(*args)'") + (line-end-position)))))) + +(ert-deftest python-nav-forward-block-1 () + "This also accounts as a test for `python-nav-backward-block'." + (python-tests-with-temp-buffer + " +if request.user.is_authenticated(): + # def block(): + # pass + try: + profile = request.user.get_profile() + except Profile.DoesNotExist: + profile = Profile.objects.create(user=request.user) + else: + if profile.stats: + profile.recalculate_stats() + else: + profile.clear_stats() + finally: + profile.views += 1 + profile.save() +" + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "if request.user.is_authenticated():"))) + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "try:"))) + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "except Profile.DoesNotExist:"))) + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "else:"))) + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "if profile.stats:"))) + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "else:"))) + (should (= (save-excursion (python-nav-forward-block)) + (python-tests-look-at "finally:"))) + ;; When point is at the last block, leave it there and return nil + (should (not (save-excursion (python-nav-forward-block)))) + ;; Move backwards, and even if the number of moves is less than the + ;; provided argument return the point. + (should (= (save-excursion (python-nav-forward-block -10)) + (python-tests-look-at + "if request.user.is_authenticated():" -1))))) + +(ert-deftest python-nav-forward-sexp-1 () + (python-tests-with-temp-buffer + " +a() +b() +c() +" + (python-tests-look-at "a()") + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (beginning-of-line) + (looking-at "a()"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (beginning-of-line) + (looking-at "b()"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (beginning-of-line) + (looking-at "c()"))) + ;; 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()"))) + (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 () + (python-tests-with-temp-buffer + " +def func(): + if True: + aaa = bbb + ccc = ddd + eee = fff + return ggg +" + (python-tests-look-at "aa =") + (python-nav-forward-sexp) + (should (looking-at " = bbb")) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (back-to-indentation) + (looking-at "aaa = bbb"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (back-to-indentation) + (looking-at "ccc = ddd"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (back-to-indentation) + (looking-at "eee = fff"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should (save-excursion + (back-to-indentation) + (looking-at "return ggg"))) + (python-nav-forward-sexp -1) + (should (looking-at "def func():")))) + +(ert-deftest python-nav-forward-sexp-3 () + (python-tests-with-temp-buffer + " +from some_module import some_sub_module +from another_module import another_sub_module + +def another_statement(): + pass +" + (python-tests-look-at "some_module") + (python-nav-forward-sexp) + (should (looking-at " import")) + (python-nav-forward-sexp) + (should (looking-at " some_sub_module")) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should + (save-excursion + (back-to-indentation) + (looking-at + "from some_module import some_sub_module"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should + (save-excursion + (back-to-indentation) + (looking-at + "from another_module import another_sub_module"))) + (python-nav-forward-sexp) + (should (looking-at "$")) + (should + (save-excursion + (back-to-indentation) + (looking-at + "pass"))) + (python-nav-forward-sexp -1) + (should (looking-at "def another_statement():")) + (python-nav-forward-sexp -1) + (should (looking-at "from another_module import another_sub_module")) + (python-nav-forward-sexp -1) + (should (looking-at "from some_module import some_sub_module")))) + +(ert-deftest python-nav-forward-sexp-safe-1 () + (python-tests-with-temp-buffer + " +profile = Profile.objects.create(user=request.user) +profile.notify() +" + (python-tests-look-at "profile =") + (python-nav-forward-sexp-safe 1) + (should (looking-at "$")) + (beginning-of-line 1) + (python-tests-look-at "user=request.user") + (python-nav-forward-sexp-safe -1) + (should (looking-at "(user=request.user)")) + (python-nav-forward-sexp-safe -4) + (should (looking-at "profile =")) + (python-tests-look-at "user=request.user") + (python-nav-forward-sexp-safe 3) + (should (looking-at ")")) + (python-nav-forward-sexp-safe 1) + (should (looking-at "$")) + (python-nav-forward-sexp-safe 1) + (should (looking-at "$")))) + +(ert-deftest python-nav-up-list-1 () + (python-tests-with-temp-buffer + " +def f(): + if True: + return [i for i in range(3)] +" + (python-tests-look-at "3)]") + (python-nav-up-list) + (should (looking-at "]")) + (python-nav-up-list) + (should (looking-at "$")))) + +(ert-deftest python-nav-backward-up-list-1 () + :expected-result :failed + (python-tests-with-temp-buffer + " +def f(): + if True: + return [i for i in range(3)] +" + (python-tests-look-at "3)]") + (python-nav-backward-up-list) + (should (looking-at "(3)\\]")) + (python-nav-backward-up-list) + (should (looking-at + "\\[i for i in range(3)\\]")) + ;; FIXME: Need to move to beginning-of-statement. + (python-nav-backward-up-list) + (should (looking-at + "return \\[i for i in range(3)\\]")) + (python-nav-backward-up-list) + (should (looking-at "if True:")) + (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"))))) + +(ert-deftest python-bob-infloop-avoid () + "Test that strings at BOB don't confuse syntax analysis. Bug#24905" + (python-tests-with-temp-buffer + " \"\n" + (goto-char (point-min)) + (font-lock-fontify-buffer))) + + +;;; Shell integration + +(defvar python-tests-shell-interpreter "python") + +(ert-deftest python-shell-get-process-name-1 () + "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)) + (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-name)))))) + +(ert-deftest python-shell-internal-get-process-name-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'." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let ((python-shell-interpreter (executable-find + python-tests-shell-interpreter)) + (python-shell-interpreter-args "-B")) + (should (string= + (format "%s %s" + (shell-quote-argument python-shell-interpreter) + python-shell-interpreter-args) + (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* ((python-shell-process-environment + '("TESTVAR1=value1" "TESTVAR2=value2")) + (process-environment (python-shell-calculate-process-environment))) + (should (equal (getenv "TESTVAR1") "value1")) + (should (equal (getenv "TESTVAR2") "value2")))) + +(ert-deftest python-shell-calculate-process-environment-2 () + "Test `python-shell-extra-pythonpaths' modification." + (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 "/path1" path-separator + "/path2" path-separator "/path0"))))) + +(ert-deftest python-shell-calculate-process-environment-3 () + "Test `python-shell-virtualenv-root' modification." + (let* ((python-shell-virtualenv-root "/env") + (process-environment + (let (process-environment process-environment) + (setenv "PYTHONHOME" "/home") + (setenv "VIRTUAL_ENV") + (python-shell-calculate-process-environment)))) + (should (not (getenv "PYTHONHOME"))) + (should (string= (getenv "VIRTUAL_ENV") "/env")))) + +(ert-deftest python-shell-calculate-process-environment-4 () + "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 () + "Test PYTHONUNBUFFERED when `python-shell-unbuffered' is nil." + (let* ((python-shell-unbuffered nil) + (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* ((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-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." + (skip-unless (executable-find python-tests-shell-interpreter)) + ;; The interpreter can get killed too quickly to allow it to clean + ;; up the tempfiles that the default python-shell-setup-codes create, + ;; so it leaves tempfiles behind, which is a minor irritation. + (let* ((python-shell-setup-codes nil) + (python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (proc-name (python-shell-get-process-name nil)) + (shell-buffer + (python-tests-with-temp-buffer + "" (python-shell-make-comint + (python-shell-calculate-command) proc-name))) + (process (get-buffer-process shell-buffer))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + (should (process-live-p process)) + (with-current-buffer shell-buffer + (should (eq major-mode 'inferior-python-mode)) + (should (string= (buffer-name) (format "*%s*" proc-name))))) + (kill-buffer shell-buffer)))) + +(ert-deftest python-shell-make-comint-2 () + "Check comint creation for internal shell buffer." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((python-shell-setup-codes nil) + (python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (proc-name (python-shell-internal-get-process-name)) + (shell-buffer + (python-tests-with-temp-buffer + "" (python-shell-make-comint + (python-shell-calculate-command) proc-name nil t))) + (process (get-buffer-process shell-buffer))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + (should (process-live-p process)) + (with-current-buffer shell-buffer + (should (eq major-mode 'inferior-python-mode)) + (should (string= (buffer-name) (format " *%s*" proc-name))))) + (kill-buffer shell-buffer)))) + +(ert-deftest python-shell-make-comint-3 () + "Check comint creation with overridden python interpreter and args. +The command passed to `python-shell-make-comint' as argument must +locally override global values set in `python-shell-interpreter' +and `python-shell-interpreter-args' in the new shell buffer." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((python-shell-setup-codes nil) + (python-shell-interpreter "interpreter") + (python-shell-interpreter-args "--some-args") + (proc-name (python-shell-get-process-name nil)) + (interpreter-override + (concat (executable-find python-tests-shell-interpreter) " " "-i")) + (shell-buffer + (python-tests-with-temp-buffer + "" (python-shell-make-comint interpreter-override proc-name nil))) + (process (get-buffer-process shell-buffer))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + (should (process-live-p process)) + (with-current-buffer shell-buffer + (should (eq major-mode 'inferior-python-mode)) + (should (file-equal-p + python-shell-interpreter + (executable-find python-tests-shell-interpreter))) + (should (string= python-shell-interpreter-args "-i")))) + (kill-buffer shell-buffer)))) + +(ert-deftest python-shell-make-comint-4 () + "Check shell calculated prompts regexps are set." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (python-shell-setup-codes nil) + (python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (python-shell-interpreter-args "-i") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled t) + (python-shell-prompt-input-regexps '("extralargeinputprompt" "sml")) + (python-shell-prompt-output-regexps '("extralargeoutputprompt" "sml")) + (python-shell-prompt-regexp "in") + (python-shell-prompt-block-regexp "block") + (python-shell-prompt-pdb-regexp "pdf") + (python-shell-prompt-output-regexp "output") + (startup-code (concat "import sys\n" + "sys.ps1 = 'py> '\n" + "sys.ps2 = '..> '\n" + "sys.ps3 = 'out '\n")) + (startup-file (python-shell--save-temp-file startup-code)) + (proc-name (python-shell-get-process-name nil)) + (shell-buffer + (progn + (setenv "PYTHONSTARTUP" startup-file) + (python-tests-with-temp-buffer + "" (python-shell-make-comint + (python-shell-calculate-command) proc-name nil)))) + (process (get-buffer-process shell-buffer))) + (unwind-protect + (progn + (set-process-query-on-exit-flag process nil) + (should (process-live-p process)) + (with-current-buffer shell-buffer + (should (eq major-mode 'inferior-python-mode)) + (should (string= + python-shell--prompt-calculated-input-regexp + (concat "^\\(extralargeinputprompt\\|\\.\\.> \\|" + "block\\|py> \\|pdf\\|sml\\|in\\)"))) + (should (string= + python-shell--prompt-calculated-output-regexp + "^\\(extralargeoutputprompt\\|output\\|out \\|sml\\)")))) + (delete-file startup-file) + (kill-buffer shell-buffer)))) + +(ert-deftest python-shell-get-process-1 () + "Check dedicated shell process preference over global." + (skip-unless (executable-find python-tests-shell-interpreter)) + (python-tests-with-temp-file + "" + (let* ((python-shell-setup-codes nil) + (python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (global-proc-name (python-shell-get-process-name nil)) + (dedicated-proc-name (python-shell-get-process-name t)) + (global-shell-buffer + (python-shell-make-comint + (python-shell-calculate-command) global-proc-name)) + (dedicated-shell-buffer + (python-shell-make-comint + (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 + (progn + (set-process-query-on-exit-flag global-process nil) + (set-process-query-on-exit-flag dedicated-process nil) + ;; Prefer dedicated if global also exists. + (should (equal (python-shell-get-process) dedicated-process)) + (kill-buffer dedicated-shell-buffer) + ;; If there's only global, use it. + (should (equal (python-shell-get-process) global-process)) + (kill-buffer global-shell-buffer) + ;; No buffer available. + (should (not (python-shell-get-process)))) + (ignore-errors (kill-buffer global-shell-buffer)) + (ignore-errors (kill-buffer dedicated-shell-buffer)))))) + +(ert-deftest python-shell-internal-get-or-create-process-1 () + "Check internal shell process creation fallback." + (skip-unless (executable-find python-tests-shell-interpreter)) + (python-tests-with-temp-file + "" + (should (not (process-live-p (python-shell-internal-get-process-name)))) + (let* ((python-shell-interpreter + (executable-find python-tests-shell-interpreter)) + (internal-process-name (python-shell-internal-get-process-name)) + (internal-process (python-shell-internal-get-or-create-process)) + (internal-shell-buffer (process-buffer internal-process))) + (unwind-protect + (progn + (set-process-query-on-exit-flag internal-process nil) + (should (equal (process-name internal-process) + internal-process-name)) + (should (equal internal-process + (python-shell-internal-get-or-create-process))) + ;; Assert the internal process is not a user process + (should (not (python-shell-get-process))) + (kill-buffer internal-shell-buffer)) + (ignore-errors (kill-buffer internal-shell-buffer)))))) + +(ert-deftest python-shell-prompt-detect-1 () + "Check prompt autodetection." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let ((process-environment process-environment)) + ;; Ensure no startup file is enabled + (setenv "PYTHONSTARTUP" "") + (should python-shell-prompt-detect-enabled) + (should (equal (python-shell-prompt-detect) '(">>> " "... " ""))))) + +(ert-deftest python-shell-prompt-detect-2 () + "Check prompt autodetection with startup file. Bug#17370." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = 'py> '\n" + "sys.ps2 = '..> '\n" + "sys.ps3 = 'out '\n")) + (startup-file (python-shell--save-temp-file startup-code))) + (unwind-protect + (progn + ;; Ensure startup file is enabled + (setenv "PYTHONSTARTUP" startup-file) + (should python-shell-prompt-detect-enabled) + (should (equal (python-shell-prompt-detect) '("py> " "..> " "out ")))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-detect-3 () + "Check prompts are not autodetected when feature is disabled." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let ((process-environment process-environment) + (python-shell-prompt-detect-enabled nil)) + ;; Ensure no startup file is enabled + (should (not python-shell-prompt-detect-enabled)) + (should (not (python-shell-prompt-detect))))) + +(ert-deftest python-shell-prompt-detect-4 () + "Check warning is shown when detection fails." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + ;; Trigger failure by removing prompts in the startup file + (startup-code (concat "import sys\n" + "sys.ps1 = ''\n" + "sys.ps2 = ''\n" + "sys.ps3 = ''\n")) + (startup-file (python-shell--save-temp-file startup-code))) + (unwind-protect + (progn + (kill-buffer (get-buffer-create "*Warnings*")) + (should (not (get-buffer "*Warnings*"))) + (setenv "PYTHONSTARTUP" startup-file) + (should python-shell-prompt-detect-failure-warning) + (should python-shell-prompt-detect-enabled) + (should (not (python-shell-prompt-detect))) + (should (get-buffer "*Warnings*"))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-detect-5 () + "Check disabled warnings are not shown when detection fails." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = ''\n" + "sys.ps2 = ''\n" + "sys.ps3 = ''\n")) + (startup-file (python-shell--save-temp-file startup-code)) + (python-shell-prompt-detect-failure-warning nil)) + (unwind-protect + (progn + (kill-buffer (get-buffer-create "*Warnings*")) + (should (not (get-buffer "*Warnings*"))) + (setenv "PYTHONSTARTUP" startup-file) + (should (not python-shell-prompt-detect-failure-warning)) + (should python-shell-prompt-detect-enabled) + (should (not (python-shell-prompt-detect))) + (should (not (get-buffer "*Warnings*")))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-detect-6 () + "Warnings are not shown when detection is disabled." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = ''\n" + "sys.ps2 = ''\n" + "sys.ps3 = ''\n")) + (startup-file (python-shell--save-temp-file startup-code)) + (python-shell-prompt-detect-failure-warning t) + (python-shell-prompt-detect-enabled nil)) + (unwind-protect + (progn + (kill-buffer (get-buffer-create "*Warnings*")) + (should (not (get-buffer "*Warnings*"))) + (setenv "PYTHONSTARTUP" startup-file) + (should python-shell-prompt-detect-failure-warning) + (should (not python-shell-prompt-detect-enabled)) + (should (not (python-shell-prompt-detect))) + (should (not (get-buffer "*Warnings*")))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-prompt-validate-regexps-1 () + "Check `python-shell-prompt-input-regexps' are validated." + (let* ((python-shell-prompt-input-regexps '("\\(")) + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (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." + (let* ((python-shell-prompt-output-regexps '("\\(")) + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (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." + (let* ((python-shell-prompt-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (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." + (let* ((python-shell-prompt-block-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (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." + (let* ((python-shell-prompt-pdb-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (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." + (let* ((python-shell-prompt-output-regexp "\\(") + (error-data (should-error (python-shell-prompt-validate-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (format-message + "Invalid regexp \\( in `python-shell-prompt-output-regexp'"))))) + +(ert-deftest python-shell-prompt-validate-regexps-7 () + "Check default regexps are valid." + ;; should not signal error + (python-shell-prompt-validate-regexps)) + +(ert-deftest python-shell-prompt-set-calculated-regexps-1 () + "Check regexps are validated." + (let* ((python-shell-prompt-output-regexp '("\\(")) + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil) + (error-data (should-error (python-shell-prompt-set-calculated-regexps) + :type 'user-error))) + (should + (string= (cadr error-data) + (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." + (let* ((python-shell-prompt-input-regexps '("my" "prompt")) + (python-shell-prompt-output-regexps '("")) + (python-shell-prompt-regexp "") + (python-shell-prompt-block-regexp "") + (python-shell-prompt-pdb-regexp "") + (python-shell-prompt-output-regexp "") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(prompt\\|my\\|\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-3 () + "Check `python-shell-prompt-output-regexps' are set." + (let* ((python-shell-prompt-input-regexps '("")) + (python-shell-prompt-output-regexps '("my" "prompt")) + (python-shell-prompt-regexp "") + (python-shell-prompt-block-regexp "") + (python-shell-prompt-pdb-regexp "") + (python-shell-prompt-output-regexp "") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(prompt\\|my\\|\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-4 () + "Check user defined prompts are set." + (let* ((python-shell-prompt-input-regexps '("")) + (python-shell-prompt-output-regexps '("")) + (python-shell-prompt-regexp "prompt") + (python-shell-prompt-block-regexp "block") + (python-shell-prompt-pdb-regexp "pdb") + (python-shell-prompt-output-regexp "output") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(prompt\\|block\\|pdb\\|\\)")) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(output\\|\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-5 () + "Check order of regexps (larger first)." + (let* ((python-shell-prompt-input-regexps '("extralargeinputprompt" "sml")) + (python-shell-prompt-output-regexps '("extralargeoutputprompt" "sml")) + (python-shell-prompt-regexp "in") + (python-shell-prompt-block-regexp "block") + (python-shell-prompt-pdb-regexp "pdf") + (python-shell-prompt-output-regexp "output") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled nil)) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(extralargeinputprompt\\|block\\|pdf\\|sml\\|in\\)")) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(extralargeoutputprompt\\|output\\|sml\\)")))) + +(ert-deftest python-shell-prompt-set-calculated-regexps-6 () + "Check detected prompts are included `regexp-quote'd." + (skip-unless (executable-find python-tests-shell-interpreter)) + (let* ((python-shell-prompt-input-regexps '("")) + (python-shell-prompt-output-regexps '("")) + (python-shell-prompt-regexp "") + (python-shell-prompt-block-regexp "") + (python-shell-prompt-pdb-regexp "") + (python-shell-prompt-output-regexp "") + (python-shell--prompt-calculated-input-regexp nil) + (python-shell--prompt-calculated-output-regexp nil) + (python-shell-prompt-detect-enabled t) + (process-environment process-environment) + (startup-code (concat "import sys\n" + "sys.ps1 = 'p.> '\n" + "sys.ps2 = '..> '\n" + "sys.ps3 = 'o.t '\n")) + (startup-file (python-shell--save-temp-file startup-code))) + (unwind-protect + (progn + (setenv "PYTHONSTARTUP" startup-file) + (python-shell-prompt-set-calculated-regexps) + (should (string= python-shell--prompt-calculated-input-regexp + "^\\(\\.\\.> \\|p\\.> \\|\\)")) + (should (string= python-shell--prompt-calculated-output-regexp + "^\\(o\\.t \\|\\)"))) + (ignore-errors (delete-file startup-file))))) + +(ert-deftest python-shell-buffer-substring-1 () + "Selecting a substring of the whole buffer must match its contents." + (python-tests-with-temp-buffer + " +class Foo(models.Model): + pass + + +class Bar(models.Model): + pass +" + (should (string= (buffer-string) + (python-shell-buffer-substring (point-min) (point-max)))))) + +(ert-deftest python-shell-buffer-substring-2 () + "Main block should be removed if NOMAIN is non-nil." + (python-tests-with-temp-buffer + " +class Foo(models.Model): + pass + +class Bar(models.Model): + pass + +if __name__ == \"__main__\": + foo = Foo() + print (foo) +" + (should (string= (python-shell-buffer-substring (point-min) (point-max) t) + " +class Foo(models.Model): + pass + +class Bar(models.Model): + pass + + + + +")))) + +(ert-deftest python-shell-buffer-substring-3 () + "Main block should be removed if NOMAIN is non-nil." + (python-tests-with-temp-buffer + " +class Foo(models.Model): + pass + +if __name__ == \"__main__\": + foo = Foo() + print (foo) + +class Bar(models.Model): + pass +" + (should (string= (python-shell-buffer-substring (point-min) (point-max) t) + " +class Foo(models.Model): + pass + + + + + +class Bar(models.Model): + pass +")))) + +(ert-deftest python-shell-buffer-substring-4 () + "Coding cookie should be added for substrings." + (python-tests-with-temp-buffer + "# coding: latin-1 + +class Foo(models.Model): + pass + +if __name__ == \"__main__\": + foo = Foo() + print (foo) + +class Bar(models.Model): + pass +" + (should (string= (python-shell-buffer-substring + (python-tests-look-at "class Foo(models.Model):") + (progn (python-nav-forward-sexp) (point))) + "# -*- coding: latin-1 -*- + +class Foo(models.Model): + pass")))) + +(ert-deftest python-shell-buffer-substring-5 () + "The proper amount of blank lines is added for a substring." + (python-tests-with-temp-buffer + "# coding: latin-1 + +class Foo(models.Model): + pass + +if __name__ == \"__main__\": + foo = Foo() + print (foo) + +class Bar(models.Model): + pass +" + (should (string= (python-shell-buffer-substring + (python-tests-look-at "class Bar(models.Model):") + (progn (python-nav-forward-sexp) (point))) + "# -*- coding: latin-1 -*- + + + + + + + + +class Bar(models.Model): + pass")))) + +(ert-deftest python-shell-buffer-substring-6 () + "Handle substring with coding cookie in the second line." + (python-tests-with-temp-buffer + " +# coding: latin-1 + +class Foo(models.Model): + pass + +if __name__ == \"__main__\": + foo = Foo() + print (foo) + +class Bar(models.Model): + pass +" + (should (string= (python-shell-buffer-substring + (python-tests-look-at "# coding: latin-1") + (python-tests-look-at "if __name__ == \"__main__\":")) + "# -*- coding: latin-1 -*- + + +class Foo(models.Model): + pass + +")))) + +(ert-deftest python-shell-buffer-substring-7 () + "Ensure first coding cookie gets precedence." + (python-tests-with-temp-buffer + "# coding: utf-8 +# coding: latin-1 + +class Foo(models.Model): + pass + +if __name__ == \"__main__\": + foo = Foo() + print (foo) + +class Bar(models.Model): + pass +" + (should (string= (python-shell-buffer-substring + (python-tests-look-at "# coding: latin-1") + (python-tests-look-at "if __name__ == \"__main__\":")) + "# -*- coding: utf-8 -*- + + +class Foo(models.Model): + pass + +")))) + +(ert-deftest python-shell-buffer-substring-8 () + "Ensure first coding cookie gets precedence when sending whole buffer." + (python-tests-with-temp-buffer + "# coding: utf-8 +# coding: latin-1 + +class Foo(models.Model): + pass +" + (should (string= (python-shell-buffer-substring (point-min) (point-max)) + "# coding: utf-8 + + +class Foo(models.Model): + pass +")))) + +(ert-deftest python-shell-buffer-substring-9 () + "Check substring starting from `point-min'." + (python-tests-with-temp-buffer + "# coding: utf-8 + +class Foo(models.Model): + pass + +class Bar(models.Model): + pass +" + (should (string= (python-shell-buffer-substring + (point-min) + (python-tests-look-at "class Bar(models.Model):")) + "# coding: utf-8 + +class Foo(models.Model): + pass + +")))) + +(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') +")))) + + + +;;; 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)))) + + + + +;;; PDB Track integration + + +;;; Symbol completion + + +;;; Fill paragraph + + +;;; Skeletons + + +;;; FFAP + + +;;; Code check + + +;;; 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")))) + + +;;; Imenu + +(ert-deftest python-imenu-create-index-1 () + (python-tests-with-temp-buffer + " +class Foo(models.Model): + pass + + +class Bar(models.Model): + pass + + +def decorator(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decorator('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wrap(f): + print ('wrap') + def wrapped_f(*args): + print ('wrapped_f') + print ('Decorator arguments:', arg1, arg2, arg3) + f(*args) + print ('called f(*args)') + return wrapped_f + return wrap + + +class Baz(object): + + def a(self): + pass + + def b(self): + pass + + class Frob(object): + + def c(self): + pass +" + (goto-char (point-max)) + (should (equal + (list + (cons "Foo (class)" (copy-marker 2)) + (cons "Bar (class)" (copy-marker 38)) + (list + "decorator (def)" + (cons "*function definition*" (copy-marker 74)) + (list + "wrap (def)" + (cons "*function definition*" (copy-marker 254)) + (cons "wrapped_f (def)" (copy-marker 294)))) + (list + "Baz (class)" + (cons "*class definition*" (copy-marker 519)) + (cons "a (def)" (copy-marker 539)) + (cons "b (def)" (copy-marker 570)) + (list + "Frob (class)" + (cons "*class definition*" (copy-marker 601)) + (cons "c (def)" (copy-marker 626))))) + (python-imenu-create-index))))) + +(ert-deftest python-imenu-create-index-2 () + (python-tests-with-temp-buffer + " +class Foo(object): + def foo(self): + def foo1(): + pass + + def foobar(self): + pass +" + (goto-char (point-max)) + (should (equal + (list + (list + "Foo (class)" + (cons "*class definition*" (copy-marker 2)) + (list + "foo (def)" + (cons "*function definition*" (copy-marker 21)) + (cons "foo1 (def)" (copy-marker 40))) + (cons "foobar (def)" (copy-marker 78)))) + (python-imenu-create-index))))) + +(ert-deftest python-imenu-create-index-3 () + (python-tests-with-temp-buffer + " +class Foo(object): + def foo(self): + def foo1(): + pass + def foo2(): + pass +" + (goto-char (point-max)) + (should (equal + (list + (list + "Foo (class)" + (cons "*class definition*" (copy-marker 2)) + (list + "foo (def)" + (cons "*function definition*" (copy-marker 21)) + (cons "foo1 (def)" (copy-marker 40)) + (cons "foo2 (def)" (copy-marker 77))))) + (python-imenu-create-index))))) + +(ert-deftest python-imenu-create-index-4 () + (python-tests-with-temp-buffer + " +class Foo(object): + class Bar(object): + def __init__(self): + pass + + def __str__(self): + pass + + def __init__(self): + pass +" + (goto-char (point-max)) + (should (equal + (list + (list + "Foo (class)" + (cons "*class definition*" (copy-marker 2)) + (list + "Bar (class)" + (cons "*class definition*" (copy-marker 21)) + (cons "__init__ (def)" (copy-marker 44)) + (cons "__str__ (def)" (copy-marker 90))) + (cons "__init__ (def)" (copy-marker 135)))) + (python-imenu-create-index))))) + +(ert-deftest python-imenu-create-flat-index-1 () + (python-tests-with-temp-buffer + " +class Foo(models.Model): + pass + + +class Bar(models.Model): + pass + + +def decorator(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decorator('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wrap(f): + print ('wrap') + def wrapped_f(*args): + print ('wrapped_f') + print ('Decorator arguments:', arg1, arg2, arg3) + f(*args) + print ('called f(*args)') + return wrapped_f + return wrap + + +class Baz(object): + + def a(self): + pass + + def b(self): + pass + + class Frob(object): + + def c(self): + pass +" + (goto-char (point-max)) + (should (equal + (list (cons "Foo" (copy-marker 2)) + (cons "Bar" (copy-marker 38)) + (cons "decorator" (copy-marker 74)) + (cons "decorator.wrap" (copy-marker 254)) + (cons "decorator.wrap.wrapped_f" (copy-marker 294)) + (cons "Baz" (copy-marker 519)) + (cons "Baz.a" (copy-marker 539)) + (cons "Baz.b" (copy-marker 570)) + (cons "Baz.Frob" (copy-marker 601)) + (cons "Baz.Frob.c" (copy-marker 626))) + (python-imenu-create-flat-index))))) + +(ert-deftest python-imenu-create-flat-index-2 () + (python-tests-with-temp-buffer + " +class Foo(object): + class Bar(object): + def __init__(self): + pass + + def __str__(self): + pass + + def __init__(self): + pass +" + (goto-char (point-max)) + (should (equal + (list + (cons "Foo" (copy-marker 2)) + (cons "Foo.Bar" (copy-marker 21)) + (cons "Foo.Bar.__init__" (copy-marker 44)) + (cons "Foo.Bar.__str__" (copy-marker 90)) + (cons "Foo.__init__" (copy-marker 135))) + (python-imenu-create-flat-index))))) + + +;;; Misc helpers + +(ert-deftest python-info-current-defun-1 () + (python-tests-with-temp-buffer + " +def foo(a, b): +" + (forward-line 1) + (should (string= "foo" (python-info-current-defun))) + (should (string= "def foo" (python-info-current-defun t))) + (forward-line 1) + (should (not (python-info-current-defun))) + (indent-for-tab-command) + (should (string= "foo" (python-info-current-defun))) + (should (string= "def foo" (python-info-current-defun t))))) + +(ert-deftest python-info-current-defun-2 () + (python-tests-with-temp-buffer + " +class C(object): + + def m(self): + if True: + return [i for i in range(3)] + else: + return [] + + def b(): + do_b() + + def a(): + do_a() + + def c(self): + do_c() +" + (forward-line 1) + (should (string= "C" (python-info-current-defun))) + (should (string= "class C" (python-info-current-defun t))) + (python-tests-look-at "return [i for ") + (should (string= "C.m" (python-info-current-defun))) + (should (string= "def C.m" (python-info-current-defun t))) + (python-tests-look-at "def b():") + (should (string= "C.m.b" (python-info-current-defun))) + (should (string= "def C.m.b" (python-info-current-defun t))) + (forward-line 2) + (indent-for-tab-command) + (python-indent-dedent-line-backspace 1) + (should (string= "C.m" (python-info-current-defun))) + (should (string= "def C.m" (python-info-current-defun t))) + (python-tests-look-at "def c(self):") + (forward-line -1) + (indent-for-tab-command) + (should (string= "C.m.a" (python-info-current-defun))) + (should (string= "def C.m.a" (python-info-current-defun t))) + (python-indent-dedent-line-backspace 1) + (should (string= "C.m" (python-info-current-defun))) + (should (string= "def C.m" (python-info-current-defun t))) + (python-indent-dedent-line-backspace 1) + (should (string= "C" (python-info-current-defun))) + (should (string= "class C" (python-info-current-defun t))) + (python-tests-look-at "def c(self):") + (should (string= "C.c" (python-info-current-defun))) + (should (string= "def C.c" (python-info-current-defun t))) + (python-tests-look-at "do_c()") + (should (string= "C.c" (python-info-current-defun))) + (should (string= "def C.c" (python-info-current-defun t))))) + +(ert-deftest python-info-current-defun-3 () + (python-tests-with-temp-buffer + " +def decoratorFunctionWithArguments(arg1, arg2, arg3): + '''print decorated function call data to stdout. + + Usage: + + @decoratorFunctionWithArguments('arg1', 'arg2') + def func(a, b, c=True): + pass + ''' + + def wwrap(f): + print 'Inside wwrap()' + def wrapped_f(*args): + print 'Inside wrapped_f()' + print 'Decorator arguments:', arg1, arg2, arg3 + f(*args) + print 'After f(*args)' + return wrapped_f + return wwrap +" + (python-tests-look-at "def wwrap(f):") + (forward-line -1) + (should (not (python-info-current-defun))) + (indent-for-tab-command 1) + (should (string= (python-info-current-defun) + "decoratorFunctionWithArguments")) + (should (string= (python-info-current-defun t) + "def decoratorFunctionWithArguments")) + (python-tests-look-at "def wrapped_f(*args):") + (should (string= (python-info-current-defun) + "decoratorFunctionWithArguments.wwrap.wrapped_f")) + (should (string= (python-info-current-defun t) + "def decoratorFunctionWithArguments.wwrap.wrapped_f")) + (python-tests-look-at "return wrapped_f") + (should (string= (python-info-current-defun) + "decoratorFunctionWithArguments.wwrap")) + (should (string= (python-info-current-defun t) + "def decoratorFunctionWithArguments.wwrap")) + (end-of-line 1) + (python-tests-look-at "return wwrap") + (should (string= (python-info-current-defun) + "decoratorFunctionWithArguments")) + (should (string= (python-info-current-defun t) + "def decoratorFunctionWithArguments")))) + +(ert-deftest python-info-current-symbol-1 () + (python-tests-with-temp-buffer + " +class C(object): + + def m(self): + self.c() + + def c(self): + print ('a') +" + (python-tests-look-at "self.c()") + (should (string= "self.c" (python-info-current-symbol))) + (should (string= "C.c" (python-info-current-symbol t))))) + +(ert-deftest python-info-current-symbol-2 () + (python-tests-with-temp-buffer + " +class C(object): + + class M(object): + + def a(self): + self.c() + + def c(self): + pass +" + (python-tests-look-at "self.c()") + (should (string= "self.c" (python-info-current-symbol))) + (should (string= "C.M.c" (python-info-current-symbol t))))) + +(ert-deftest python-info-current-symbol-3 () + "Keywords should not be considered symbols." + :expected-result :failed + (python-tests-with-temp-buffer + " +class C(object): + pass +" + ;; FIXME: keywords are not symbols. + (python-tests-look-at "class C") + (should (not (python-info-current-symbol))) + (should (not (python-info-current-symbol t))) + (python-tests-look-at "C(object)") + (should (string= "C" (python-info-current-symbol))) + (should (string= "class C" (python-info-current-symbol t))))) + +(ert-deftest python-info-statement-starts-block-p-1 () + (python-tests-with-temp-buffer + " +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (python-tests-look-at "def long_function_name") + (should (python-info-statement-starts-block-p)) + (python-tests-look-at "print (var_one)") + (python-util-forward-comment -1) + (should (python-info-statement-starts-block-p)))) + +(ert-deftest python-info-statement-starts-block-p-2 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError('sorry, you lose') +" + (python-tests-look-at "if width == 0 and") + (should (python-info-statement-starts-block-p)) + (python-tests-look-at "raise ValueError(") + (python-util-forward-comment -1) + (should (python-info-statement-starts-block-p)))) + +(ert-deftest python-info-statement-ends-block-p-1 () + (python-tests-with-temp-buffer + " +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (python-tests-look-at "print (var_one)") + (should (python-info-statement-ends-block-p)))) + +(ert-deftest python-info-statement-ends-block-p-2 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "raise ValueError(") + (should (python-info-statement-ends-block-p)))) + +(ert-deftest python-info-beginning-of-statement-p-1 () + (python-tests-with-temp-buffer + " +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (python-tests-look-at "def long_function_name") + (should (python-info-beginning-of-statement-p)) + (forward-char 10) + (should (not (python-info-beginning-of-statement-p))) + (python-tests-look-at "print (var_one)") + (should (python-info-beginning-of-statement-p)) + (goto-char (line-beginning-position)) + (should (not (python-info-beginning-of-statement-p))))) + +(ert-deftest python-info-beginning-of-statement-p-2 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "if width == 0 and") + (should (python-info-beginning-of-statement-p)) + (forward-char 10) + (should (not (python-info-beginning-of-statement-p))) + (python-tests-look-at "raise ValueError(") + (should (python-info-beginning-of-statement-p)) + (goto-char (line-beginning-position)) + (should (not (python-info-beginning-of-statement-p))))) + +(ert-deftest python-info-end-of-statement-p-1 () + (python-tests-with-temp-buffer + " +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (python-tests-look-at "def long_function_name") + (should (not (python-info-end-of-statement-p))) + (end-of-line) + (should (not (python-info-end-of-statement-p))) + (python-tests-look-at "print (var_one)") + (python-util-forward-comment -1) + (should (python-info-end-of-statement-p)) + (python-tests-look-at "print (var_one)") + (should (not (python-info-end-of-statement-p))) + (end-of-line) + (should (python-info-end-of-statement-p)))) + +(ert-deftest python-info-end-of-statement-p-2 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "if width == 0 and") + (should (not (python-info-end-of-statement-p))) + (end-of-line) + (should (not (python-info-end-of-statement-p))) + (python-tests-look-at "raise ValueError(") + (python-util-forward-comment -1) + (should (python-info-end-of-statement-p)) + (python-tests-look-at "raise ValueError(") + (should (not (python-info-end-of-statement-p))) + (end-of-line) + (should (not (python-info-end-of-statement-p))) + (goto-char (point-max)) + (python-util-forward-comment -1) + (should (python-info-end-of-statement-p)))) + +(ert-deftest python-info-beginning-of-block-p-1 () + (python-tests-with-temp-buffer + " +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (python-tests-look-at "def long_function_name") + (should (python-info-beginning-of-block-p)) + (python-tests-look-at "var_one, var_two, var_three,") + (should (not (python-info-beginning-of-block-p))) + (python-tests-look-at "print (var_one)") + (should (not (python-info-beginning-of-block-p))))) + +(ert-deftest python-info-beginning-of-block-p-2 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "if width == 0 and") + (should (python-info-beginning-of-block-p)) + (python-tests-look-at "color == 'red' and emphasis") + (should (not (python-info-beginning-of-block-p))) + (python-tests-look-at "raise ValueError(") + (should (not (python-info-beginning-of-block-p))))) + +(ert-deftest python-info-end-of-block-p-1 () + (python-tests-with-temp-buffer + " +def long_function_name( + var_one, var_two, var_three, + var_four): + print (var_one) +" + (python-tests-look-at "def long_function_name") + (should (not (python-info-end-of-block-p))) + (python-tests-look-at "var_one, var_two, var_three,") + (should (not (python-info-end-of-block-p))) + (python-tests-look-at "var_four):") + (end-of-line) + (should (not (python-info-end-of-block-p))) + (python-tests-look-at "print (var_one)") + (should (not (python-info-end-of-block-p))) + (end-of-line 1) + (should (python-info-end-of-block-p)))) + +(ert-deftest python-info-end-of-block-p-2 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "if width == 0 and") + (should (not (python-info-end-of-block-p))) + (python-tests-look-at "color == 'red' and emphasis == 'strong' or") + (should (not (python-info-end-of-block-p))) + (python-tests-look-at "highlight > 100:") + (end-of-line) + (should (not (python-info-end-of-block-p))) + (python-tests-look-at "raise ValueError(") + (should (not (python-info-end-of-block-p))) + (end-of-line 1) + (should (not (python-info-end-of-block-p))) + (goto-char (point-max)) + (python-util-forward-comment -1) + (should (python-info-end-of-block-p)))) + +(ert-deftest python-info-dedenter-opening-block-position-1 () + (python-tests-with-temp-buffer + " +if request.user.is_authenticated(): + try: + profile = request.user.get_profile() + except Profile.DoesNotExist: + profile = Profile.objects.create(user=request.user) + else: + if profile.stats: + profile.recalculate_stats() + else: + profile.clear_stats() + finally: + profile.views += 1 + profile.save() +" + (python-tests-look-at "try:") + (should (not (python-info-dedenter-opening-block-position))) + (python-tests-look-at "except Profile.DoesNotExist:") + (should (= (python-tests-look-at "try:" -1 t) + (python-info-dedenter-opening-block-position))) + (python-tests-look-at "else:") + (should (= (python-tests-look-at "except Profile.DoesNotExist:" -1 t) + (python-info-dedenter-opening-block-position))) + (python-tests-look-at "if profile.stats:") + (should (not (python-info-dedenter-opening-block-position))) + (python-tests-look-at "else:") + (should (= (python-tests-look-at "if profile.stats:" -1 t) + (python-info-dedenter-opening-block-position))) + (python-tests-look-at "finally:") + (should (= (python-tests-look-at "else:" -2 t) + (python-info-dedenter-opening-block-position))))) + +(ert-deftest python-info-dedenter-opening-block-position-2 () + (python-tests-with-temp-buffer + " +if request.user.is_authenticated(): + profile = Profile.objects.get_or_create(user=request.user) + if profile.stats: + profile.recalculate_stats() + +data = { + 'else': 'do it' +} + 'else' +" + (python-tests-look-at "'else': 'do it'") + (should (not (python-info-dedenter-opening-block-position))) + (python-tests-look-at "'else'") + (should (not (python-info-dedenter-opening-block-position))))) + +(ert-deftest python-info-dedenter-opening-block-position-3 () + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + else + finally: + data.free() +" + (python-tests-look-at "try:") + (should (not (python-info-dedenter-opening-block-position))) + + (python-tests-look-at "except IOError:") + (should (= (python-tests-look-at "try:" -1 t) + (python-info-dedenter-opening-block-position))) + + (python-tests-look-at "except Exception:") + (should (= (python-tests-look-at "except IOError:" -1 t) + (python-info-dedenter-opening-block-position))) + + (python-tests-look-at "if hide_details:") + (should (not (python-info-dedenter-opening-block-position))) + + ;; check indentation modifies the detected opening block + (python-tests-look-at "else") + (should (= (python-tests-look-at "if hide_details:" -1 t) + (python-info-dedenter-opening-block-position))) + + (indent-line-to 8) + (should (= (python-tests-look-at "if hide_details:" -1 t) + (python-info-dedenter-opening-block-position))) + + (indent-line-to 4) + (should (= (python-tests-look-at "except Exception:" -1 t) + (python-info-dedenter-opening-block-position))) + + (indent-line-to 0) + (should (= (python-tests-look-at "if save:" -1 t) + (python-info-dedenter-opening-block-position))))) + +(ert-deftest python-info-dedenter-opening-block-positions-1 () + (python-tests-with-temp-buffer + " +if save: + try: + write_to_disk(data) + except IOError: + msg = 'Error saving to disk' + message(msg) + logger.exception(msg) + except Exception: + if hide_details: + logger.exception('Unhandled exception') + else + finally: + data.free() +" + (python-tests-look-at "try:") + (should (not (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "except IOError:") + (should + (equal (list + (python-tests-look-at "try:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "except Exception:") + (should + (equal (list + (python-tests-look-at "except IOError:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "if hide_details:") + (should (not (python-info-dedenter-opening-block-positions))) + + ;; check indentation does not modify the detected opening blocks + (python-tests-look-at "else") + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (indent-line-to 8) + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (indent-line-to 4) + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (indent-line-to 0) + (should + (equal (list + (python-tests-look-at "if hide_details:" -1 t) + (python-tests-look-at "except Exception:" -1 t) + (python-tests-look-at "if save:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-2 () + "Test detection of opening blocks for elif." + (python-tests-with-temp-buffer + " +if var: + if var2: + something() + elif var3: + something_else() + elif +" + (python-tests-look-at "elif var3:") + (should + (equal (list + (python-tests-look-at "if var2:" -1 t) + (python-tests-look-at "if var:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "elif\n") + (should + (equal (list + (python-tests-look-at "elif var3:" -1 t) + (python-tests-look-at "if var:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-3 () + "Test detection of opening blocks for else." + (python-tests-with-temp-buffer + " +try: + something() +except: + if var: + if var2: + something() + elif var3: + something_else() + else + +if var4: + while var5: + var4.pop() + else + + for value in var6: + if value > 0: + print value + else +" + (python-tests-look-at "else\n") + (should + (equal (list + (python-tests-look-at "elif var3:" -1 t) + (python-tests-look-at "if var:" -1 t) + (python-tests-look-at "except:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "else\n") + (should + (equal (list + (python-tests-look-at "while var5:" -1 t) + (python-tests-look-at "if var4:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "else\n") + (should + (equal (list + (python-tests-look-at "if value > 0:" -1 t) + (python-tests-look-at "for value in var6:" -1 t) + (python-tests-look-at "if var4:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-4 () + "Test detection of opening blocks for except." + (python-tests-with-temp-buffer + " +try: + something() +except ValueError: + something_else() + except +" + (python-tests-look-at "except ValueError:") + (should + (equal (list (python-tests-look-at "try:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "except\n") + (should + (equal (list (python-tests-look-at "except ValueError:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-positions-5 () + "Test detection of opening blocks for finally." + (python-tests-with-temp-buffer + " +try: + something() + finally + +try: + something_else() +except: + logger.exception('something went wrong') + finally + +try: + something_else_else() +except Exception: + logger.exception('something else went wrong') +else: + print ('all good') + finally +" + (python-tests-look-at "finally\n") + (should + (equal (list (python-tests-look-at "try:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "finally\n") + (should + (equal (list (python-tests-look-at "except:" -1 t)) + (python-info-dedenter-opening-block-positions))) + + (python-tests-look-at "finally\n") + (should + (equal (list (python-tests-look-at "else:" -1 t)) + (python-info-dedenter-opening-block-positions))))) + +(ert-deftest python-info-dedenter-opening-block-message-1 () + "Test dedenters inside strings are ignored." + (python-tests-with-temp-buffer + "''' +try: + something() +except: + logger.exception('something went wrong') +''' +" + (python-tests-look-at "except\n") + (should (not (python-info-dedenter-opening-block-message))))) + +(ert-deftest python-info-dedenter-opening-block-message-2 () + "Test except keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +" + (python-tests-look-at "except:") + (should (string= + "Closes try:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes try:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + +(ert-deftest python-info-dedenter-opening-block-message-3 () + "Test else keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +" + (python-tests-look-at "else:") + (should (string= + "Closes except:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes except:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + +(ert-deftest python-info-dedenter-opening-block-message-4 () + "Test finally keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +finally: + clean() +" + (python-tests-look-at "finally:") + (should (string= + "Closes else:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes else:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + +(ert-deftest python-info-dedenter-opening-block-message-5 () + "Test elif keyword." + (python-tests-with-temp-buffer + " +if a: + something() +elif b: +" + (python-tests-look-at "elif b:") + (should (string= + "Closes if a:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))) + (end-of-line) + (should (string= + "Closes if a:" + (substring-no-properties + (python-info-dedenter-opening-block-message)))))) + + +(ert-deftest python-info-dedenter-statement-p-1 () + "Test dedenters inside strings are ignored." + (python-tests-with-temp-buffer + "''' +try: + something() +except: + logger.exception('something went wrong') +''' +" + (python-tests-look-at "except\n") + (should (not (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-2 () + "Test except keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +" + (python-tests-look-at "except:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-3 () + "Test else keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +" + (python-tests-look-at "else:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-4 () + "Test finally keyword." + (python-tests-with-temp-buffer + " +try: + something() +except: + logger.exception('something went wrong') +else: + logger.debug('all good') +finally: + clean() +" + (python-tests-look-at "finally:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-dedenter-statement-p-5 () + "Test elif keyword." + (python-tests-with-temp-buffer + " +if a: + something() +elif b: +" + (python-tests-look-at "elif b:") + (should (= (point) (python-info-dedenter-statement-p))) + (end-of-line) + (should (= (save-excursion + (back-to-indentation) + (point)) + (python-info-dedenter-statement-p))))) + +(ert-deftest python-info-line-ends-backslash-p-1 () + (python-tests-with-temp-buffer + " +objects = Thing.objects.all() \\\\ + .filter( + type='toy', + status='bought' + ) \\\\ + .aggregate( + Sum('amount') + ) \\\\ + .values_list() +" + (should (python-info-line-ends-backslash-p 2)) ; .filter(... + (should (python-info-line-ends-backslash-p 3)) + (should (python-info-line-ends-backslash-p 4)) + (should (python-info-line-ends-backslash-p 5)) + (should (python-info-line-ends-backslash-p 6)) ; ) \... + (should (python-info-line-ends-backslash-p 7)) + (should (python-info-line-ends-backslash-p 8)) + (should (python-info-line-ends-backslash-p 9)) + (should (not (python-info-line-ends-backslash-p 10))))) ; .values_list()... + +(ert-deftest python-info-beginning-of-backslash-1 () + (python-tests-with-temp-buffer + " +objects = Thing.objects.all() \\\\ + .filter( + type='toy', + status='bought' + ) \\\\ + .aggregate( + Sum('amount') + ) \\\\ + .values_list() +" + (let ((first 2) + (second (python-tests-look-at ".filter(")) + (third (python-tests-look-at ".aggregate("))) + (should (= first (python-info-beginning-of-backslash 2))) + (should (= second (python-info-beginning-of-backslash 3))) + (should (= second (python-info-beginning-of-backslash 4))) + (should (= second (python-info-beginning-of-backslash 5))) + (should (= second (python-info-beginning-of-backslash 6))) + (should (= third (python-info-beginning-of-backslash 7))) + (should (= third (python-info-beginning-of-backslash 8))) + (should (= third (python-info-beginning-of-backslash 9))) + (should (not (python-info-beginning-of-backslash 10)))))) + +(ert-deftest python-info-continuation-line-p-1 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "if width == 0 and height == 0 and") + (should (not (python-info-continuation-line-p))) + (python-tests-look-at "color == 'red' and emphasis == 'strong' or") + (should (python-info-continuation-line-p)) + (python-tests-look-at "highlight > 100:") + (should (python-info-continuation-line-p)) + (python-tests-look-at "raise ValueError(") + (should (not (python-info-continuation-line-p))) + (python-tests-look-at "'sorry, you lose'") + (should (python-info-continuation-line-p)) + (forward-line 1) + (should (python-info-continuation-line-p)) + (python-tests-look-at ")") + (should (python-info-continuation-line-p)) + (forward-line 1) + (should (not (python-info-continuation-line-p))))) + +(ert-deftest python-info-block-continuation-line-p-1 () + (python-tests-with-temp-buffer + " +if width == 0 and height == 0 and \\\\ + color == 'red' and emphasis == 'strong' or \\\\ + highlight > 100: + raise ValueError( +'sorry, you lose' + +) +" + (python-tests-look-at "if width == 0 and") + (should (not (python-info-block-continuation-line-p))) + (python-tests-look-at "color == 'red' and emphasis == 'strong' or") + (should (= (python-info-block-continuation-line-p) + (python-tests-look-at "if width == 0 and" -1 t))) + (python-tests-look-at "highlight > 100:") + (should (not (python-info-block-continuation-line-p))))) + +(ert-deftest python-info-block-continuation-line-p-2 () + (python-tests-with-temp-buffer + " +def foo(a, + b, + c): + pass +" + (python-tests-look-at "def foo(a,") + (should (not (python-info-block-continuation-line-p))) + (python-tests-look-at "b,") + (should (= (python-info-block-continuation-line-p) + (python-tests-look-at "def foo(a," -1 t))) + (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 + " +data = foo(), bar() \\\\ + baz(), 4 \\\\ + 5, 6 +" + (python-tests-look-at "data = foo(), bar()") + (should (not (python-info-assignment-continuation-line-p))) + (python-tests-look-at "baz(), 4") + (should (= (python-info-assignment-continuation-line-p) + (python-tests-look-at "foo()," -1 t))) + (python-tests-look-at "5, 6") + (should (not (python-info-assignment-continuation-line-p))))) + +(ert-deftest python-info-assignment-continuation-line-p-2 () + (python-tests-with-temp-buffer + " +data = (foo(), bar() + baz(), 4 + 5, 6) +" + (python-tests-look-at "data = (foo(), bar()") + (should (not (python-info-assignment-continuation-line-p))) + (python-tests-look-at "baz(), 4") + (should (= (python-info-assignment-continuation-line-p) + (python-tests-look-at "(foo()," -1 t))) + (python-tests-look-at "5, 6)") + (should (not (python-info-assignment-continuation-line-p))))) + +(ert-deftest python-info-looking-at-beginning-of-defun-1 () + (python-tests-with-temp-buffer + " +def decorat0r(deff): + '''decorates stuff. + + @decorat0r + def foo(arg): + ... + ''' + def wrap(): + deff() + return wwrap +" + (python-tests-look-at "def decorat0r(deff):") + (should (python-info-looking-at-beginning-of-defun)) + (python-tests-look-at "def foo(arg):") + (should (not (python-info-looking-at-beginning-of-defun))) + (python-tests-look-at "def wrap():") + (should (python-info-looking-at-beginning-of-defun)) + (python-tests-look-at "deff()") + (should (not (python-info-looking-at-beginning-of-defun))))) + +(ert-deftest python-info-current-line-comment-p-1 () + (python-tests-with-temp-buffer + " +# this is a comment +foo = True # another comment +'#this is a string' +if foo: + # more comments + print ('bar') # print bar +" + (python-tests-look-at "# this is a comment") + (should (python-info-current-line-comment-p)) + (python-tests-look-at "foo = True # another comment") + (should (not (python-info-current-line-comment-p))) + (python-tests-look-at "'#this is a string'") + (should (not (python-info-current-line-comment-p))) + (python-tests-look-at "# more comments") + (should (python-info-current-line-comment-p)) + (python-tests-look-at "print ('bar') # print bar") + (should (not (python-info-current-line-comment-p))))) + +(ert-deftest python-info-current-line-empty-p () + (python-tests-with-temp-buffer + " +# this is a comment + +foo = True # another comment +" + (should (python-info-current-line-empty-p)) + (python-tests-look-at "# this is a comment") + (should (not (python-info-current-line-empty-p))) + (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 + "# coding=latin-1 + +foo = True # another comment +" + (should (eq (python-info-encoding-from-cookie) 'latin-1)))) + +(ert-deftest python-info-encoding-from-cookie-2 () + "Should detect it on second line." + (python-tests-with-temp-buffer + " +# coding=latin-1 + +foo = True # another comment +" + (should (eq (python-info-encoding-from-cookie) 'latin-1)))) + +(ert-deftest python-info-encoding-from-cookie-3 () + "Should not be detected on third line (and following ones)." + (python-tests-with-temp-buffer + " + +# coding=latin-1 +foo = True # another comment +" + (should (not (python-info-encoding-from-cookie))))) + +(ert-deftest python-info-encoding-from-cookie-4 () + "Should detect Emacs style." + (python-tests-with-temp-buffer + "# -*- coding: latin-1 -*- + +foo = True # another comment" + (should (eq (python-info-encoding-from-cookie) 'latin-1)))) + +(ert-deftest python-info-encoding-from-cookie-5 () + "Should detect Vim style." + (python-tests-with-temp-buffer + "# vim: set fileencoding=latin-1 : + +foo = True # another comment" + (should (eq (python-info-encoding-from-cookie) 'latin-1)))) + +(ert-deftest python-info-encoding-from-cookie-6 () + "First cookie wins." + (python-tests-with-temp-buffer + "# -*- coding: iso-8859-1 -*- +# vim: set fileencoding=latin-1 : + +foo = True # another comment" + (should (eq (python-info-encoding-from-cookie) 'iso-8859-1)))) + +(ert-deftest python-info-encoding-from-cookie-7 () + "First cookie wins." + (python-tests-with-temp-buffer + "# vim: set fileencoding=latin-1 : +# -*- coding: iso-8859-1 -*- + +foo = True # another comment" + (should (eq (python-info-encoding-from-cookie) 'latin-1)))) + +(ert-deftest python-info-encoding-1 () + "Should return the detected encoding from cookie." + (python-tests-with-temp-buffer + "# vim: set fileencoding=latin-1 : + +foo = True # another comment" + (should (eq (python-info-encoding) 'latin-1)))) + +(ert-deftest python-info-encoding-2 () + "Should default to utf-8." + (python-tests-with-temp-buffer + "# No encoding for you + +foo = True # another comment" + (should (eq (python-info-encoding) 'utf-8)))) + + +;;; Utility functions + +(ert-deftest python-util-goto-line-1 () + (python-tests-with-temp-buffer + (concat + "# a comment +# another comment +def foo(a, b, c): + pass" (make-string 20 ?\n)) + (python-util-goto-line 10) + (should (= (line-number-at-pos) 10)) + (python-util-goto-line 20) + (should (= (line-number-at-pos) 20)))) + +(ert-deftest python-util-clone-local-variables-1 () + (let ((buffer (generate-new-buffer + "python-util-clone-local-variables-1")) + (varcons + '((python-fill-docstring-style . django) + (python-shell-interpreter . "python") + (python-shell-interpreter-args . "manage.py shell") + (python-shell-prompt-regexp . "In \\[[0-9]+\\]: ") + (python-shell-prompt-output-regexp . "Out\\[[0-9]+\\]: ") + (python-shell-extra-pythonpaths "/home/user/pylib/") + (python-shell-completion-setup-code + . "from IPython.core.completerlib import module_completion") + (python-shell-completion-string-code + . "';'.join(get_ipython().Completer.all_completions('''%s'''))\n") + (python-shell-virtualenv-root + . "/home/user/.virtualenvs/project")))) + (with-current-buffer buffer + (kill-all-local-variables) + (dolist (ccons varcons) + (set (make-local-variable (car ccons)) (cdr ccons)))) + (python-tests-with-temp-buffer + "" + (python-util-clone-local-variables buffer) + (dolist (ccons varcons) + (should + (equal (symbol-value (car ccons)) (cdr ccons))))) + (kill-buffer buffer))) + +(ert-deftest python-util-strip-string-1 () + (should (string= (python-util-strip-string "\t\r\n str") "str")) + (should (string= (python-util-strip-string "str \n\r") "str")) + (should (string= (python-util-strip-string "\t\r\n str \n\r ") "str")) + (should + (string= (python-util-strip-string "\n str \nin \tg \n\r") "str \nin \tg")) + (should (string= (python-util-strip-string "\n \t \n\r ") "")) + (should (string= (python-util-strip-string "") ""))) + +(ert-deftest python-util-forward-comment-1 () + (python-tests-with-temp-buffer + (concat + "# a comment +# another comment + # bad indented comment +# more comments" (make-string 9999 ?\n)) + (python-util-forward-comment 1) + (should (= (point) (point-max))) + (python-util-forward-comment -1) + (should (= (point) (point-min))))) + +(ert-deftest python-util-valid-regexp-p-1 () + (should (python-util-valid-regexp-p "")) + (should (python-util-valid-regexp-p python-shell-prompt-regexp)) + (should (not (python-util-valid-regexp-p "\\(")))) + + +;;; Electricity + +(ert-deftest python-parens-electric-indent-1 () + (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 + +from myapp import views + + +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)))) + (or eim (electric-indent-mode -1))))) + +(ert-deftest python-triple-quote-pairing () + (let ((epm electric-pair-mode)) + (unwind-protect + (progn + (python-tests-with-temp-buffer + "\"\"\n" + (or epm (electric-pair-mode 1)) + (goto-char (1- (point-max))) + (python-tests-self-insert ?\") + (should (string= (buffer-string) + "\"\"\"\"\"\"\n")) + (should (= (point) 4))) + (python-tests-with-temp-buffer + "\n" + (python-tests-self-insert (list ?\" ?\" ?\")) + (should (string= (buffer-string) + "\"\"\"\"\"\"\n")) + (should (= (point) 4))) + (python-tests-with-temp-buffer + "\"\n\"\"\n" + (goto-char (1- (point-max))) + (python-tests-self-insert ?\") + (should (= (point) (1- (point-max)))) + (should (string= (buffer-string) + "\"\n\"\"\"\n")))) + (or epm (electric-pair-mode -1))))) + + +;;; Hideshow support + +(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: +;; indent-tabs-mode: nil +;; End: + +;;; python-tests.el ends here diff --git a/test/lisp/progmodes/ruby-mode-tests.el b/test/lisp/progmodes/ruby-mode-tests.el new file mode 100644 index 00000000000..97f277bff41 --- /dev/null +++ b/test/lisp/progmodes/ruby-mode-tests.el @@ -0,0 +1,752 @@ +;;; ruby-mode-tests.el --- Test suite for ruby-mode + +;; Copyright (C) 2012-2016 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'ert) +(require 'ruby-mode) + +(defmacro ruby-with-temp-buffer (contents &rest body) + (declare (indent 1) (debug t)) + `(with-temp-buffer + (insert ,contents) + (ruby-mode) + ,@body)) + +(defun ruby-should-indent (content column) + "Assert indentation COLUMN on the last line of CONTENT." + (ruby-with-temp-buffer content + (indent-according-to-mode) + (should (= (current-indentation) column)))) + +(defun ruby-should-indent-buffer (expected content) + "Assert that CONTENT turns into EXPECTED after the buffer is re-indented. + +The whitespace before and including \"|\" on each line is removed." + (ruby-with-temp-buffer (ruby-test-string content) + (indent-region (point-min) (point-max)) + (should (string= (ruby-test-string expected) (buffer-string))))) + +(defun ruby-test-string (s &rest args) + (apply 'format (replace-regexp-in-string "^[ \t]*|" "" s) args)) + +(defun ruby-assert-state (content index value &optional point) + "Assert syntax state values at the end of CONTENT. + +VALUES-PLIST is a list with alternating index and value elements." + (ruby-with-temp-buffer content + (when point (goto-char point)) + (syntax-propertize (point)) + (should (eq (nth index + (parse-partial-sexp (point-min) (point))) + value)))) + +(defun ruby-assert-face (content pos face) + (ruby-with-temp-buffer content + (font-lock-ensure nil nil) + (should (eq face (get-text-property pos 'face))))) + +(ert-deftest ruby-indent-after-symbol-made-from-string-interpolation () + "It can indent the line after symbol made using string interpolation." + (ruby-should-indent "def foo(suffix)\n :\"bar#{suffix}\"\n" + ruby-indent-level)) + +(ert-deftest ruby-indent-after-js-style-symbol-with-block-beg-name () + "JS-style hash symbol can have keyword name." + (ruby-should-indent "link_to \"home\", home_path, class: \"foo\"\n" 0)) + +(ert-deftest ruby-discern-singleton-class-from-heredoc () + (ruby-assert-state "foo <<asd\n" 3 ?\n) + (ruby-assert-state "class <<asd\n" 3 nil)) + +(ert-deftest ruby-heredoc-font-lock () + (let ((s "foo <<eos.gsub('^ *', '')")) + (ruby-assert-face s 9 font-lock-string-face) + (ruby-assert-face s 10 nil))) + +(ert-deftest ruby-singleton-class-no-heredoc-font-lock () + (ruby-assert-face "class<<a" 8 nil)) + +(ert-deftest ruby-heredoc-highlights-interpolations () + (ruby-assert-face "s = <<EOS\n #{foo}\nEOS" 15 font-lock-variable-name-face)) + +(ert-deftest ruby-no-heredoc-inside-quotes () + (ruby-assert-state "\"<<\", \"\",\nfoo" 3 nil)) + +(ert-deftest ruby-no-heredoc-left-shift () + ;; We can't really detect the left shift operator (like in similar + ;; cases, it depends on the type of foo), so we just require for << + ;; to be preceded by a character from a known set. + (ruby-assert-state "foo(a<<b)" 3 nil)) + +(ert-deftest ruby-no-heredoc-class-self () + (ruby-assert-state "class <<self\nend" 3 nil)) + +(ert-deftest ruby-exit!-font-lock () + (ruby-assert-face "exit!" 5 font-lock-builtin-face)) + +(ert-deftest ruby-deep-indent () + (let ((ruby-deep-arglist nil) + (ruby-deep-indent-paren '(?\( ?\{ ?\[ ?\] t))) + (ruby-should-indent "foo = [1,\n2" 7) + (ruby-should-indent "foo = {a: b,\nc: d" 7) + (ruby-should-indent "foo(a,\nb" 4))) + +(ert-deftest ruby-deep-indent-disabled () + (let ((ruby-deep-arglist nil) + (ruby-deep-indent-paren nil)) + (ruby-should-indent "foo = [\n1" ruby-indent-level) + (ruby-should-indent "foo = {\na: b" ruby-indent-level) + (ruby-should-indent "foo(\na" ruby-indent-level))) + +(ert-deftest ruby-indent-after-keyword-in-a-string () + (ruby-should-indent "a = \"abc\nif\"\n " 0) + (ruby-should-indent "a = %w[abc\n def]\n " 0) + (ruby-should-indent "a = \"abc\n def\"\n " 0)) + +(ert-deftest ruby-regexp-doesnt-start-in-string () + (ruby-assert-state "'(/', /\d+/" 3 nil)) + +(ert-deftest ruby-regexp-starts-after-string () + (ruby-assert-state "'(/', /\d+/" 3 ?/ 8)) + +(ert-deftest ruby-regexp-interpolation-is-highlighted () + (ruby-assert-face "/#{foobs}/" 4 font-lock-variable-name-face)) + +(ert-deftest ruby-regexp-skips-over-interpolation () + (ruby-assert-state "/#{foobs.join('/')}/" 3 nil)) + +(ert-deftest ruby-regexp-continues-till-end-when-unclosed () + (ruby-assert-state "/bars" 3 ?/)) + +(ert-deftest ruby-regexp-can-be-multiline () + (ruby-assert-state "/bars\ntees # toots \nfoos/" 3 nil)) + +(ert-deftest ruby-slash-symbol-is-not-mistaken-for-regexp () + (ruby-assert-state ":/" 3 nil)) + +(ert-deftest ruby-slash-char-literal-is-not-mistaken-for-regexp () + (ruby-assert-state "?/" 3 nil)) + +(ert-deftest ruby-regexp-is-not-mistaken-for-slash-symbol () + (ruby-assert-state "x = /foo:/" 3 nil)) + +(ert-deftest ruby-indent-simple () + (ruby-should-indent-buffer + "if foo + | bar + |end + |zot + |" + "if foo + |bar + | end + | zot + |")) + +(ert-deftest ruby-indent-keyword-label () + (ruby-should-indent-buffer + "bar(class: XXX) do + | foo + |end + |bar + |" + "bar(class: XXX) do + | foo + | end + | bar + |")) + +(ert-deftest ruby-indent-method-with-question-mark () + (ruby-should-indent-buffer + "if x.is_a?(XXX) + | foo + |end + |" + "if x.is_a?(XXX) + | foo + | end + |")) + +(ert-deftest ruby-indent-expr-in-regexp () + (ruby-should-indent-buffer + "if /#{foo}/ =~ s + | x = 1 + |end + |" + "if /#{foo}/ =~ s + | x = 1 + | end + |")) + +(ert-deftest ruby-indent-singleton-class () + (ruby-should-indent-buffer + "class<<bar + | foo + |end + |" + "class<<bar + |foo + | end + |")) + +(ert-deftest ruby-indent-inside-heredoc-after-operator () + (ruby-should-indent-buffer + "b=<<eos + | 42" + "b=<<eos + | 42")) + +(ert-deftest ruby-indent-inside-heredoc-after-space () + (ruby-should-indent-buffer + "foo <<eos.gsub(' ', '*') + | 42" + "foo <<eos.gsub(' ', '*') + | 42")) + +(ert-deftest ruby-indent-array-literal () + (let ((ruby-deep-indent-paren nil)) + (ruby-should-indent-buffer + "foo = [ + | bar + |] + |" + "foo = [ + | bar + | ] + |")) + (ruby-should-indent-buffer + "foo do + | [bar] + |end + |" + "foo do + |[bar] + | end + |")) + +(ert-deftest ruby-indent-begin-end () + (ruby-should-indent-buffer + "begin + | a[b] + |end + |" + "begin + | a[b] + | end + |")) + +(ert-deftest ruby-indent-array-after-paren-and-space () + (ruby-should-indent-buffer + "class A + | def foo + | foo( []) + | end + |end + |" + "class A + | def foo + |foo( []) + |end + | end + |")) + +(ert-deftest ruby-indent-after-block-in-continued-expression () + (ruby-should-indent-buffer + "var = + | begin + | val + | end + |statement" + "var = + |begin + |val + |end + |statement")) + +(ert-deftest ruby-indent-spread-args-in-parens () + (let ((ruby-deep-indent-paren '(?\())) + (ruby-should-indent-buffer + "foo(1, + | 2, + | 3) + |" + "foo(1, + | 2, + | 3) + |"))) + +(ert-deftest ruby-align-to-stmt-keywords-t () + (let ((ruby-align-to-stmt-keywords t)) + (ruby-should-indent-buffer + "foo = if bar? + | 1 + |else + | 2 + |end + | + |foo || begin + | bar + |end + | + |foo || + | begin + | bar + | end + |" + "foo = if bar? + | 1 + |else + | 2 + | end + | + | foo || begin + | bar + |end + | + | foo || + | begin + |bar + | end + |") + )) + +(ert-deftest ruby-align-to-stmt-keywords-case () + (let ((ruby-align-to-stmt-keywords '(case))) + (ruby-should-indent-buffer + "b = case a + |when 13 + | 6 + |else + | 42 + |end" + "b = case a + | when 13 + | 6 + | else + | 42 + | end"))) + +(ert-deftest ruby-align-chained-calls () + (let ((ruby-align-chained-calls t)) + (ruby-should-indent-buffer + "one.two.three + | .four + | + |my_array.select { |str| str.size > 5 } + | .map { |str| str.downcase }" + "one.two.three + | .four + | + |my_array.select { |str| str.size > 5 } + | .map { |str| str.downcase }"))) + +(ert-deftest ruby-move-to-block-stops-at-indentation () + (ruby-with-temp-buffer "def f\nend" + (beginning-of-line) + (ruby-move-to-block -1) + (should (looking-at "^def")))) + +(ert-deftest ruby-toggle-block-to-do-end () + (ruby-with-temp-buffer "foo {|b|\n}" + (beginning-of-line) + (ruby-toggle-block) + (should (string= "foo do |b|\nend" (buffer-string))))) + +(ert-deftest ruby-toggle-block-to-brace () + (let ((pairs '((17 . "foo { |b| b + 2 }") + (16 . "foo { |b|\n b + 2\n}")))) + (dolist (pair pairs) + (with-temp-buffer + (let ((fill-column (car pair))) + (insert "foo do |b|\n b + 2\nend") + (ruby-mode) + (beginning-of-line) + (ruby-toggle-block) + (should (string= (cdr pair) (buffer-string)))))))) + +(ert-deftest ruby-toggle-block-to-multiline () + (ruby-with-temp-buffer "foo {|b| b + 1}" + (beginning-of-line) + (ruby-toggle-block) + (should (string= "foo do |b|\n b + 1\nend" (buffer-string))))) + +(ert-deftest ruby-toggle-block-with-interpolation () + (ruby-with-temp-buffer "foo do\n \"#{bar}\"\nend" + (beginning-of-line) + (ruby-toggle-block) + (should (string= "foo { \"#{bar}\" }" (buffer-string))))) + +(ert-deftest ruby-recognize-symbols-starting-with-at-character () + (ruby-assert-face ":@abc" 3 font-lock-constant-face)) + +(ert-deftest ruby-hash-character-not-interpolation () + (ruby-assert-face "\"This is #{interpolation}\"" 15 + font-lock-variable-name-face) + (ruby-assert-face "\"This is \\#{no interpolation} despite the #\"" + 15 font-lock-string-face) + (ruby-assert-face "\n#@comment, not ruby code" 5 font-lock-comment-face) + (ruby-assert-state "\n#@comment, not ruby code" 4 t) + (ruby-assert-face "# A comment cannot have #{an interpolation} in it" + 30 font-lock-comment-face) + (ruby-assert-face "# #{comment}\n \"#{interpolation}\"" 16 + font-lock-variable-name-face)) + +(ert-deftest ruby-interpolation-suppresses-quotes-inside () + (let ((s "\"<ul><li>#{@files.join(\"</li><li>\")}</li></ul>\"")) + (ruby-assert-state s 8 nil) + (ruby-assert-face s 9 font-lock-string-face) + (ruby-assert-face s 10 font-lock-variable-name-face) + (ruby-assert-face s 41 font-lock-string-face))) + +(ert-deftest ruby-interpolation-suppresses-one-double-quote () + (let ((s "\"foo#{'\"'}\"")) + (ruby-assert-state s 8 nil) + (ruby-assert-face s 8 font-lock-variable-name-face) + (ruby-assert-face s 11 font-lock-string-face))) + +(ert-deftest ruby-interpolation-suppresses-one-backtick () + (let ((s "`as#{'`'}das`")) + (ruby-assert-state s 8 nil))) + +(ert-deftest ruby-interpolation-keeps-non-quote-syntax () + (let ((s "\"foo#{baz.tee}bar\"")) + (ruby-with-temp-buffer s + (goto-char (point-min)) + (ruby-mode) + (syntax-propertize (point-max)) + (search-forward "tee") + (should (string= (thing-at-point 'symbol) "tee"))))) + +(ert-deftest ruby-interpolation-inside-percent-literal () + (let ((s "%( #{boo} )")) + (ruby-assert-face s 1 font-lock-string-face) + (ruby-assert-face s 4 font-lock-variable-name-face) + (ruby-assert-face s 10 font-lock-string-face) + (ruby-assert-state s 8 nil))) + +(ert-deftest ruby-interpolation-inside-percent-literal-with-paren () + :expected-result :failed + (let ((s "%(^#{\")\"}^)")) + (ruby-assert-face s 3 font-lock-string-face) + (ruby-assert-face s 4 font-lock-variable-name-face) + (ruby-assert-face s 10 font-lock-string-face) + ;; It's confused by the closing paren in the middle. + (ruby-assert-state s 8 nil))) + +(ert-deftest ruby-interpolation-inside-another-interpolation () + :expected-result :failed + (let ((s "\"#{[a, b, c].map { |v| \"#{v}\" }.join}\"")) + (ruby-assert-face s 1 font-lock-string-face) + (ruby-assert-face s 2 font-lock-variable-name-face) + (ruby-assert-face s 38 font-lock-string-face) + (ruby-assert-state s 8 nil))) + +(ert-deftest ruby-interpolation-inside-double-quoted-percent-literals () + (ruby-assert-face "%Q{foo #@bar}" 8 font-lock-variable-name-face) + (ruby-assert-face "%W{foo #@bar}" 8 font-lock-variable-name-face) + (ruby-assert-face "%r{foo #@bar}" 8 font-lock-variable-name-face) + (ruby-assert-face "%x{foo #@bar}" 8 font-lock-variable-name-face)) + +(ert-deftest ruby-no-interpolation-in-single-quoted-literals () + (ruby-assert-face "'foo #@bar'" 7 font-lock-string-face) + (ruby-assert-face "%q{foo #@bar}" 8 font-lock-string-face) + (ruby-assert-face "%w{foo #@bar}" 8 font-lock-string-face) + (ruby-assert-face "%s{foo #@bar}" 8 font-lock-string-face)) + +(ert-deftest ruby-interpolation-after-dollar-sign () + (ruby-assert-face "\"$#{balance}\"" 2 'font-lock-string-face) + (ruby-assert-face "\"$#{balance}\"" 3 'font-lock-variable-name-face)) + +(ert-deftest ruby-no-unknown-percent-literals () + ;; No folding of case. + (ruby-assert-face "%S{foo}" 4 nil) + (ruby-assert-face "%R{foo}" 4 nil)) + +(ert-deftest ruby-no-nested-percent-literals () + (ruby-with-temp-buffer "a = %w[b %()]" + (syntax-propertize (point)) + (should (null (nth 8 (syntax-ppss)))) + (should (eq t (nth 3 (syntax-ppss (1- (point-max)))))) + (search-backward "[") + (should (eq t (nth 3 (syntax-ppss)))))) + +(ert-deftest ruby-add-log-current-method-examples () + (let ((pairs '(("foo" . "#foo") + ("C.foo" . ".foo") + ("self.foo" . ".foo")))) + (dolist (pair pairs) + (let ((name (car pair)) + (value (cdr pair))) + (ruby-with-temp-buffer (ruby-test-string + "module M + | class C + | def %s + | _ + | end + | end + |end" + name) + (search-backward "_") + (forward-line) + (should (string= (ruby-add-log-current-method) + (format "M::C%s" value)))))))) + +(ert-deftest ruby-add-log-current-method-outside-of-method () + (ruby-with-temp-buffer (ruby-test-string + "module M + | class C + | def foo + | end + | _ + | end + |end") + (search-backward "_") + (should (string= (ruby-add-log-current-method)"M::C")))) + +(ert-deftest ruby-add-log-current-method-in-singleton-class () + (ruby-with-temp-buffer (ruby-test-string + "class C + | class << self + | def foo + | _ + | end + | end + |end") + (search-backward "_") + (should (string= (ruby-add-log-current-method) "C.foo")))) + +(ert-deftest ruby-add-log-current-method-namespace-shorthand () + (ruby-with-temp-buffer (ruby-test-string + "class C::D + | def foo + | _ + | end + |end") + (search-backward "_") + (should (string= (ruby-add-log-current-method) "C::D#foo")))) + +(ert-deftest ruby-add-log-current-method-after-inner-class () + (ruby-with-temp-buffer (ruby-test-string + "module M + | class C + | class D + | end + | def foo + | _ + | end + | end + |end") + (search-backward "_") + (should (string= (ruby-add-log-current-method) "M::C#foo")))) + +(defvar ruby-block-test-example + (ruby-test-string + "class C + | def foo + | 1 + | end + | + | def bar + | 2 + | end + | + | def baz + |some do + |3 + | end + | end + |end")) + +(defmacro ruby-deftest-move-to-block (name &rest body) + (declare (indent defun)) + `(ert-deftest ,(intern (format "ruby-move-to-block-%s" name)) () + (with-temp-buffer + (insert ruby-block-test-example) + (ruby-mode) + (goto-char (point-min)) + ,@body))) + +(ruby-deftest-move-to-block works-on-do + (forward-line 10) + (ruby-end-of-block) + (should (= 13 (line-number-at-pos))) + (ruby-beginning-of-block) + (should (= 11 (line-number-at-pos)))) + +(ruby-deftest-move-to-block zero-is-noop + (forward-line 4) + (ruby-move-to-block 0) + (should (= 5 (line-number-at-pos)))) + +(ruby-deftest-move-to-block ok-with-three + (forward-line 1) + (ruby-move-to-block 3) + (should (= 14 (line-number-at-pos)))) + +(ruby-deftest-move-to-block ok-with-minus-two + (forward-line 9) + (ruby-move-to-block -2) + (should (= 2 (line-number-at-pos)))) + +(ert-deftest ruby-move-to-block-skips-percent-literal () + (dolist (s (list (ruby-test-string + "foo do + | a = %%w( + | def yaa + | ) + |end") + (ruby-test-string + "foo do + | a = %%w| + | end + | | + |end"))) + (ruby-with-temp-buffer s + (goto-char (point-min)) + (ruby-end-of-block) + (should (= 5 (line-number-at-pos))) + (ruby-beginning-of-block) + (should (= 1 (line-number-at-pos)))))) + +(ert-deftest ruby-move-to-block-skips-heredoc () + (ruby-with-temp-buffer + (ruby-test-string + "if something_wrong? + | ActiveSupport::Deprecation.warn(<<-eowarn) + | boo hoo + | end + | eowarn + |end") + (goto-char (point-min)) + (ruby-end-of-block) + (should (= 6 (line-number-at-pos))) + (ruby-beginning-of-block) + (should (= 1 (line-number-at-pos))))) + +(ert-deftest ruby-move-to-block-does-not-fold-case () + (ruby-with-temp-buffer + (ruby-test-string + "foo do + | Module.to_s + |end") + (let ((case-fold-search t)) + (ruby-beginning-of-block)) + (should (= 1 (line-number-at-pos))))) + +(ert-deftest ruby-move-to-block-moves-from-else-to-if () + (ruby-with-temp-buffer (ruby-test-string + "if true + | nested_block do + | end + |else + |end") + (goto-char (point-min)) + (forward-line 3) + (ruby-beginning-of-block) + (should (= 1 (line-number-at-pos))))) + +(ert-deftest ruby-beginning-of-defun-does-not-fold-case () + (ruby-with-temp-buffer + (ruby-test-string + "class C + | def bar + | Class.to_s + | end + |end") + (goto-char (point-min)) + (forward-line 3) + (let ((case-fold-search t)) + (beginning-of-defun)) + (should (= 2 (line-number-at-pos))))) + +(ert-deftest ruby-end-of-defun-skips-to-next-line-after-the-method () + (ruby-with-temp-buffer + (ruby-test-string + "class D + | def tee + | 'ho hum' + | end + |end") + (goto-char (point-min)) + (forward-line 1) + (end-of-defun) + (should (= 5 (line-number-at-pos))))) + +(defvar ruby-sexp-test-example + (ruby-test-string + "class C + | def foo + | self.end + | D.new.class + | [1, 2, 3].map do |i| + | i + 1 + | end.sum + | end + |end")) + +(ert-deftest ruby-forward-sexp-skips-method-calls-with-keyword-names () + (ruby-with-temp-buffer ruby-sexp-test-example + (goto-line 2) + (ruby-forward-sexp) + (should (= 8 (line-number-at-pos))))) + +(ert-deftest ruby-backward-sexp-skips-method-calls-with-keyword-names () + (ruby-with-temp-buffer ruby-sexp-test-example + (goto-line 8) + (end-of-line) + (ruby-backward-sexp) + (should (= 2 (line-number-at-pos))))) + +(ert-deftest ruby-toggle-string-quotes-quotes-correctly () + (let ((pairs + '(("puts '\"foo\"\\''" . "puts \"\\\"foo\\\"'\"") + ("puts \"'foo'\\\"\"" . "puts '\\'foo\\'\"'")))) + (dolist (pair pairs) + (ruby-with-temp-buffer (car pair) + (beginning-of-line) + (search-forward "foo") + (ruby-toggle-string-quotes) + (should (string= (buffer-string) (cdr pair))))))) + +(ert-deftest ruby--insert-coding-comment-ruby-style () + (with-temp-buffer + (let ((ruby-encoding-magic-comment-style 'ruby)) + (ruby--insert-coding-comment "utf-8") + (should (string= "# coding: utf-8\n" (buffer-string)))))) + +(ert-deftest ruby--insert-coding-comment-emacs-style () + (with-temp-buffer + (let ((ruby-encoding-magic-comment-style 'emacs)) + (ruby--insert-coding-comment "utf-8") + (should (string= "# -*- coding: utf-8 -*-\n" (buffer-string)))))) + +(ert-deftest ruby--insert-coding-comment-custom-style () + (with-temp-buffer + (let ((ruby-encoding-magic-comment-style 'custom) + (ruby-custom-encoding-magic-comment-template "# encoding: %s\n")) + (ruby--insert-coding-comment "utf-8") + (should (string= "# encoding: utf-8\n\n" (buffer-string)))))) + + +(provide 'ruby-mode-tests) + +;;; ruby-mode-tests.el ends here diff --git a/test/lisp/progmodes/sql-tests.el b/test/lisp/progmodes/sql-tests.el new file mode 100644 index 00000000000..e05247a60ed --- /dev/null +++ b/test/lisp/progmodes/sql-tests.el @@ -0,0 +1,47 @@ +;;; sql-tests.el --- Tests for sql.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Simen Heggestøyl <simenheg@gmail.com> +;; Keywords: + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(require 'cl-lib) +(require 'ert) +(require 'sql) + +(ert-deftest sql-tests-postgres-list-databases () + "Test that output from `psql -ltX' is parsed correctly." + (cl-letf + (((symbol-function 'executable-find) + (lambda (_command) t)) + ((symbol-function 'process-lines) + (lambda (_program &rest _args) + '(" db-name-1 | foo-user | UTF8 | en_US.UTF-8 | en_US.UTF-8 | " + " db_name_2 | foo-user | UTF8 | en_US.UTF-8 | en_US.UTF-8 | " + "")))) + (should (equal (sql-postgres-list-databases) + '("db-name-1" "db_name_2"))))) + +(provide 'sql-tests) +;;; sql-tests.el ends here diff --git a/test/lisp/progmodes/subword-tests.el b/test/lisp/progmodes/subword-tests.el new file mode 100644 index 00000000000..5a562765bb1 --- /dev/null +++ b/test/lisp/progmodes/subword-tests.el @@ -0,0 +1,81 @@ +;;; subword-tests.el --- Testing the subword rules + +;; Copyright (C) 2011-2016 Free Software Foundation, Inc. + +;; Author: Stefan Monnier <monnier@iro.umontreal.ca> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(require 'ert) +(require 'subword) + +(defconst subword-tests-strings + '("ABC^" ;;Bug#13758 + "ABC^ ABC^Foo^ ABC^-Foo^ toto^ ABC^")) + +(ert-deftest subword-tests () + "Test the `subword-mode' rules." + (with-temp-buffer + (dolist (str subword-tests-strings) + (erase-buffer) + (insert str) + (goto-char (point-min)) + (while (search-forward "^" nil t) + (replace-match "")) + (goto-char (point-min)) + (while (not (eobp)) + (subword-forward 1) + (insert "^")) + (should (equal (buffer-string) str))))) + +(ert-deftest subword-tests2 () + "Test that motion in subword-mode stops at the right places." + + (let* ((line "fooBarBAZ quXD g_TESTThingAbc word BLAH test") + (fwrd "* * * * * * * * * * * * *") + (bkwd "* * * * * * * * * * * * *")) + + (with-temp-buffer + (subword-mode 1) + (insert line) + + ;; Test forward motion. + + (goto-char (point-min)) + (let ((stops (make-string (length fwrd) ?\ ))) + (while (progn + (aset stops (1- (point)) ?\*) + (not (eobp))) + (forward-word)) + (should (equal stops fwrd))) + + ;; Test backward motion. + + (goto-char (point-max)) + (let ((stops (make-string (length bkwd) ?\ ))) + (while (progn + (aset stops (1- (point)) ?\*) + (not (bobp))) + (backward-word)) + (should (equal stops bkwd)))))) + +(provide 'subword-tests) +;;; subword-tests.el ends here diff --git a/test/lisp/progmodes/xref-tests.el b/test/lisp/progmodes/xref-tests.el new file mode 100644 index 00000000000..2b745816c62 --- /dev/null +++ b/test/lisp/progmodes/xref-tests.el @@ -0,0 +1,91 @@ +;;; xref-tests.el --- tests for xref + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Dmitry Gutov <dgutov@yandex.ru> + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'xref) +(require 'cl-lib) + +(defvar xref-tests-data-dir + (expand-file-name "data/xref/" + (getenv "EMACS_TEST_DIRECTORY"))) + +(ert-deftest xref-collect-matches-finds-none-for-some-regexp () + (should (null (xref-collect-matches "zzz" "*" xref-tests-data-dir nil)))) + +(ert-deftest xref-collect-matches-finds-some-for-bar () + (let* ((matches (xref-collect-matches "bar" "*" xref-tests-data-dir nil)) + (locs (cl-sort (mapcar #'xref-item-location matches) + #'string< + :key #'xref-location-group))) + (should (= 2 (length matches))) + (should (string-match-p "file1\\.txt\\'" (xref-location-group (nth 0 locs)))) + (should (string-match-p "file2\\.txt\\'" (xref-location-group (nth 1 locs)))))) + +(ert-deftest xref-collect-matches-finds-two-matches-on-the-same-line () + (let* ((matches (xref-collect-matches "foo" "*" xref-tests-data-dir nil)) + (locs (mapcar #'xref-item-location matches))) + (should (= 2 (length matches))) + (should (string-match-p "file1\\.txt\\'" (xref-location-group (nth 0 locs)))) + (should (string-match-p "file1\\.txt\\'" (xref-location-group (nth 1 locs)))) + (should (equal 1 (xref-location-line (nth 0 locs)))) + (should (equal 1 (xref-location-line (nth 1 locs)))) + (should (equal 0 (xref-file-location-column (nth 0 locs)))) + (should (equal 4 (xref-file-location-column (nth 1 locs)))))) + +(ert-deftest xref-collect-matches-finds-an-empty-line-regexp-match () + (let* ((matches (xref-collect-matches "^$" "*" xref-tests-data-dir nil)) + (locs (mapcar #'xref-item-location matches))) + (should (= 1 (length matches))) + (should (string-match-p "file2\\.txt\\'" (xref-location-group (nth 0 locs)))) + (should (equal 1 (xref-location-line (nth 0 locs)))) + (should (equal 0 (xref-file-location-column (nth 0 locs)))))) + +(ert-deftest xref--buf-pairs-iterator-groups-markers-by-buffers-1 () + (let* ((xrefs (xref-collect-matches "foo" "*" xref-tests-data-dir nil)) + (iter (xref--buf-pairs-iterator xrefs)) + (cons (funcall iter :next))) + (should (null (funcall iter :next))) + (should (string-match "file1\\.txt\\'" (buffer-file-name (car cons)))) + (should (= 2 (length (cdr cons)))))) + +(ert-deftest xref--buf-pairs-iterator-groups-markers-by-buffers-2 () + (let* ((xrefs (xref-collect-matches "bar" "*" xref-tests-data-dir nil)) + (iter (xref--buf-pairs-iterator xrefs)) + (cons1 (funcall iter :next)) + (cons2 (funcall iter :next))) + (should (null (funcall iter :next))) + (should-not (equal (car cons1) (car cons2))) + (should (= 1 (length (cdr cons1)))) + (should (= 1 (length (cdr cons2)))))) + +(ert-deftest xref--buf-pairs-iterator-cleans-up-markers () + (let* ((xrefs (xref-collect-matches "bar" "*" xref-tests-data-dir nil)) + (iter (xref--buf-pairs-iterator xrefs)) + (cons1 (funcall iter :next)) + (cons2 (funcall iter :next))) + (funcall iter :cleanup) + (should (null (marker-position (car (nth 0 (cdr cons1)))))) + (should (null (marker-position (cdr (nth 0 (cdr cons1)))))) + (should (null (marker-position (car (nth 0 (cdr cons2)))))) + (should (null (marker-position (cdr (nth 0 (cdr cons2)))))))) |