summaryrefslogtreecommitdiff
path: root/test/lisp/progmodes/eglot-tests.el
diff options
context:
space:
mode:
Diffstat (limited to 'test/lisp/progmodes/eglot-tests.el')
-rw-r--r--test/lisp/progmodes/eglot-tests.el175
1 files changed, 99 insertions, 76 deletions
diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el
index 4b6528351b2..7ac26732737 100644
--- a/test/lisp/progmodes/eglot-tests.el
+++ b/test/lisp/progmodes/eglot-tests.el
@@ -31,23 +31,20 @@
;; Some of these tests rely on the GNU ELPA package company.el and
;; yasnippet.el being available.
-;; Some of the tests require access to a remote host files. Since
-;; this could be problematic, a mock-up connection method "mock" is
-;; used. Emulating a remote connection, it simply calls "sh -i".
-;; Tramp's file name handlers still run, so this test is sufficient
-;; except for connection establishing.
-
-;; If you want to test a real Tramp connection, set
-;; $REMOTE_TEMPORARY_FILE_DIRECTORY to a suitable value in order to
-;; overwrite the default value. If you want to skip tests accessing a
-;; remote host, set this environment variable to "/dev/null" or
-;; whatever is appropriate on your system.
+;; Some of the tests require access to a remote host files, which is
+;; mocked in the simplest case. If you want to test a real Tramp
+;; connection, override $REMOTE_TEMPORARY_FILE_DIRECTORY to a suitable
+;; value (FIXME: like what?) in order to overwrite the default value.
+;;
+;; IMPORTANT: Since Eglot is a :core ELPA package, these tests are
+ ;;supposed to run on Emacsen down to 26.3. Do not use bleeding-edge
+ ;;functionality not compatible with that Emacs version.
;;; Code:
(require 'eglot)
(require 'cl-lib)
(require 'ert)
-(require 'tramp) ; must be prior ert-x
+(require 'tramp)
(require 'ert-x) ; ert-simulate-command
(require 'edebug)
(require 'python) ; some tests use pylsp
@@ -59,6 +56,11 @@
;;; Helpers
+(defun eglot--test-message (format &rest args)
+ "Message out with FORMAT with ARGS."
+ (message "[eglot-tests] %s"
+ (apply #'format format args)))
+
(defmacro eglot--with-fixture (fixture &rest body)
"Setup FIXTURE, call BODY, teardown FIXTURE.
FIXTURE is a list. Its elements are of the form (FILE . CONTENT)
@@ -102,23 +104,23 @@ then restored."
(push (cons (car spec) (symbol-value (car spec))) syms-to-restore)
(set (car spec) (cadr spec)))
((stringp (car spec)) (push spec file-specs))))
+ (eglot--test-message "[%s]: test start" (ert-test-name (ert-running-test)))
(unwind-protect
- (let* ((home (getenv "HOME"))
- (process-environment
+ (let* ((process-environment
(append
`(;; Set XDF_CONFIG_HOME to /dev/null to prevent
;; user-configuration to have an influence on
;; language servers. (See github#441)
"XDG_CONFIG_HOME=/dev/null"
;; ... on the flip-side, a similar technique by
- ;; Emacs's test makefiles means that HOME is set to
- ;; /nonexistent. This breaks some common
- ;; installations for LSP servers like pylsp, making
- ;; these tests mostly useless, so we hack around it
- ;; here with a great big hack.
+ ;; Emacs's test makefiles means that HOME is
+ ;; spoofed to /nonexistent, or sometimes /tmp.
+ ;; This breaks some common installations for LSP
+ ;; servers like pylsp, rust-analyzer making these
+ ;; tests mostly useless, so we hack around it here
+ ;; with a great big hack.
,(format "HOME=%s"
- (if (file-exists-p home) home
- (format "/home/%s" (getenv "USER")))))
+ (expand-file-name (format "~%s" (user-login-name)))))
process-environment))
;; Prevent "Can't guess python-indent-offset ..." messages.
(python-indent-guess-indent-offset-verbose . nil)
@@ -127,8 +129,8 @@ then restored."
(setq created-files (mapcan #'eglot--make-file-or-dir file-specs))
(prog1 (funcall fn)
(setq test-body-successful-p t)))
- (eglot--message
- "Test body was %s" (if test-body-successful-p "OK" "A FAILURE"))
+ (eglot--test-message "[%s]: %s" (ert-test-name (ert-running-test))
+ (if test-body-successful-p "OK" "FAILED"))
(unwind-protect
(let ((eglot-autoreconnect nil))
(dolist (server new-servers)
@@ -137,8 +139,7 @@ then restored."
(eglot-shutdown
server nil 3 (not test-body-successful-p))
(error
- (eglot--message "Non-critical shutdown error after test: %S"
- oops))))
+ (eglot--test-message "Non-critical cleanup error: %S" oops))))
(when (not test-body-successful-p)
;; We want to do this after the sockets have
;; shut down such that any pending data has been
@@ -151,21 +152,21 @@ then restored."
(jsonrpc-events-buffer server)))))
(cond (noninteractive
(dolist (buffer buffers)
- (eglot--message "%s:" (buffer-name buffer))
+ (eglot--test-message "contents of `%s':" (buffer-name buffer))
(princ (with-current-buffer buffer (buffer-string))
'external-debugging-output)))
(t
- (eglot--message "Preserved for inspection: %s"
- (mapconcat #'buffer-name buffers ", "))))))))
+ (eglot--test-message "Preserved for inspection: %s"
+ (mapconcat #'buffer-name buffers ", "))))))))
(eglot--cleanup-after-test fixture-directory created-files syms-to-restore)))))
(defun eglot--cleanup-after-test (fixture-directory created-files syms-to-restore)
(let ((buffers-to-delete
(delete nil (mapcar #'find-buffer-visiting created-files))))
- (eglot--message "Killing %s, wiping %s, restoring %s"
- buffers-to-delete
- fixture-directory
- (mapcar #'car syms-to-restore))
+ (eglot--test-message "Killing %s, wiping %s, restoring %s"
+ buffers-to-delete
+ fixture-directory
+ (mapcar #'car syms-to-restore))
(cl-loop for (sym . val) in syms-to-restore
do (set sym val))
(dolist (buf buffers-to-delete) ;; have to save otherwise will get prompted
@@ -253,12 +254,12 @@ then restored."
(advice-remove #'jsonrpc--log-event ',log-event-ad-sym))))
(cl-defmacro eglot--wait-for ((events-sym &optional (timeout 1) message) args &body body)
- "Spin until FN match in EVENTS-SYM, flush events after it.
-Pass TIMEOUT to `eglot--with-timeout'."
(declare (indent 2) (debug (sexp sexp sexp &rest form)))
`(eglot--with-timeout '(,timeout ,(or message
(format "waiting for:\n%s" (pp-to-string body))))
- (let ((event
+ (eglot--test-message "waiting for `%s'" (with-output-to-string
+ (mapc #'princ ',body)))
+ (let ((events
(cl-loop thereis (cl-loop for json in ,events-sym
for method = (plist-get json :method)
when (keywordp method)
@@ -272,16 +273,21 @@ Pass TIMEOUT to `eglot--with-timeout'."
collect json into before)
for i from 0
when (zerop (mod i 5))
- ;; do (eglot--message "still struggling to find in %s"
- ;; ,events-sym)
+ ;; do (eglot--test-message "still struggling to find in %s"
+ ;; ,events-sym)
do
;; `read-event' is essential to have the file
;; watchers come through.
- (read-event "[eglot] Waiting a bit..." nil 0.1)
+ (cond ((fboundp 'flush-standard-output)
+ (read-event nil nil 0.1) (princ ".")
+ (flush-standard-output))
+ (t
+ (read-event "." nil 0.1)))
(accept-process-output nil 0.1))))
- (setq ,events-sym (cdr event))
- (eglot--message "Event detected:\n%s"
- (pp-to-string (car event))))))
+ (setq ,events-sym (cdr events))
+ (cl-destructuring-bind (&key method id &allow-other-keys) (car events)
+ (eglot--test-message "detected: %s"
+ (or method (and id (format "id=%s" id))))))))
;; `rust-mode' is not a part of Emacs, so we define these two shims
;; which should be more than enough for testing.
@@ -408,7 +414,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
)
(should (eglot--tests-connect))
(let (register-id)
- (eglot--wait-for (s-requests 1)
+ (eglot--wait-for (s-requests 3)
(&key id method &allow-other-keys)
(setq register-id id)
(string= method "client/registerCapability"))
@@ -435,7 +441,7 @@ Pass TIMEOUT to `eglot--with-timeout'."
(eglot--find-file-noselect "diag-project/main.c")
(eglot--sniffing (:server-notifications s-notifs)
(eglot--tests-connect)
- (eglot--wait-for (s-notifs 2)
+ (eglot--wait-for (s-notifs 10)
(&key _id method &allow-other-keys)
(string= method "textDocument/publishDiagnostics"))
(flymake-start)
@@ -445,16 +451,19 @@ Pass TIMEOUT to `eglot--with-timeout'."
(ert-deftest eglot-test-diagnostic-tags-unnecessary-code ()
"Test rendering of diagnostics tagged \"unnecessary\"."
- (skip-unless (executable-find "rust-analyzer"))
- (skip-unless (executable-find "cargo"))
+ (skip-unless (executable-find "clangd"))
(eglot--with-fixture
- '(("diagnostic-tag-project" .
- (("main.rs" .
- "fn main() -> () { let test=3; }"))))
+ `(("diag-project" .
+ (("main.cpp" . "int main(){float a = 42.2; return 0;}"))))
(with-current-buffer
- (eglot--find-file-noselect "diagnostic-tag-project/main.rs")
- (let ((eglot-server-programs '((rust-mode . ("rust-analyzer")))))
- (should (zerop (shell-command "cargo init")))
+ (eglot--find-file-noselect "diag-project/main.cpp")
+ (eglot--make-file-or-dir '(".git"))
+ (eglot--make-file-or-dir
+ `("compile_commands.json" .
+ ,(jsonrpc--json-encode
+ `[(:directory ,default-directory :command "/usr/bin/c++ -Wall -c main.cpp"
+ :file ,(expand-file-name "main.cpp"))])))
+ (let ((eglot-server-programs '((c++-mode . ("clangd")))))
(eglot--sniffing (:server-notifications s-notifs)
(eglot--tests-connect)
(eglot--wait-for (s-notifs 10)
@@ -732,7 +741,7 @@ pylsp prefers autopep over yafp, despite its README stating the contrary."
(should (zerop (shell-command "cargo init")))
(eglot--sniffing (:server-notifications s-notifs)
(should (eglot--tests-connect))
- (eglot--wait-for (s-notifs 10) (&key method &allow-other-keys)
+ (eglot--wait-for (s-notifs 20) (&key method &allow-other-keys)
(string= method "textDocument/publishDiagnostics")))
(goto-char (point-max))
(eglot--simulate-key-event ?.)
@@ -798,33 +807,35 @@ pylsp prefers autopep over yafp, despite its README stating the contrary."
(should (= 4 (length (flymake--project-diagnostics))))))))))
(ert-deftest eglot-test-project-wide-diagnostics-rust-analyzer ()
- "Test diagnostics through multiple files in a TypeScript LSP."
+ "Test diagnostics through multiple files in rust-analyzer."
(skip-unless (executable-find "rust-analyzer"))
(skip-unless (executable-find "cargo"))
+ (skip-unless (executable-find "git"))
(eglot--with-fixture
'(("project" .
(("main.rs" .
- "fn main() -> () { let test=3; }")
+ "fn main() -> i32 { return 42.2;}")
("other-file.rs" .
"fn foo() -> () { let hi=3; }"))))
- (eglot--make-file-or-dir '(".git"))
(let ((eglot-server-programs '((rust-mode . ("rust-analyzer")))))
- ;; Open other-file, and see diagnostics arrive for main.rs
+ ;; Open other-file.rs, and see diagnostics arrive for main.rs,
+ ;; which we didn't open.
(with-current-buffer (eglot--find-file-noselect "project/other-file.rs")
+ (should (zerop (shell-command "git init")))
(should (zerop (shell-command "cargo init")))
(eglot--sniffing (:server-notifications s-notifs)
(eglot--tests-connect)
(flymake-start)
- (eglot--wait-for (s-notifs 10)
- (&key _id method &allow-other-keys)
- (string= method "textDocument/publishDiagnostics"))
- (let ((diags (flymake--project-diagnostics)))
- (should (= 2 (length diags)))
- ;; Check that we really get a diagnostic from main.rs, and
- ;; not from other-file.rs
- (should (string-suffix-p
- "main.rs"
- (flymake-diagnostic-buffer (car diags))))))))))
+ (eglot--wait-for (s-notifs 20)
+ (&key _id method params &allow-other-keys)
+ (and (string= method "textDocument/publishDiagnostics")
+ (string-suffix-p "main.rs" (plist-get params :uri))))
+ (let* ((diags (flymake--project-diagnostics)))
+ (should (cl-some (lambda (diag)
+ (let ((locus (flymake-diagnostic-buffer diag)))
+ (and (stringp (flymake-diagnostic-buffer diag))
+ (string-suffix-p "main.rs" locus))))
+ diags))))))))
(ert-deftest eglot-test-json-basic ()
"Test basic autocompletion in vscode-json-languageserver."
@@ -856,8 +867,8 @@ pylsp prefers autopep over yafp, despite its README stating the contrary."
'((c-mode . ("clangd")))))
(with-current-buffer
(eglot--find-file-noselect "project/foo.c")
- (setq-local eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column)
- (setq-local eglot-current-column-function #'eglot-lsp-abiding-column)
+ (setq-local eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos)
+ (setq-local eglot-current-linepos-function #'eglot-utf-16-linepos)
(eglot--sniffing (:client-notifications c-notifs)
(eglot--tests-connect)
(end-of-line)
@@ -866,12 +877,12 @@ pylsp prefers autopep over yafp, despite its README stating the contrary."
(eglot--wait-for (c-notifs 2) (&key params &allow-other-keys)
(should (equal 71 (cadddr (cadadr (aref (cadddr params) 0))))))
(beginning-of-line)
- (should (eq eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column))
- (funcall eglot-move-to-column-function 71)
+ (should (eq eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos))
+ (funcall eglot-move-to-linepos-function 71)
(should (looking-at "p")))))))
(ert-deftest eglot-test-lsp-abiding-column ()
- "Test basic `eglot-lsp-abiding-column' and `eglot-move-to-lsp-abiding-column'."
+ "Test basic LSP character counting logic."
(skip-unless (executable-find "clangd"))
(eglot-tests--lsp-abiding-column-1))
@@ -1258,16 +1269,28 @@ macro will assume it exists."
(defvar tramp-histfile-override)
(defun eglot--call-with-tramp-test (fn)
+ (unless (>= emacs-major-version 27)
+ (ert-skip "Eglot Tramp support only on Emacs >= 27"))
;; Set up a Tramp method that’s just a shell so the remote host is
;; really just the local host.
- (let* ((tramp-remote-path (cons 'tramp-own-remote-path tramp-remote-path))
+ (let* ((tramp-remote-path (cons 'tramp-own-remote-path
+ tramp-remote-path))
(tramp-histfile-override t)
(tramp-verbose 1)
- (temporary-file-directory ert-remote-temporary-file-directory)
+ (temporary-file-directory
+ (or (bound-and-true-p ert-remote-temporary-file-directory)
+ (prog1 (format "/mock::%s" temporary-file-directory)
+ (add-to-list
+ 'tramp-methods
+ '("mock"
+ (tramp-login-program "sh") (tramp-login-args (("-i")))
+ (tramp-direct-async ("-c")) (tramp-remote-shell "/bin/sh")
+ (tramp-remote-shell-args ("-c")) (tramp-connection-timeout 10)))
+ (add-to-list 'tramp-default-host-alist
+ `("\\`mock\\'" nil ,(system-name)))
+ (when (and noninteractive (not (file-directory-p "~/")))
+ (setenv "HOME" temporary-file-directory)))))
(default-directory temporary-file-directory))
- ;; We must check the remote LSP server. So far, just "clangd" is used.
- (unless (executable-find "clangd" 'remote)
- (ert-skip "Remote clangd not found"))
(funcall fn)))
(ert-deftest eglot-test-tramp-test ()