Skip to content

Commit 4e2ef42

Browse files
committed
Sync eglot.el and eglot-tests.el from upstream
Also update Makefile
1 parent db91d58 commit 4e2ef42

File tree

3 files changed

+773
-397
lines changed

3 files changed

+773
-397
lines changed

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ ELPADEPS ?=--eval '(setq package-user-dir (expand-file-name "elpa-eglot-test" te
3030
--eval '(install-latest (quote company))' \
3131
--eval '(install-latest (quote yasnippet))' \
3232
--eval '(install-latest (quote external-completion))'\
33-
--eval '(install-latest (quote flymake))'
33+
--eval '(install-latest (quote flymake))' \
34+
--eval '(install-latest (quote compat))' \
35+
--eval '(install-latest (quote track-changes))'
3436

3537
BYTECOMP_ERROR_ON_WARN := \
3638
--eval '(setq byte-compile-error-on-warn $(ERROR_ON_WARN))'
3739

3840
all: compile
3941

40-
# Compilation. Note BYTECOMP_ERROR_ON_WARN after ELPADEPS
42+
# Compilation. Note BYTECOMP_ERROR_ON_WARN after ELPADEPS
4143
# so deps can still warn on compilation.
4244
#
4345
%.elc: %.el

eglot-tests.el

Lines changed: 211 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;;; eglot-tests.el --- Tests for eglot.el -*- lexical-binding: t; -*-
22

3-
;; Copyright (C) 2018-2023 Free Software Foundation, Inc.
3+
;; Copyright (C) 2018-2025 Free Software Foundation, Inc.
44

55
;; Author: João Távora <joaotavora@gmail.com>
66
;; Keywords: tests
@@ -136,9 +136,11 @@ directory hierarchy."
136136
(jsonrpc-events-buffer server)))))
137137
(cond (noninteractive
138138
(dolist (buffer buffers)
139-
(eglot--test-message "contents of `%s':" (buffer-name buffer))
140-
(princ (with-current-buffer buffer (buffer-string))
141-
'external-debugging-output)))
139+
(eglot--test-message "contents of `%s' %S:" (buffer-name buffer) buffer)
140+
(if (buffer-live-p buffer)
141+
(princ (with-current-buffer buffer (buffer-string))
142+
'external-debugging-output)
143+
(princ "Killed\n" #'external-debugging-output))))
142144
(t
143145
(eglot--test-message "Preserved for inspection: %s"
144146
(mapconcat #'buffer-name buffers ", "))))))))
@@ -230,7 +232,7 @@ directory hierarchy."
230232
`(push message ,client-replies)))))))))
231233
(unwind-protect
232234
(progn
233-
(add-hook 'jsonrpc-event-hook #',log-event-hook-sym)
235+
(add-hook 'jsonrpc-event-hook #',log-event-hook-sym t)
234236
,@body)
235237
(remove-hook 'jsonrpc-event-hook #',log-event-hook-sym))))))
236238

@@ -436,6 +438,56 @@ directory hierarchy."
436438
(flymake-goto-next-error 1 '() t)
437439
(should (eq 'flymake-error (face-at-point)))))))
438440

441+
(ert-deftest eglot-test-basic-symlink ()
442+
"Test basic symlink support."
443+
(skip-unless (executable-find "clangd"))
444+
;; MS-Windows either fails symlink creation or pops up UAC prompts.
445+
(skip-when (eq system-type 'windows-nt))
446+
(eglot--with-fixture
447+
`(("symlink-project" .
448+
(("main.cpp" . "#include\"foo.h\"\nint main() { return foo(); }")
449+
("foo.h" . "int foo();"))))
450+
(with-current-buffer
451+
(find-file-noselect "symlink-project/main.cpp")
452+
(make-symbolic-link "main.cpp" "mainlink.cpp")
453+
(eglot--tests-connect)
454+
(eglot--sniffing (:client-notifications c-notifs)
455+
(let ((eglot-autoshutdown nil)) (kill-buffer (current-buffer)))
456+
(eglot--wait-for (c-notifs 10)
457+
(&key method &allow-other-keys)
458+
(and (string= method "textDocument/didClose")))))
459+
(eglot--sniffing (:client-notifications c-notifs)
460+
(with-current-buffer
461+
(find-file-noselect "symlink-project/main.cpp")
462+
(should (eglot-current-server)))
463+
(eglot--wait-for (c-notifs 10)
464+
(&rest whole &key params method &allow-other-keys)
465+
(and (string= method "textDocument/didOpen")
466+
(string-match "main.cpp$"
467+
(plist-get (plist-get params :textDocument)
468+
:uri)))))
469+
;; This last segment is deactivated, because it's likely not needed.
470+
;; The only way the server would answer with '3' references is if we
471+
;; had erroneously sent a 'didOpen' for anything other than
472+
;; `main.cpp', but if we got this far is because we've just asserted
473+
;; that we didn't.
474+
(when nil
475+
(with-current-buffer
476+
(find-file-noselect "symlink-project/foo.h")
477+
;; Give clangd some time to settle its analysis so it can
478+
;; accurately respond to `textDocument/references'
479+
(sleep-for 3)
480+
(search-forward "foo")
481+
(eglot--sniffing (:server-replies s-replies)
482+
(call-interactively 'xref-find-references)
483+
(eglot--wait-for (s-replies 10)
484+
(&key method result &allow-other-keys)
485+
;; Expect xref buffer to not contain duplicate references to
486+
;; main.cpp and mainlink.cpp. If it did, 'result's length
487+
;; would be 3.
488+
(and (string= method "textDocument/references")
489+
(= (length result) 2))))))))
490+
439491
(ert-deftest eglot-test-diagnostic-tags-unnecessary-code ()
440492
"Test rendering of diagnostics tagged \"unnecessary\"."
441493
(skip-unless (executable-find "clangd"))
@@ -537,6 +589,19 @@ directory hierarchy."
537589
(eglot--wait-for (s-notifs 20) (&key method &allow-other-keys)
538590
(string= method "textDocument/publishDiagnostics"))))
539591

592+
(defun eglot--wait-for-rust-analyzer ()
593+
(eglot--sniffing (:server-notifications s-notifs)
594+
(should (eglot--tests-connect))
595+
(eglot--wait-for (s-notifs 20) (&key method params &allow-other-keys)
596+
(and
597+
(string= method "$/progress")
598+
(equal (plist-get params :token) "rustAnalyzer/Roots Scanned")
599+
(equal (plist-get (plist-get params :value) :kind) "end")))
600+
;; Annoyingly, waiting for that special progress report is still not
601+
;; enough to make sure the server is ready to provide completions,
602+
;; so here's two extra seconds.
603+
(sit-for 2)))
604+
540605
(ert-deftest eglot-test-basic-completions ()
541606
"Test basic autocompletion in a clangd LSP."
542607
(skip-unless (executable-find "clangd"))
@@ -569,19 +634,125 @@ directory hierarchy."
569634
(forward-line -1)
570635
(should (looking-at "Complete, but not unique")))))))
571636

572-
(ert-deftest eglot-test-basic-xref ()
573-
"Test basic xref functionality in a clangd LSP."
637+
(ert-deftest eglot-test-stop-completion-on-nonprefix ()
638+
"Test completion also resulting in 'Complete, but not unique'."
639+
(skip-unless (executable-find "clangd"))
640+
(eglot--with-fixture
641+
`(("project" . (("coiso.c" .
642+
,(concat "int foot; int footer; int fo_obar;"
643+
"int main() {foo")))))
644+
(with-current-buffer
645+
(eglot--find-file-noselect "project/coiso.c")
646+
(eglot--wait-for-clangd)
647+
(goto-char (point-max))
648+
(completion-at-point)
649+
(should (looking-back "foo")))))
650+
651+
(defun eglot--kill-completions-buffer ()
652+
(when (buffer-live-p (get-buffer "*Completions*"))
653+
(kill-buffer "*Completions*")))
654+
655+
(ert-deftest eglot-test-try-completion-nomatch ()
656+
"Test completion table with non-matching input, returning nil."
657+
(skip-unless (executable-find "clangd"))
658+
(eglot--with-fixture
659+
`(("project" . (("coiso.c" .
660+
,(concat "int main() {abc")))))
661+
(with-current-buffer
662+
(eglot--find-file-noselect "project/coiso.c")
663+
(eglot--wait-for-clangd)
664+
(eglot--kill-completions-buffer)
665+
(goto-char (point-max))
666+
(completion-at-point)
667+
(should (looking-back "abc"))
668+
(should-not (get-buffer "*Completions*")))))
669+
670+
(ert-deftest eglot-test-try-completion-inside-symbol ()
671+
"Test completion table inside symbol, with only prefix matching."
672+
(skip-unless (executable-find "clangd"))
673+
(eglot--with-fixture
674+
`(("project" . (("coiso.c" .
675+
,(concat
676+
"int foobar;"
677+
"int foobarbaz;"
678+
"int main() {foo123")))))
679+
(with-current-buffer
680+
(eglot--find-file-noselect "project/coiso.c")
681+
(eglot--wait-for-clangd)
682+
(goto-char (- (point-max) 3))
683+
(eglot--kill-completions-buffer)
684+
(completion-at-point)
685+
(should (looking-back "foo"))
686+
(should (looking-at "123"))
687+
(should (get-buffer "*Completions*")))))
688+
689+
(ert-deftest eglot-test-try-completion-inside-symbol-2 ()
690+
"Test completion table inside symbol, with only prefix matching."
574691
(skip-unless (executable-find "clangd"))
575692
(eglot--with-fixture
576693
`(("project" . (("coiso.c" .
577-
,(concat "int foo=42; int fooey;"
578-
"int main() {foo=82;}")))))
694+
,(concat
695+
"int foobar;"
696+
"int main() {foo123")))))
579697
(with-current-buffer
580698
(eglot--find-file-noselect "project/coiso.c")
699+
(eglot--wait-for-clangd)
700+
(goto-char (- (point-max) 3))
701+
(completion-at-point)
702+
(should (looking-back "foobar"))
703+
(should (looking-at "123")))))
704+
705+
(ert-deftest eglot-test-rust-completion-exit-function ()
706+
"Ensure rust-analyzer exit function creates the expected contents."
707+
:tags '(:expensive-test)
708+
;; This originally appeared in github#1339
709+
(skip-unless (executable-find "rust-analyzer"))
710+
(skip-unless (executable-find "cargo"))
711+
(eglot--with-fixture
712+
'(("cmpl-project" .
713+
(("main.rs" .
714+
"fn test() -> i32 { let v: usize = 1; v.count_on1234.1234567890;"))))
715+
(with-current-buffer
716+
(eglot--find-file-noselect "cmpl-project/main.rs")
717+
(should (zerop (shell-command "cargo init")))
718+
(search-forward "v.count_on")
719+
(eglot--wait-for-rust-analyzer)
720+
(completion-at-point)
721+
(should
722+
(equal
723+
(if (bound-and-true-p yas-minor-mode)
724+
"fn test() -> i32 { let v: usize = 1; v.count_ones().1234567890;"
725+
"fn test() -> i32 { let v: usize = 1; v.count_ones.1234567890;")
726+
(buffer-string))))))
727+
728+
(ert-deftest eglot-test-zig-insert-replace-completion ()
729+
"Test zls's use of 'InsertReplaceEdit'."
730+
(skip-unless (functionp 'zig-ts-mode))
731+
(eglot--with-fixture
732+
`(("project" .
733+
(("main.zig" .
734+
,(concat "const Foo = struct {correct_name: u32,\n};\n"
735+
"fn example(foo: Foo) u32 {return foo.correc_name; }")))))
736+
(with-current-buffer
737+
(eglot--find-file-noselect "project/main.zig")
581738
(should (eglot--tests-connect))
582-
(search-forward "{foo")
583-
(call-interactively 'xref-find-definitions)
584-
(should (looking-at "foo=42")))))
739+
(search-forward "foo.correc")
740+
(completion-at-point)
741+
(should (looking-back "correct_name")))))
742+
743+
(ert-deftest eglot-test-basic-xref ()
744+
"Test basic xref functionality in a clangd LSP."
745+
(skip-unless (executable-find "clangd"))
746+
(eglot--with-fixture
747+
`(("project" . (("coiso.c" .
748+
,(concat "int foo=42; int fooey;"
749+
"int main() {foo=82;}")))))
750+
(with-current-buffer
751+
(eglot--find-file-noselect "project/coiso.c")
752+
(should (eglot--tests-connect))
753+
(search-forward "{foo")
754+
(call-interactively 'xref-find-definitions)
755+
(should (looking-at "foo=42")))))
585756

586757
(defvar eglot--test-c-buffer
587758
"\
@@ -710,6 +881,7 @@ int main() {
710881

711882
(ert-deftest eglot-test-javascript-basic ()
712883
"Test basic autocompletion in a JavaScript LSP."
884+
:tags '(:expensive-test)
713885
(skip-unless (and (executable-find "typescript-language-server")
714886
(executable-find "tsserver")))
715887
(eglot--with-fixture
@@ -724,14 +896,14 @@ int main() {
724896
:client-notifications
725897
c-notifs)
726898
(should (eglot--tests-connect))
727-
(eglot--wait-for (s-notifs 2) (&key method &allow-other-keys)
899+
(eglot--wait-for (s-notifs 10) (&key method &allow-other-keys)
728900
(string= method "textDocument/publishDiagnostics"))
729901
(should (not (eq 'flymake-error (face-at-point))))
730902
(insert "{")
731903
(eglot--signal-textDocument/didChange)
732904
(eglot--wait-for (c-notifs 1) (&key method &allow-other-keys)
733905
(string= method "textDocument/didChange"))
734-
(eglot--wait-for (s-notifs 2) (&key params method &allow-other-keys)
906+
(eglot--wait-for (s-notifs 10) (&key params method &allow-other-keys)
735907
(and (string= method "textDocument/publishDiagnostics")
736908
(cl-destructuring-bind (&key _uri diagnostics) params
737909
(cl-find-if (jsonrpc-lambda (&key severity &allow-other-keys)
@@ -821,6 +993,12 @@ int main() {
821993
(should (looking-back "\"foo.bar\": \""))
822994
(should (looking-at "fb\"$"))))))
823995

996+
(defun eglot-tests--get (object path)
997+
(dolist (op path)
998+
(setq object (if (natnump op) (aref object op)
999+
(plist-get object op))))
1000+
object)
1001+
8241002
(defun eglot-tests--lsp-abiding-column-1 ()
8251003
(eglot--with-fixture
8261004
'(("project" .
@@ -837,7 +1015,11 @@ int main() {
8371015
(insert "p ")
8381016
(eglot--signal-textDocument/didChange)
8391017
(eglot--wait-for (c-notifs 2) (&key params &allow-other-keys)
840-
(should (equal 71 (cadddr (cadadr (aref (cadddr params) 0))))))
1018+
(message "PARAMS=%S" params)
1019+
(should (equal 71 (eglot-tests--get
1020+
params
1021+
'(:contentChanges 0
1022+
:range :start :character)))))
8411023
(beginning-of-line)
8421024
(should (eq eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos))
8431025
(funcall eglot-move-to-linepos-function 71)
@@ -1175,8 +1357,8 @@ GUESSED-MAJOR-MODES-SYM are bound to the useful return values of
11751357
(let ((eglot-server-programs '(((baz-mode (foo-mode :language-id "bar"))
11761358
. ("prog-executable")))))
11771359
(eglot--guessing-contact (_ nil _ _ modes guessed-langs)
1178-
(should (equal guessed-langs '("bar" "baz")))
1179-
(should (equal modes '(foo-mode baz-mode)))))))
1360+
(should (equal guessed-langs '("baz" "bar")))
1361+
(should (equal modes '(baz-mode foo-mode)))))))
11801362

11811363
(defun eglot--glob-match (glob str)
11821364
(funcall (eglot--glob-compile glob t t) str))
@@ -1224,13 +1406,19 @@ GUESSED-MAJOR-MODES-SYM are bound to the useful return values of
12241406
;; (should (eglot--glob-match "{foo,bar}/**" "foo"))
12251407
;; (should (eglot--glob-match "{foo,bar}/**" "bar"))
12261408

1227-
;; VSCode also supports nested blobs. Do we care?
1409+
;; VSCode also supports nested blobs. Do we care? Apparently yes:
1410+
;; github#1403
12281411
;;
1229-
;; (should (eglot--glob-match "{**/*.d.ts,**/*.js}" "/testing/foo.js"))
1230-
;; (should (eglot--glob-match "{**/*.d.ts,**/*.js}" "testing/foo.d.ts"))
1231-
;; (should (eglot--glob-match "{**/*.d.ts,**/*.js,foo.[0-9]}" "foo.5"))
1232-
;; (should (eglot--glob-match "prefix/{**/*.d.ts,**/*.js,foo.[0-9]}" "prefix/foo.8"))
1233-
)
1412+
(should (eglot--glob-match "{**/*.d.ts,**/*.js}" "/testing/foo.js"))
1413+
(should (eglot--glob-match "{**/*.d.ts,**/*.js}" "testing/foo.d.ts"))
1414+
(should (eglot--glob-match "{**/*.d.ts,**/*.js,foo.[0-9]}" "foo.5"))
1415+
(should-not (eglot--glob-match "{**/*.d.ts,**/*.js,foo.[0-4]}" "foo.5"))
1416+
(should (eglot--glob-match "prefix/{**/*.d.ts,**/*.js,foo.[0-9]}"
1417+
"prefix/foo.8"))
1418+
(should (eglot--glob-match "prefix/{**/*.js,**/foo.[0-9]}.suffix"
1419+
"prefix/a/b/c/d/foo.5.suffix"))
1420+
(should (eglot--glob-match "prefix/{**/*.js,**/foo.[0-9]}.suffix"
1421+
"prefix/a/b/c/d/foo.js.suffix")))
12341422

12351423
(defvar tramp-histfile-override)
12361424
(defun eglot--call-with-tramp-test (fn)

0 commit comments

Comments
 (0)