all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
       [not found] ` <20221212001223.E9A9DC004BE@vcs2.savannah.gnu.org>
@ 2022-12-12  8:34   ` Stefan Kangas
  2022-12-12  9:52     ` João Távora
  2022-12-12 10:32   ` Michael Albinus
  2022-12-12 10:59   ` Michael Albinus
  2 siblings, 1 reply; 23+ messages in thread
From: Stefan Kangas @ 2022-12-12  8:34 UTC (permalink / raw)
  To: João Távora, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 503 bytes --]

João Távora <joaotavora@gmail.com> writes:

> branch: emacs-29
> commit 9c0d7bb73bb6a8d81b476d3fa497569c3061bdca
> Author: João Távora <joaotavora@gmail.com>
> Commit: João Távora <joaotavora@gmail.com>
>
>     Add automated tests for Eglot

I'm seeing some test failures:

SUMMARY OF TEST RESULTS
-----------------------
Files examined: 463
Ran 7070 tests, 6976 results as expected, 5 unexpected, 89 skipped
1 files contained unexpected results:
  lisp/progmodes/eglot-tests.log

[-- Attachment #2: eglot-tests.log --]
[-- Type: application/octet-stream, Size: 34320 bytes --]

Running 50 tests (2022-12-12 09:10:56+0100, selector ‘(not (or (tag :expensive-test) (tag :unstable)))’)
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode c-ts-mode c++-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (cena.c coiso.c merdix.c), wiping /tmp/eglot--fixtureKJKS9O, restoring nil
   passed   1/50  auto-detect-running-server (0.323126 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `project'.
[jsonrpc] Server exited with status 9
[eglot] (warning) Reconnecting after unexpected server exit.
Warning (eglot): Reconnecting after unexpected server exit.
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `project'.
[eglot] Reconnected!
[jsonrpc] Server exited with status 9
[eglot] (warning) Not auto-reconnecting, last one didn't last long.
Warning (eglot): Not auto-reconnecting, last one didn't last long.
[eglot] Test body was OK
[eglot] Killing (thingy.c), wiping /tmp/eglot--fixtureJWSU9J, restoring nil
   passed   2/50  auto-reconnect (2.348442 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `project'.
[eglot] Asking EGLOT (project/(c-mode c-ts-mode c++-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Test body was OK
[eglot] Killing nil, wiping /tmp/eglot--fixturerheXZq, restoring nil
   passed   3/50  auto-shutdown (0.141519 sec)
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Non-critical shutdown error after test: (jsonrpc-error "request id=7 failed:" (jsonrpc-error-message . "Timed out"))
[eglot] Killing (something.py), wiping /tmp/eglot--fixturehz1FzJ, restoring nil
   passed   4/50  basic-completions (5.267488 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `diag-project'.
[eglot] Waiting a bit...
[eglot] Event detected:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
	  (:diagnostics
	   [(:code "undeclared_var_use_suggest" :message "Use of undeclared identifier 'froat'; did you mean 'float'? (fix available)" :range
		   (:end
		    (:character 16 :line 0)
		    :start
		    (:character 11 :line 0))
		   :severity 1 :source "clang")]
	   :uri "file:///tmp/eglot--fixture7CbD51/diag-project/main.c" :version 0))


[eglot] Test body was OK
[eglot] Asking EGLOT (diag-project/(c-mode c-ts-mode c++-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (main.c), wiping /tmp/eglot--fixture7CbD51, restoring nil
   passed   5/50  basic-diagnostics (0.342727 sec)
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (something.py), wiping /tmp/eglot--fixturepkAN9D, restoring nil
   passed   6/50  basic-xref (1.731290 sec)
/bin/bash: line 1: cargo: command not found
[eglot] Test body was A FAILURE
[eglot] Killing (main.rs), wiping /tmp/eglot--fixtureEUaOvq, restoring nil
Test diagnostic-tags-unnecessary-code backtrace:
  signal(ert-test-failed (((should (zerop (shell-command "cargo init")
  ert-fail(((should (zerop (shell-command "cargo init"))) :form (= 0 1
  #f(compiled-function () #<bytecode -0x8a8f76411610a21>)()
  eglot--call-with-fixture((("diagnostic-tag-project" ("main.rs" . "fn
  #f(compiled-function () #<bytecode -0x1f963e3a32b08def>)()
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name diagnostic-tags-unnecessary-code :doc
  ert-run-or-rerun-test(#s(ert--stats :selector ... :tests ... :test-m
  ert-run-tests((not (or (tag :expensive-test) (tag :unstable))) #f(co
  ert-run-tests-batch((not (or (tag :expensive-test) (tag :unstable)))
  ert-run-tests-batch-and-exit((not (or (tag :expensive-test) (tag :un
  command-line-1(("-L" ":." "-l" "ert" "-l" "lisp/progmodes/eglot-test
  command-line()
  normal-top-level()
Test diagnostic-tags-unnecessary-code condition:
    (ert-test-failed
     ((should
       (zerop
	(shell-command "cargo init")))
      :form
      (= 0 127)
      :value nil))
   FAILED   7/50  diagnostic-tags-unnecessary-code (0.008564 sec) at lisp/progmodes/eglot-tests.el:431
  skipped   8/50  eclipse-connect (0.000291 sec)
   passed   9/50  eglot--glob-test (0.018594 sec)
  skipped  10/50  eglot--path-to-uri-windows (0.000083 sec)
[eglot] Connected! Server `clangd' now managing `(c++-mode c-mode c-ts-mode c++-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c++-mode c-mode c-ts-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (foo.cpp foolib.h foolib.c), wiping /tmp/eglot--fixture7AHwRT, restoring nil
   passed  11/50  eglot--same-server-multi-mode (0.148467 sec)
Tramp: Sending command ‘exec /bin/sh’
Tramp: Found remote shell prompt on ‘joffe’


Tramp: Sending command ‘exec /bin/sh’
Tramp: Found remote shell prompt on ‘joffe’
Tramp: Sending command ‘exec /bin/sh’
Tramp: Found remote shell prompt on ‘joffe’
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `project'.




[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode c-ts-mode c++-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (cena.c coiso.c merdix.c), wiping /loopback:joffe:/tmp/eglot--fixtureEsbEW8, restoring nil
   passed  12/50  eglot--tramp-test (0.863837 sec)
Tramp: Sending command ‘exec /bin/sh’
Tramp: Found remote shell prompt on ‘joffe’


Tramp: Sending command ‘exec /bin/sh’
Tramp: Found remote shell prompt on ‘joffe’
Tramp: Sending command ‘exec /bin/sh’
Tramp: Found remote shell prompt on ‘joffe’
[eglot] Connected! Server `clangd' now managing `(c-mode)' buffers in project `project'.
[eglot] Event detected:
(:jsonrpc "2.0" :method "textDocument/didChange" :params
	  (:textDocument
	   (:uri "file:///tmp/eglot--fixtureqeONNk/project/foo.c" :version 1)
	   :contentChanges
	   [(:range
	     (:start
	      (:line 0 :character 71)
	      :end
	      (:line 0 :character 71))
	     :rangeLength 0 :text "p ")]))

[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (foo.c), wiping /loopback:joffe:/tmp/eglot--fixtureqeONNk, restoring nil
File is missing: /loopback:joffe:/tmp/eglot--fixtureqeONNk/project/foo.c~
   passed  13/50  eglot--tramp-test-2 (0.476373 sec)
   passed  14/50  eglot-capabilities (0.000094 sec)
   passed  15/50  eglot-dcase (0.000098 sec)
   passed  16/50  eglot-dcase-issue-452 (0.000081 sec)
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Non-critical shutdown error after test: (jsonrpc-error "request id=10 failed:" (jsonrpc-error-message . "Timed out"))
[eglot] Killing (something.py), wiping /tmp/eglot--fixtureAgUev4, restoring nil
   passed  17/50  eglot-eldoc-after-completions (5.346342 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode c-ts-mode c++-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (foo.c bar.c), wiping /tmp/eglot--fixture6ETUlV, restoring (c-mode-hook)
   passed  18/50  eglot-ensure (0.262567 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode)' buffers in project `project'.
[eglot] Event detected:
(:jsonrpc "2.0" :method "textDocument/didChange" :params
	  (:textDocument
	   (:uri "file:///tmp/eglot--fixtureg0Yjoa/project/foo.c" :version 1)
	   :contentChanges
	   [(:range
	     (:start
	      (:line 0 :character 71)
	      :end
	      (:line 0 :character 71))
	     :rangeLength 0 :text "p ")]))

[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (foo.c), wiping /tmp/eglot--fixtureg0Yjoa, restoring nil
   passed  19/50  eglot-lsp-abiding-column (0.141156 sec)
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (hover-first.py), wiping /tmp/eglot--fixtureQdnauc, restoring nil
   passed  20/50  eglot-multiline-eldoc (3.299877 sec)
   passed  21/50  eglot-server-programs-class-name-and-contact-spec (0.002112 sec)
   passed  22/50  eglot-server-programs-class-name-and-plist (0.000214 sec)
   passed  23/50  eglot-server-programs-executable-multiple-major-modes (0.000197 sec)
   passed  24/50  eglot-server-programs-executable-with-arg (0.000180 sec)
   passed  25/50  eglot-server-programs-executable-with-args-and-autoport (0.000169 sec)
   passed  26/50  eglot-server-programs-function (0.000186 sec)
   passed  27/50  eglot-server-programs-guess-lang (0.000298 sec)
   passed  28/50  eglot-server-programs-host-and-port (0.000161 sec)
   passed  29/50  eglot-server-programs-host-and-port-and-tcp-args (0.000168 sec)
   passed  30/50  eglot-server-programs-simple-executable (0.000180 sec)
   passed  31/50  eglot-server-programs-simple-missing-executable (0.000194 sec)
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (hover-first.py), wiping /tmp/eglot--fixtureOnXekx, restoring nil
   passed  32/50  eglot-single-line-eldoc (2.559164 sec)
   passed  33/50  eglot-strict-interfaces (0.000322 sec)
[jsonrpc] Server exited with status 9
[eglot] Test body was A FAILURE
[eglot]  *EGLOT (project/(js-mode)) output*:
[eglot]  *EGLOT (project/(js-mode)) stderr*:

Process EGLOT (project/(js-mode)) stderr finished
[eglot] *EGLOT (project/(js-mode)) events*:
[internal] Mon Dec 12 09:11:19 2022:
(:message "Running language server: typescript-language-server --stdio")
[client-request] (id:1) Mon Dec 12 09:11:19 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
	  (:processId 1771677 :rootPath "/tmp/eglot--fixtureb788i9/project/" :rootUri "file:///tmp/eglot--fixtureb788i9/project" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
																				      ())
		      :capabilities
		      (:workspace
		       (:applyEdit t :executeCommand
				   (:dynamicRegistration :json-false)
				   :workspaceEdit
				   (:documentChanges t)
				   :didChangeWatchedFiles
				   (:dynamicRegistration t)
				   :symbol
				   (:dynamicRegistration :json-false)
				   :configuration t :workspaceFolders t)
		       :textDocument
		       (:synchronization
			(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
			:completion
			(:dynamicRegistration :json-false :completionItem
					      (:snippetSupport :json-false :deprecatedSupport t :resolveSupport
							       (:properties
								["documentation" "details" "additionalTextEdits"])
							       :tagSupport
							       (:valueSet
								[1]))
					      :contextSupport t)
			:hover
			(:dynamicRegistration :json-false :contentFormat
					      ["plaintext"])
			:signatureHelp
			(:dynamicRegistration :json-false :signatureInformation
					      (:parameterInformation
					       (:labelOffsetSupport t)
					       :activeParameterSupport t))
			:references
			(:dynamicRegistration :json-false)
			:definition
			(:dynamicRegistration :json-false :linkSupport t)
			:declaration
			(:dynamicRegistration :json-false :linkSupport t)
			:implementation
			(:dynamicRegistration :json-false :linkSupport t)
			:typeDefinition
			(:dynamicRegistration :json-false :linkSupport t)
			:documentSymbol
			(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
					      (:valueSet
					       [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
			:documentHighlight
			(:dynamicRegistration :json-false)
			:codeAction
			(:dynamicRegistration :json-false :codeActionLiteralSupport
					      (:codeActionKind
					       (:valueSet
						["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
					      :isPreferredSupport t)
			:formatting
			(:dynamicRegistration :json-false)
			:rangeFormatting
			(:dynamicRegistration :json-false)
			:rename
			(:dynamicRegistration :json-false)
			:publishDiagnostics
			(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
					     (:valueSet
					      [1 2])))
		       :experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
						   ()))
		      :workspaceFolders
		      [(:uri "file:///tmp/eglot--fixtureb788i9/project" :name "/tmp/eglot--fixtureb788i9/project/")]))
[server-reply] (id:1) ERROR Mon Dec 12 09:11:22 2022:
(:jsonrpc "2.0" :id 1 :error
	  (:code -32603 :message "Request initialize failed with message: Could not find a valid tsserver version. Exiting."))
[internal] Mon Dec 12 09:11:22 2022:
(:message "Connection state changed" :change "killed\n")

----------b---y---e---b---y---e----------
[stderr] 
[stderr] 
[stderr] nil
[stderr] nil
[stderr] Process EGLOT (project/(js-mode)) stderr finished
[eglot] Killing (hello.js), wiping /tmp/eglot--fixtureb788i9, restoring nil
Test javascript-basic backtrace:
  error("[eglot] %s" "-32603: Request initialize failed with message: 
  eglot--error("-32603: Request initialize failed with message: Co..."
  eglot--connect((js-mode) (transient . "/tmp/eglot--fixtureb788i9/pro
  apply(eglot--connect ((js-mode) (transient . "/tmp/eglot--fixtureb78
  eglot--tests-connect()
  apply(eglot--tests-connect nil)
  #f(compiled-function () #<bytecode 0xe5c8480dcb27b>)()
  eglot--call-with-fixture((("project" ("hello.js" . "console.log('Hel
  #f(compiled-function () #<bytecode 0xf760de60d51bd60>)()
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name javascript-basic :documentation "Test
  ert-run-or-rerun-test(#s(ert--stats :selector ... :tests ... :test-m
  ert-run-tests((not (or (tag :expensive-test) (tag :unstable))) #f(co
  ert-run-tests-batch((not (or (tag :expensive-test) (tag :unstable)))
  ert-run-tests-batch-and-exit((not (or (tag :expensive-test) (tag :un
  command-line-1(("-L" ":." "-l" "ert" "-l" "lisp/progmodes/eglot-test
  command-line()
  normal-top-level()
Test javascript-basic condition:
    (error "[eglot] -32603: Request initialize failed with message: Could not find a valid tsserver version. Exiting.")
   FAILED  34/50  javascript-basic (3.135755 sec) at lisp/progmodes/eglot-tests.el:723
  skipped  35/50  json-basic (0.000317 sec)
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
Complete, but not unique

[eglot] Test body was OK
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (something.py), wiping /tmp/eglot--fixturej5dyPN, restoring nil
   passed  36/50  non-unique-completions (3.915684 sec)
/bin/bash: line 1: cargo: command not found
[eglot] Test body was A FAILURE
[eglot] Killing (other-file.rs), wiping /tmp/eglot--fixtureCVZOqZ, restoring nil
Test project-wide-diagnostics-rust-analyzer backtrace:
  signal(ert-test-failed (((should (zerop (shell-command "cargo init")
  ert-fail(((should (zerop (shell-command "cargo init"))) :form (= 0 1
  #f(compiled-function () #<bytecode -0xd049d4dc592f371>)()
  eglot--call-with-fixture((("project" ("main.rs" . "fn main() -> () {
  #f(compiled-function () #<bytecode -0x1f963e3a3d43edef>)()
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name project-wide-diagnostics-rust-analyze
  ert-run-or-rerun-test(#s(ert--stats :selector ... :tests ... :test-m
  ert-run-tests((not (or (tag :expensive-test) (tag :unstable))) #f(co
  ert-run-tests-batch((not (or (tag :expensive-test) (tag :unstable)))
  ert-run-tests-batch-and-exit((not (or (tag :expensive-test) (tag :un
  command-line-1(("-L" ":." "-l" "ert" "-l" "lisp/progmodes/eglot-test
  command-line()
  normal-top-level()
Test project-wide-diagnostics-rust-analyzer condition:
    (ert-test-failed
     ((should
       (zerop
	(shell-command "cargo init")))
      :form
      (= 0 127)
      :value nil))
   FAILED  37/50  project-wide-diagnostics-rust-analyzer (0.016784 sec) at lisp/progmodes/eglot-tests.el:780
[jsonrpc] Server exited with status 9
[eglot] Test body was A FAILURE
[eglot]  *EGLOT (eglot--fixtureUsREZP/(typescript-mode)) output*:
[eglot]  *EGLOT (eglot--fixtureUsREZP/(typescript-mode)) stderr*:

Process EGLOT (eglot--fixtureUsREZP/(typescript-mode)) stderr finished
[eglot] *EGLOT (eglot--fixtureUsREZP/(typescript-mode)) events*:
[internal] Mon Dec 12 09:11:27 2022:
(:message "Running language server: typescript-language-server --stdio")
[client-request] (id:1) Mon Dec 12 09:11:27 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
	  (:processId 1771677 :rootPath "/tmp/eglot--fixtureUsREZP/" :rootUri "file:///tmp/eglot--fixtureUsREZP" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
																		      ())
		      :capabilities
		      (:workspace
		       (:applyEdit t :executeCommand
				   (:dynamicRegistration :json-false)
				   :workspaceEdit
				   (:documentChanges t)
				   :didChangeWatchedFiles
				   (:dynamicRegistration t)
				   :symbol
				   (:dynamicRegistration :json-false)
				   :configuration t :workspaceFolders t)
		       :textDocument
		       (:synchronization
			(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
			:completion
			(:dynamicRegistration :json-false :completionItem
					      (:snippetSupport :json-false :deprecatedSupport t :resolveSupport
							       (:properties
								["documentation" "details" "additionalTextEdits"])
							       :tagSupport
							       (:valueSet
								[1]))
					      :contextSupport t)
			:hover
			(:dynamicRegistration :json-false :contentFormat
					      ["plaintext"])
			:signatureHelp
			(:dynamicRegistration :json-false :signatureInformation
					      (:parameterInformation
					       (:labelOffsetSupport t)
					       :activeParameterSupport t))
			:references
			(:dynamicRegistration :json-false)
			:definition
			(:dynamicRegistration :json-false :linkSupport t)
			:declaration
			(:dynamicRegistration :json-false :linkSupport t)
			:implementation
			(:dynamicRegistration :json-false :linkSupport t)
			:typeDefinition
			(:dynamicRegistration :json-false :linkSupport t)
			:documentSymbol
			(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
					      (:valueSet
					       [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
			:documentHighlight
			(:dynamicRegistration :json-false)
			:codeAction
			(:dynamicRegistration :json-false :codeActionLiteralSupport
					      (:codeActionKind
					       (:valueSet
						["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
					      :isPreferredSupport t)
			:formatting
			(:dynamicRegistration :json-false)
			:rangeFormatting
			(:dynamicRegistration :json-false)
			:rename
			(:dynamicRegistration :json-false)
			:publishDiagnostics
			(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
					     (:valueSet
					      [1 2])))
		       :experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
						   ()))
		      :workspaceFolders
		      [(:uri "file:///tmp/eglot--fixtureUsREZP" :name "/tmp/eglot--fixtureUsREZP/")]))
[server-reply] (id:1) ERROR Mon Dec 12 09:11:27 2022:
(:jsonrpc "2.0" :id 1 :error
	  (:code -32603 :message "Request initialize failed with message: Could not find a valid tsserver version. Exiting."))
[internal] Mon Dec 12 09:11:27 2022:
(:message "Connection state changed" :change "killed\n")

----------b---y---e---b---y---e----------
[stderr] 
[stderr] 
[stderr] nil
[stderr] nil
[stderr] Process EGLOT (eglot--fixtureUsREZP/(typescript-mode)) stderr finished
[eglot] Killing (hello2.ts), wiping /tmp/eglot--fixtureUsREZP, restoring nil
Test project-wide-diagnostics-typescript backtrace:
  error("[eglot] %s" "-32603: Request initialize failed with message: 
  eglot--error("-32603: Request initialize failed with message: Co..."
  eglot--connect((typescript-mode) (vc Git "/tmp/eglot--fixtureUsREZP/
  apply(eglot--connect ((typescript-mode) (vc Git "/tmp/eglot--fixture
  eglot--tests-connect()
  #f(compiled-function () #<bytecode 0x5d937f73c973338>)()
  eglot--call-with-fixture((("project" ("hello.ts" . "const thing = 5;
  #f(compiled-function () #<bytecode 0xf760de60d521160>)()
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name project-wide-diagnostics-typescript :
  ert-run-or-rerun-test(#s(ert--stats :selector ... :tests ... :test-m
  ert-run-tests((not (or (tag :expensive-test) (tag :unstable))) #f(co
  ert-run-tests-batch((not (or (tag :expensive-test) (tag :unstable)))
  ert-run-tests-batch-and-exit((not (or (tag :expensive-test) (tag :un
  command-line-1(("-L" ":." "-l" "ert" "-l" "lisp/progmodes/eglot-test
  command-line()
  normal-top-level()
Test project-wide-diagnostics-typescript condition:
    (error "[eglot] -32603: Request initialize failed with message: Could not find a valid tsserver version. Exiting.")
   FAILED  38/50  project-wide-diagnostics-typescript (0.478075 sec) at lisp/progmodes/eglot-tests.el:752
[eglot] Connected! Server `pylsp' now managing `(python-mode python-ts-mode)' buffers in project `project'.
[eglot] applying 1 edits to `something.py'... 
[eglot] applying 1 edits to `something.py'...done
[eglot] Test body was A FAILURE
[eglot] Asking EGLOT (project/(python-mode python-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot]  *EGLOT (project/(python-mode python-ts-mode)) output*:
[eglot]  *EGLOT (project/(python-mode python-ts-mode)) stderr*:
2022-12-12 09:11:28,534 CET - WARNING - pylsp.config.config - Failed to load pylsp entry point 'yapf': No module named 'whatthepatch'
2022-12-12 09:11:28,645 CET - WARNING - matplotlib - Matplotlib created a temporary config/cache directory at /tmp/matplotlib-4cjuat2i because the default path (/dev/null/matplotlib) is not a writable directory; it is highly recommended to set the MPLCONFIGDIR environment variable to a writable directory, in particular to speed up the import of Matplotlib and to better support multiprocessing.

Process EGLOT (project/(python-mode python-ts-mode)) stderr finished
[eglot] *EGLOT (project/(python-mode python-ts-mode)) events*:
[internal] Mon Dec 12 09:11:27 2022:
(:message "Running language server: /bin/pylsp")
[client-request] (id:1) Mon Dec 12 09:11:27 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
	  (:processId 1771677 :rootPath "/tmp/eglot--fixture2r4nUz/project/" :rootUri "file:///tmp/eglot--fixture2r4nUz/project" :initializationOptions #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
																				      ())
		      :capabilities
		      (:workspace
		       (:applyEdit t :executeCommand
				   (:dynamicRegistration :json-false)
				   :workspaceEdit
				   (:documentChanges t)
				   :didChangeWatchedFiles
				   (:dynamicRegistration t)
				   :symbol
				   (:dynamicRegistration :json-false)
				   :configuration t :workspaceFolders t)
		       :textDocument
		       (:synchronization
			(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
			:completion
			(:dynamicRegistration :json-false :completionItem
					      (:snippetSupport :json-false :deprecatedSupport t :resolveSupport
							       (:properties
								["documentation" "details" "additionalTextEdits"])
							       :tagSupport
							       (:valueSet
								[1]))
					      :contextSupport t)
			:hover
			(:dynamicRegistration :json-false :contentFormat
					      ["plaintext"])
			:signatureHelp
			(:dynamicRegistration :json-false :signatureInformation
					      (:parameterInformation
					       (:labelOffsetSupport t)
					       :activeParameterSupport t))
			:references
			(:dynamicRegistration :json-false)
			:definition
			(:dynamicRegistration :json-false :linkSupport t)
			:declaration
			(:dynamicRegistration :json-false :linkSupport t)
			:implementation
			(:dynamicRegistration :json-false :linkSupport t)
			:typeDefinition
			(:dynamicRegistration :json-false :linkSupport t)
			:documentSymbol
			(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
					      (:valueSet
					       [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
			:documentHighlight
			(:dynamicRegistration :json-false)
			:codeAction
			(:dynamicRegistration :json-false :codeActionLiteralSupport
					      (:codeActionKind
					       (:valueSet
						["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
					      :isPreferredSupport t)
			:formatting
			(:dynamicRegistration :json-false)
			:rangeFormatting
			(:dynamicRegistration :json-false)
			:rename
			(:dynamicRegistration :json-false)
			:publishDiagnostics
			(:relatedInformation :json-false :codeDescriptionSupport :json-false :tagSupport
					     (:valueSet
					      [1 2])))
		       :experimental #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
						   ()))
		      :workspaceFolders
		      [(:uri "file:///tmp/eglot--fixture2r4nUz/project" :name "/tmp/eglot--fixture2r4nUz/project/")]))
[stderr] 2022-12-12 09:11:28,534 CET - WARNING - pylsp.config.config - Failed to load pylsp entry point 'yapf': No module named 'whatthepatch'
[stderr] 2022-12-12 09:11:28,645 CET - WARNING - matplotlib - Matplotlib created a temporary config/cache directory at /tmp/matplotlib-4cjuat2i because the default path (/dev/null/matplotlib) is not a writable directory; it is highly recommended to set the MPLCONFIGDIR environment variable to a writable directory, in particular to speed up the import of Matplotlib and to better support multiprocessing.
[server-reply] (id:1) Mon Dec 12 09:11:28 2022:
(:jsonrpc "2.0" :id 1 :result
	  (:capabilities
	   (:codeActionProvider t :codeLensProvider
				(:resolveProvider :json-false)
				:completionProvider
				(:resolveProvider t :triggerCharacters
						  ["."])
				:documentFormattingProvider t :documentHighlightProvider t :documentRangeFormattingProvider t :documentSymbolProvider t :definitionProvider t :executeCommandProvider
				(:commands
				 [])
				:hoverProvider t :referencesProvider t :renameProvider t :foldingRangeProvider t :signatureHelpProvider
				(:triggerCharacters
				 ["(" "," "="])
				:textDocumentSync
				(:change 2 :save
					 (:includeText t)
					 :openClose t)
				:workspace
				(:workspaceFolders
				 (:supported t :changeNotifications t))
				:experimental nil)
	   :serverInfo
	   (:name "pylsp" :version "1.5.0")))
[client-notification] Mon Dec 12 09:11:28 2022:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
							    ()))
[client-notification] Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
	  (:textDocument
	   (:uri "file:///tmp/eglot--fixture2r4nUz/project/something.py" :version 0 :languageId "python" :text "def a():pass\n\ndef b():pass")))
[client-notification] Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
	  (:settings #s(hash-table size 1 test eql rehash-size 1.5 rehash-threshold 0.8125 data
				   ())))
[client-request] (id:2) Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :id 2 :method "textDocument/rangeFormatting" :params
	  (:textDocument
	   (:uri "file:///tmp/eglot--fixture2r4nUz/project/something.py")
	   :options
	   (:tabSize 8 :insertSpaces t :insertFinalNewline t :trimFinalNewlines t)
	   :range
	   (:start
	    (:line 2 :character 0)
	    :end
	    (:line 2 :character 12))))
[server-reply] (id:2) Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :id 2 :result
	  [(:range
	    (:start
	     (:line 0 :character 0)
	     :end
	     (:line 3 :character 0))
	    :newText "def a():pass\n\ndef b(): pass")])
[client-request] (id:3) Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :id 3 :method "shutdown" :params nil)
[server-reply] (id:3) Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :id 3 :result nil)
[client-notification] Mon Dec 12 09:11:29 2022:
(:jsonrpc "2.0" :method "exit" :params nil)
[internal] Mon Dec 12 09:11:29 2022:
(:message "Connection state changed" :change "killed\n")

----------b---y---e---b---y---e----------
[stderr] 
[stderr] 
[stderr] nil
[stderr] nil
[stderr] Process EGLOT (project/(python-mode python-ts-mode)) stderr finished
[eglot] Killing (something.py), wiping /tmp/eglot--fixture2r4nUz, restoring nil
Test python-autopep-formatting backtrace:
  signal(ert-test-failed (((should (string= (buffer-string) "def a():p
  ert-fail(((should (string= (buffer-string) "def a():pass\n\n\ndef b(
  #f(compiled-function () #<bytecode 0x100b52a9229872e>)()
  eglot--call-with-fixture((("project" ("something.py" . "def a():pass
  #f(compiled-function () #<bytecode 0x1521b0156d1dc18d>)()
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name python-autopep-formatting :documentat
  ert-run-or-rerun-test(#s(ert--stats :selector ... :tests ... :test-m
  ert-run-tests((not (or (tag :expensive-test) (tag :unstable))) #f(co
  ert-run-tests-batch((not (or (tag :expensive-test) (tag :unstable)))
  ert-run-tests-batch-and-exit((not (or (tag :expensive-test) (tag :un
  command-line-1(("-L" ":." "-l" "ert" "-l" "lisp/progmodes/eglot-test
  command-line()
  normal-top-level()
Test python-autopep-formatting condition:
    (ert-test-failed
     ((should
       (string=
	(buffer-string)
	"def a():pass\n\n\ndef b(): pass\n"))
      :form
      (string= "def a():pass\n\ndef b(): pass" "def a():pass\n\n\ndef b(): pass\n")
      :value nil :explanation
      (arrays-of-different-length 27 29 "def a():pass\n\ndef b(): pass" "def a():pass\n\n\ndef b(): pass\n" first-mismatch-at 14)))
   FAILED  39/50  python-autopep-formatting (1.429933 sec) at lisp/progmodes/eglot-tests.el:658
  skipped  40/50  python-yapf-formatting (0.000162 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode c-ts-mode c++-mode c++-ts-mode)' buffers in project `rename-project'.
[eglot] applying 2 edits to `main.c'... 
[eglot] applying 2 edits to `main.c'...done
[eglot] Edit successful!
[eglot] Test body was OK
[eglot] Asking EGLOT (rename-project/(c-mode c-ts-mode c++-mode c++-ts-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (main.c), wiping /tmp/eglot--fixture4RV9iR, restoring nil
   passed  41/50  rename-a-symbol (0.155775 sec)
  skipped  42/50  rust-analyzer-hover-after-edit (0.000675 sec)
  skipped  43/50  rust-analyzer-watches-files (0.000644 sec)
  skipped  44/50  rust-on-type-formatting (0.000667 sec)
[eglot] Waiting in background for server `EGLOT (project/(c-mode))'
[eglot] Connected! Server `clangd' now managing `(c-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (something.c), wiping /tmp/eglot--fixture4zQ4os, restoring nil
   passed  45/50  slow-async-connection (2.216443 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (something.c), wiping /tmp/eglot--fixturehDDt2e, restoring nil
   passed  46/50  slow-sync-connection-intime (1.159687 sec)
[eglot] Connected! Server `clangd' now managing `(c-mode)' buffers in project `project'.
[eglot] Test body was OK
[eglot] Asking EGLOT (project/(c-mode)) politely to terminate
[jsonrpc] Server exited with status 9
[eglot] Killing (something.c), wiping /tmp/eglot--fixtureZh52RI, restoring nil
   passed  47/50  slow-sync-connection-wait (1.156091 sec)
[jsonrpc] Server exited with status 9
[eglot] Test body was OK
[eglot] Killing (something.c), wiping /tmp/eglot--fixtureEIjSp4, restoring nil
   passed  48/50  slow-sync-timeout (1.129866 sec)
  skipped  49/50  snippet-completions (0.000305 sec)
  skipped  50/50  snippet-completions-with-company (0.000267 sec)

Ran 50 tests, 36 results as expected, 5 unexpected, 9 skipped (2022-12-12 09:11:35+0100, 38.597245 sec)

5 unexpected results:
   FAILED  diagnostic-tags-unnecessary-code
   FAILED  javascript-basic
   FAILED  project-wide-diagnostics-rust-analyzer
   FAILED  project-wide-diagnostics-typescript
   FAILED  python-autopep-formatting

9 skipped results:
  SKIPPED  eclipse-connect
  SKIPPED  eglot--path-to-uri-windows
  SKIPPED  json-basic
  SKIPPED  python-yapf-formatting
  SKIPPED  rust-analyzer-hover-after-edit
  SKIPPED  rust-analyzer-watches-files
  SKIPPED  rust-on-type-formatting
  SKIPPED  snippet-completions
  SKIPPED  snippet-completions-with-company


^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12  8:34   ` emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot Stefan Kangas
@ 2022-12-12  9:52     ` João Távora
  2022-12-12 11:26       ` Stefan Kangas
  0 siblings, 1 reply; 23+ messages in thread
From: João Távora @ 2022-12-12  9:52 UTC (permalink / raw)
  To: Stefan Kangas; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1476 bytes --]

Hi Stefan,

Are you seeing these failures on Emacs's CI infrastructure (EMBA,
If I'm not mistaken), or on your own machine? Tests run fine on
my machine.

I'm going to assume this was on your machine from now on.

It seems you have some language servers and language
toolchains installed but that, for some reason, they are not behaving
as they should.

In tests such as 'project-wide-diagnostics-rust-analyzer' an additional
"skip-unless"  check for "cargo" was missing.  I've now fixed that.  Can you
explain why you have a "rust-analyzer" executable, but not "cargo"? Is
this a usual Rust setup?

In other tests, such as 'javascript-basic',
'project-wide-diagnostics-typescript',
the error is strange:

(error "[eglot] -32603: Request initialize failed with message:
  Could not find a valid tsserver version. Exiting.")

I don't know what this can mean. Again, it seems you have
"typescript-language-server", but somehow "no valid tsserver".

In general, I don't know if it's a good idea to keep these tests in the
normal 'make check' run, since many Eglot tests are dependent
on having correctly working servers.  Or at least what I and other test
authors believe to be "correctly configured".  Which is generally the
simplest installation method for said servers, with the fewest bells
and whistles.

Let me know if you've investigated your setup for these servers (again,
I'm assuming this is on your machine and not EMBA).

João

[-- Attachment #2: Type: text/html, Size: 1781 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
       [not found] ` <20221212001223.E9A9DC004BE@vcs2.savannah.gnu.org>
  2022-12-12  8:34   ` emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot Stefan Kangas
@ 2022-12-12 10:32   ` Michael Albinus
  2022-12-12 10:37     ` João Távora
  2022-12-12 10:59   ` Michael Albinus
  2 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-12 10:32 UTC (permalink / raw)
  To: emacs-devel; +Cc: João Távora

João Távora <joaotavora@gmail.com> writes:

Hi João,

> +(require 'tramp)
> +(require 'tramp-sh)

Do you really need this?
> +(require 'subr-x)
> +(require 'flymake) ; project-diagnostics
> +
> +;;; Helpers
> +
> +(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)
> +to create a readable FILE with CONTENT.  FILE may be a directory
> +name and CONTENT another (FILE . CONTENT) list to specify a
> +directory hierarchy.  FIXTURE's elements can also be (SYMBOL
> +VALUE) meaning SYMBOL should be bound to VALUE during BODY and
> +then restored."
> +  (declare (indent 1) (debug t))
> +  `(eglot--call-with-fixture
> +    ,fixture #'(lambda () ,@body)))
> +
> +(defun eglot--make-file-or-dir (ass)
> +  (let ((file-or-dir-name (car ass))
> +        (content (cdr ass)))
> +    (cond ((listp content)
> +           (make-directory file-or-dir-name 'parents)
> +           (let ((default-directory (concat default-directory "/" file-or-dir-name)))
> +             (mapcan #'eglot--make-file-or-dir content)))
> +          ((stringp content)
> +           (with-temp-buffer
> +             (insert content)
> +             (write-region nil nil file-or-dir-name nil 'nomessage))
> +           (list (expand-file-name file-or-dir-name)))
> +          (t
> +           (eglot--error "Expected a string or a directory spec")))))
> +
> +(defun eglot--call-with-fixture (fixture fn)
> +  "Helper for `eglot--with-fixture'.  Run FN under FIXTURE."
> +  (let* ((fixture-directory (make-temp-file "eglot--fixture" t))
> +         (default-directory fixture-directory)
> +         file-specs created-files
> +         syms-to-restore
> +         new-servers
> +         test-body-successful-p)
> +    (dolist (spec fixture)
> +      (cond ((symbolp spec)
> +             (push (cons spec (symbol-value spec)) syms-to-restore)
> +             (set spec nil))
> +            ((symbolp (car spec))
> +             (push (cons (car spec) (symbol-value (car spec))) syms-to-restore)
> +             (set (car spec) (cadr spec)))
> +            ((stringp (car spec)) (push spec file-specs))))
> +    (unwind-protect
> +        (let* ((home (getenv "HOME"))
> +               (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.
> +                   ,(format "HOME=%s"
> +                            (if (file-exists-p home) home
> +                              (format "/home/%s" (getenv "USER")))))
> +                 process-environment))
> +               ;; Prevent "Can't guess python-indent-offset ..." messages.
> +               (python-indent-guess-indent-offset-verbose . nil)
> +               (eglot-server-initialized-hook
> +                (lambda (server) (push server new-servers))))
> +          (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"))
> +      (unwind-protect
> +          (let ((eglot-autoreconnect nil))
> +            (dolist (server new-servers)
> +              (when (jsonrpc-running-p server)
> +                (condition-case oops
> +                    (eglot-shutdown
> +                     server nil 3 (not test-body-successful-p))
> +                  (error
> +                   (eglot--message "Non-critical shutdown error after test: %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
> +                ;; consumed and is available in the process
> +                ;; buffers.
> +                (let ((buffers (delq nil (list
> +                                          ;; FIXME: Accessing "internal" symbol here.
> +                                          (process-buffer (jsonrpc--process server))
> +                                          (jsonrpc-stderr-buffer server)
> +                                          (jsonrpc-events-buffer server)))))
> +                  (cond (noninteractive
> +                         (dolist (buffer buffers)
> +                           (eglot--message "%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--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))
> +    (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
> +      (with-current-buffer buf (save-buffer) (kill-buffer)))
> +    (delete-directory fixture-directory 'recursive)))
> +
> +(cl-defmacro eglot--with-timeout (timeout &body body)
> +  (declare (indent 1) (debug t))
> +  `(eglot--call-with-timeout ,timeout (lambda () ,@body)))
> +
> +(defun eglot--call-with-timeout (timeout fn)
> +  (let* ((tag (gensym "eglot-test-timeout"))
> +         (timed-out (make-symbol "timeout"))
> +         (timeout-and-message
> +          (if (listp timeout) timeout
> +            (list timeout "waiting for test to finish")))
> +         (timeout (car timeout-and-message))
> +         (message (cadr timeout-and-message))
> +         (timer)
> +         (retval))
> +    (unwind-protect
> +        (setq retval
> +              (catch tag
> +                (setq timer
> +                      (run-with-timer timeout nil
> +                                      (lambda ()
> +                                        (unless edebug-active
> +                                          (throw tag timed-out)))))
> +                (funcall fn)))
> +      (cancel-timer timer)
> +      (when (eq retval timed-out)
> +        (error "%s" (concat "Timed out " message))))))
> +
> +(defun eglot--find-file-noselect (file &optional noerror)
> +  (unless (or noerror
> +              (file-readable-p file)) (error "%s does not exist" file))
> +  (find-file-noselect file))
> +
> +(cl-defmacro eglot--sniffing ((&key server-requests
> +                                    server-notifications
> +                                    server-replies
> +                                    client-requests
> +                                    client-notifications
> +                                    client-replies)
> +                              &rest body)
> +  "Run BODY saving LSP JSON messages in variables, most recent first."
> +  (declare (indent 1) (debug (sexp &rest form)))
> +  (let ((log-event-ad-sym (make-symbol "eglot--event-sniff")))
> +    `(unwind-protect
> +         (let ,(delq nil (list server-requests
> +                               server-notifications
> +                               server-replies
> +                               client-requests
> +                               client-notifications
> +                               client-replies))
> +           (advice-add
> +            #'jsonrpc--log-event :before
> +            (lambda (_proc message &optional type)
> +              (cl-destructuring-bind (&key method id _error &allow-other-keys)
> +                  message
> +                (let ((req-p (and method id))
> +                      (notif-p method)
> +                      (reply-p id))
> +                  (cond
> +                   ((eq type 'server)
> +                    (cond (req-p ,(when server-requests
> +                                    `(push message ,server-requests)))
> +                          (notif-p ,(when server-notifications
> +                                      `(push message ,server-notifications)))
> +                          (reply-p ,(when server-replies
> +                                      `(push message ,server-replies)))))
> +                   ((eq type 'client)
> +                    (cond (req-p ,(when client-requests
> +                                    `(push message ,client-requests)))
> +                          (notif-p ,(when client-notifications
> +                                      `(push message ,client-notifications)))
> +                          (reply-p ,(when client-replies
> +                                      `(push message ,client-replies)))))))))
> +            '((name . ,log-event-ad-sym)))
> +           ,@body)
> +       (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
> +            (cl-loop thereis (cl-loop for json in ,events-sym
> +                                      for method = (plist-get json :method)
> +                                      when (keywordp method)
> +                                      do (plist-put json :method
> +                                                    (substring
> +                                                     (symbol-name method)
> +                                                     1))
> +                                      when (funcall
> +                                            (jsonrpc-lambda ,args ,@body) json)
> +                                      return (cons json before)
> +                                      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
> +                     ;; `read-event' is essential to have the file
> +                     ;; watchers come through.
> +                     (read-event "[eglot] Waiting a bit..." 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))))))
> +
> +;; `rust-mode' is not a part of Emacs, so we define these two shims
> +;; which should be more than enough for testing.
> +(unless (functionp 'rust-mode)
> +  (define-derived-mode rust-mode prog-mode "Rust")
> +  (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)))
> +
> +;; `typescript-mode' is not a part of Emacs, so we define these two
> +;; shims which should be more than enough for testing.
> +(unless (functionp 'typescript-mode)
> +  (define-derived-mode typescript-mode prog-mode "TypeScript")
> +  (add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode)))
> +
> +(defun eglot--tests-connect (&optional timeout)
> +  (let* ((timeout (or timeout 10))
> +         (eglot-sync-connect t)
> +         (eglot-connect-timeout timeout))
> +    (apply #'eglot--connect (eglot--guess-contact))))
> +
> +(defun eglot--simulate-key-event (char)
> +  "Like (execute-kbd-macro (vector char)), but with `call-interactively'."
> +  ;; Also, this is a bit similar to what electric-tests.el does.
> +  (setq last-input-event char)
> +  (setq last-command-event char)
> +  (call-interactively (key-binding (vector char))))
> +
> +\f
> +;;; Unit tests
> +
> +(ert-deftest eclipse-connect ()
> +  "Connect to eclipse.jdt.ls server."
> +  (skip-unless (executable-find "jdtls"))
> +  (eglot--with-fixture
> +      '(("project/src/main/java/foo" . (("Main.java" . "")))
> +        ("project/.git/" . nil))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/src/main/java/foo/Main.java")
> +      (eglot--sniffing (:server-notifications s-notifs)
> +        (should (eglot--tests-connect 20))
> +        (eglot--wait-for (s-notifs 10)
> +            (&key _id method &allow-other-keys)
> +          (string= method "language/status"))))))
> +
> +(defun eglot-tests--auto-detect-running-server-1 ()
> +  (let (server)
> +    (eglot--with-fixture
> +     `(("project" . (("coiso.c" . "bla")
> +                     ("merdix.c" . "bla")))
> +       ("anotherproject" . (("cena.c" . "bla"))))
> +     (with-current-buffer
> +         (eglot--find-file-noselect "project/coiso.c")
> +       (should (setq server (eglot--tests-connect)))
> +       (should (eglot-current-server)))
> +     (with-current-buffer
> +         (eglot--find-file-noselect "project/merdix.c")
> +       (should (eglot-current-server))
> +       (should (eq (eglot-current-server) server)))
> +     (with-current-buffer
> +         (eglot--find-file-noselect "anotherproject/cena.c")
> +       (should-error (eglot--current-server-or-lose))))))
> +
> +(ert-deftest auto-detect-running-server ()
> +  "Visit a file and \\[eglot], then visit a neighbour."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot-tests--auto-detect-running-server-1))
> +
> +(ert-deftest auto-shutdown ()
> +  "Visit a file and \\[eglot], then kill buffer."
> +  (skip-unless (executable-find "clangd"))
> +  (let (server
> +        buffer)
> +    (eglot--with-fixture
> +        `(("project" . (("thingy.c" . "int main() {return 0;}"))))
> +      (with-current-buffer
> +          (setq buffer (eglot--find-file-noselect "project/thingy.c"))
> +        (should (setq server (eglot--tests-connect)))
> +        (should (eglot-current-server))
> +        (let ((eglot-autoshutdown nil)) (kill-buffer buffer))
> +        (should (jsonrpc-running-p server))
> +        ;; re-find file...
> +        (setq buffer (eglot--find-file-noselect (buffer-file-name buffer)))
> +        ;; ;; but now kill it with `eglot-autoshutdown' set to t
> +        (let ((eglot-autoshutdown t)) (kill-buffer buffer))
> +        (should (not (jsonrpc-running-p server)))))))
> +
> +(ert-deftest auto-reconnect ()
> +  "Start a server.  Kill it.  Watch it reconnect."
> +  (skip-unless (executable-find "clangd"))
> +  (let (server (eglot-autoreconnect 1))
> +    (eglot--with-fixture
> +        `(("project" . (("thingy.c" . "bla")
> +                        ("thingy2.c" . "bla"))))
> +      (with-current-buffer
> +          (eglot--find-file-noselect "project/thingy.c")
> +        (should (setq server (eglot--tests-connect)))
> +        ;; In 1.2 seconds > `eglot-autoreconnect' kill servers. We
> +        ;; should have a automatic reconnection.
> +        (run-with-timer 1.2 nil (lambda () (delete-process
> +                                            (jsonrpc--process server))))
> +        (while (jsonrpc-running-p server) (accept-process-output nil 0.5))
> +        (should (eglot-current-server))
> +        ;; Now try again too quickly
> +        (setq server (eglot-current-server))
> +        (let ((proc (jsonrpc--process server)))
> +          (run-with-timer 0.5 nil (lambda () (delete-process proc)))
> +          (while (process-live-p proc) (accept-process-output nil 0.5)))
> +        (should (not (eglot-current-server)))))))
> +
> +(ert-deftest rust-analyzer-watches-files ()
> +  "Start rust-analyzer.  Notify it when a critical file changes."
> +  (skip-unless (executable-find "rust-analyzer"))
> +  (skip-unless (executable-find "cargo"))
> +  (let ((eglot-autoreconnect 1))
> +    (eglot--with-fixture
> +        '(("watch-project" . (("coiso.rs" . "bla")
> +                              ("merdix.rs" . "bla"))))
> +      (with-current-buffer
> +          (eglot--find-file-noselect "watch-project/coiso.rs")
> +        (should (zerop (shell-command "cargo init")))
> +        (eglot--sniffing (
> +                          :server-requests s-requests
> +                          :client-notifications c-notifs
> +                          :client-replies c-replies
> +                          )
> +          (should (eglot--tests-connect))
> +          (let (register-id)
> +            (eglot--wait-for (s-requests 1)
> +                (&key id method &allow-other-keys)
> +              (setq register-id id)
> +              (string= method "client/registerCapability"))
> +            (eglot--wait-for (c-replies 1)
> +                (&key id error &allow-other-keys)
> +              (and (eq id register-id) (null error))))
> +          (delete-file "Cargo.toml")
> +          (eglot--wait-for
> +              (c-notifs 3 "waiting for didChangeWatchedFiles notification")
> +              (&key method params &allow-other-keys)
> +            (and (string= method "workspace/didChangeWatchedFiles")
> +                 (cl-destructuring-bind (&key uri type)
> +                     (elt (plist-get params :changes) 0)
> +                   (and (string= (eglot--path-to-uri "Cargo.toml") uri)
> +                        (= type 3))))))))))
> +
> +(ert-deftest basic-diagnostics ()
> +  "Test basic diagnostics."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("diag-project" .
> +         (("main.c" . "int main(){froat a = 42.2; return 0;}"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "diag-project/main.c")
> +      (eglot--sniffing (:server-notifications s-notifs)
> +        (eglot--tests-connect)
> +        (eglot--wait-for (s-notifs 2)
> +            (&key _id method &allow-other-keys)
> +          (string= method "textDocument/publishDiagnostics"))
> +        (flymake-start)
> +        (goto-char (point-min))
> +        (flymake-goto-next-error 1 '() t)
> +        (should (eq 'flymake-error (face-at-point)))))))
> +
> +(ert-deftest diagnostic-tags-unnecessary-code ()
> +  "Test rendering of diagnostics tagged \"unnecessary\"."
> +  (skip-unless (executable-find "rust-analyzer"))
> +  (eglot--with-fixture
> +      '(("diagnostic-tag-project" .
> +         (("main.rs" .
> +           "fn main() -> () { let test=3; }"))))
> +    (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--sniffing (:server-notifications s-notifs)
> +          (eglot--tests-connect)
> +          (eglot--wait-for (s-notifs 10)
> +              (&key _id method &allow-other-keys)
> +            (string= method "textDocument/publishDiagnostics"))
> +          (flymake-start)
> +          (goto-char (point-min))
> +          (flymake-goto-next-error 1 '() t)
> +          (should (eq 'eglot-diagnostic-tag-unnecessary-face (face-at-point))))))))
> +
> +(defun eglot--eldoc-on-demand ()
> +  ;; Trick Eldoc 1.1.0 into accepting on-demand calls.
> +  (eldoc t))
> +
> +(defun eglot--tests-force-full-eldoc ()
> +  ;; FIXME: This uses some Eldoc implementation defatils.
> +  (when (buffer-live-p eldoc--doc-buffer)
> +    (with-current-buffer eldoc--doc-buffer
> +      (let ((inhibit-read-only t))
> +        (erase-buffer))))
> +  (eglot--eldoc-on-demand)
> +  (cl-loop
> +   repeat 10
> +   for retval = (and (buffer-live-p eldoc--doc-buffer)
> +                     (with-current-buffer eldoc--doc-buffer
> +                       (let ((bs (buffer-string)))
> +                         (unless (zerop (length bs)) bs))))
> +   when retval return retval
> +   do (sit-for 0.5)
> +   finally (error "eglot--tests-force-full-eldoc didn't deliver")))
> +
> +(ert-deftest rust-analyzer-hover-after-edit ()
> +  "Hover and highlightChanges."
> +  (skip-unless (executable-find "rust-analyzer"))
> +  (skip-unless (executable-find "cargo"))
> +  (eglot--with-fixture
> +      '(("hover-project" .
> +         (("main.rs" .
> +           "fn test() -> i32 { let test=3; return te; }"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "hover-project/main.rs")
> +      (should (zerop (shell-command "cargo init")))
> +      (eglot--sniffing (
> +                        :server-replies s-replies
> +                        :client-requests c-reqs
> +                        )
> +        (eglot--tests-connect)
> +        (goto-char (point-min))
> +        (search-forward "return te")
> +        (insert "st")
> +        (progn
> +          ;; simulate these two which don't happen when buffer isn't
> +          ;; visible in a window.
> +          (eglot--signal-textDocument/didChange)
> +          (eglot--eldoc-on-demand))
> +        (let (pending-id)
> +          (eglot--wait-for (c-reqs 2)
> +              (&key id method &allow-other-keys)
> +            (setq pending-id id)
> +            (string= method "textDocument/documentHighlight"))
> +          (eglot--wait-for (s-replies 2)
> +              (&key id &allow-other-keys)
> +            (eq id pending-id)))))))
> +
> +(ert-deftest rename-a-symbol ()
> +  "Test basic symbol renaming."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("rename-project"
> +         . (("main.c" .
> +             "int foo() {return 42;} int main() {return foo();}"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "rename-project/main.c")
> +      (eglot--tests-connect)
> +      (goto-char (point-min)) (search-forward "foo")
> +      (eglot-rename "bar")
> +      (should (equal (buffer-string)
> +                     "int bar() {return 42;} int main() {return bar();}")))))
> +
> +(ert-deftest basic-completions ()
> +  "Test basic autocompletion in a python LSP."
> +  (skip-unless (executable-find "pylsp"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . "import sys\nsys.exi"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (should (eglot--tests-connect))
> +      (goto-char (point-max))
> +      (completion-at-point)
> +      (should (looking-back "sys.exit")))))
> +
> +(ert-deftest non-unique-completions ()
> +  "Test completion resulting in 'Complete, but not unique'."
> +  (skip-unless (executable-find "pylsp"))
> +  (eglot--with-fixture
> +      '(("project" . (("something.py" . "foo=1\nfoobar=2\nfoo"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (should (eglot--tests-connect))
> +      (goto-char (point-max))
> +      (completion-at-point))
> +    ;; FIXME: `current-message' doesn't work here :-(
> +    (with-current-buffer (messages-buffer)
> +      (save-excursion
> +        (goto-char (point-max))
> +        (forward-line -1)
> +        (should (looking-at "Complete, but not unique"))))))
> +
> +(ert-deftest basic-xref ()
> +  "Test basic xref functionality in a python LSP."
> +  (skip-unless (executable-find "pylsp"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . "def foo(): pass\ndef bar(): foo()"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (should (eglot--tests-connect))
> +      (search-forward "bar(): f")
> +      (call-interactively 'xref-find-definitions)
> +      (should (looking-at "foo(): pass")))))
> +
> +(defvar eglot--test-python-buffer
> +  "\
> +def foobarquux(a, b, c=True): pass
> +def foobazquuz(d, e, f): pass
> +")
> +
> +(declare-function yas-minor-mode nil)
> +
> +(ert-deftest snippet-completions ()
> +  "Test simple snippet completion in a python LSP."
> +  (skip-unless (and (executable-find "pylsp")
> +                    (functionp 'yas-minor-mode)))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . ,eglot--test-python-buffer))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (yas-minor-mode 1)
> +      (let ((eglot-workspace-configuration
> +             `((:pylsp . (:plugins (:jedi_completion (:include_params t)))))))
> +        (should (eglot--tests-connect)))
> +      (goto-char (point-max))
> +      (insert "foobar")
> +      (completion-at-point)
> +      (should (looking-back "foobarquux("))
> +      (should (looking-at "a, b)")))))
> +
> +(defvar company-candidates)
> +(declare-function company-mode nil)
> +(declare-function company-complete nil)
> +
> +(ert-deftest snippet-completions-with-company ()
> +  "Test simple snippet completion in a python LSP."
> +  (skip-unless (and (executable-find "pylsp")
> +                    (functionp 'yas-minor-mode)
> +                    (functionp 'company-complete)))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . ,eglot--test-python-buffer))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (yas-minor-mode 1)
> +      (let ((eglot-workspace-configuration
> +             `((:pylsp . (:plugins (:jedi_completion (:include_params t)))))))
> +        (should (eglot--tests-connect)))
> +      (goto-char (point-max))
> +      (insert "foo")
> +      (company-mode)
> +      (company-complete)
> +      (should (looking-back "fooba"))
> +      (should (= 2 (length company-candidates)))
> +      ;; this last one is brittle, since there it is possible that
> +      ;; pylsp will change the representation of this candidate
> +      (should (member "foobazquuz(d, e, f)" company-candidates)))))
> +
> +(ert-deftest eglot-eldoc-after-completions ()
> +  "Test documentation echo in a python LSP."
> +  (skip-unless (executable-find "pylsp"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . "import sys\nsys.exi"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (should (eglot--tests-connect))
> +      (goto-char (point-max))
> +      (completion-at-point)
> +      (should (looking-back "sys.exit"))
> +      (should (string-match "^exit" (eglot--tests-force-full-eldoc))))))
> +
> +(ert-deftest eglot-multiline-eldoc ()
> +  "Test if suitable amount of lines of hover info are shown."
> +  (skip-unless (executable-find "pylsp"))
> +  (eglot--with-fixture
> +      `(("project" . (("hover-first.py" . "from datetime import datetime"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/hover-first.py")
> +      (should (eglot--tests-connect))
> +      (goto-char (point-max))
> +      ;; one-line
> +      (let* ((eldoc-echo-area-use-multiline-p t)
> +             (captured-message (eglot--tests-force-full-eldoc)))
> +        (should (string-match "datetim" captured-message))
> +        (should (cl-find ?\n captured-message))))))
> +
> +(ert-deftest eglot-single-line-eldoc ()
> +  "Test if suitable amount of lines of hover info are shown."
> +  (skip-unless (executable-find "pylsp"))
> +  (eglot--with-fixture
> +      `(("project" . (("hover-first.py" . "from datetime import datetime"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/hover-first.py")
> +      (should (eglot--tests-connect))
> +      (goto-char (point-max))
> +      ;; one-line
> +      (let* ((eldoc-echo-area-use-multiline-p nil)
> +             (captured-message (eglot--tests-force-full-eldoc)))
> +        (should (string-match "datetim" captured-message))
> +        (should (not (cl-find ?\n eldoc-last-message)))))))
> +
> +(ert-deftest python-autopep-formatting ()
> +  "Test formatting in the pylsp python LSP.
> +pylsp prefers autopep over yafp, despite its README stating the contrary."
> +  ;; Beware, default autopep rules can change over time, which may
> +  ;; affect this test.
> +  (skip-unless (and (executable-find "pylsp")
> +                    (executable-find "autopep8")))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . "def a():pass\n\ndef b():pass"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (should (eglot--tests-connect))
> +      ;; Try to format just the second line
> +      (search-forward "b():pa")
> +      (eglot-format (line-beginning-position) (line-end-position))
> +      (should (looking-at "ss"))
> +      (should
> +       (string= (buffer-string) "def a():pass\n\n\ndef b(): pass\n"))
> +      ;; now format the whole buffer
> +      (eglot-format-buffer)
> +      (should
> +       (string= (buffer-string) "def a(): pass\n\n\ndef b(): pass\n")))))
> +
> +(ert-deftest python-yapf-formatting ()
> +  "Test formatting in the pylsp python LSP."
> +  (skip-unless (and (executable-find "pylsp")
> +                    (not (executable-find "autopep8"))
> +                    (or (executable-find "yapf")
> +                        (executable-find "yapf3"))))
> +  (eglot--with-fixture
> +      `(("project" . (("something.py" . "def a():pass\ndef b():pass"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.py")
> +      (should (eglot--tests-connect))
> +      ;; Try to format just the second line
> +      (search-forward "b():pa")
> +      (eglot-format (line-beginning-position) (line-end-position))
> +      (should (looking-at "ss"))
> +      (should
> +       (string= (buffer-string) "def a():pass\n\n\ndef b():\n    pass\n"))
> +      ;; now format the whole buffer
> +      (eglot-format-buffer)
> +      (should
> +       (string= (buffer-string) "def a():\n    pass\n\n\ndef b():\n    pass\n")))))
> +
> +(ert-deftest rust-on-type-formatting ()
> +  "Test textDocument/onTypeFormatting agains rust-analyzer."
> +  (skip-unless (executable-find "rust-analyzer"))
> +  (skip-unless (executable-find "cargo"))
> +  (eglot--with-fixture
> +      '(("on-type-formatting-project" .
> +         (("main.rs" .
> +           "fn main() -> () {\n  foo\n    .bar()\n  "))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "on-type-formatting-project/main.rs")
> +      (let ((eglot-server-programs '((rust-mode . ("rust-analyzer")))))
> +        (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)
> +             (string= method "textDocument/publishDiagnostics")))
> +        (goto-char (point-max))
> +        (eglot--simulate-key-event ?.)
> +        (should (looking-back "^    \\."))))))
> +
> +(ert-deftest javascript-basic ()
> +  "Test basic autocompletion in a JavaScript LSP."
> +  (skip-unless (executable-find "typescript-language-server"))
> +  (eglot--with-fixture
> +      '(("project" . (("hello.js" . "console.log('Hello world!');"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/hello.js")
> +      (let ((eglot-server-programs
> +             '((js-mode . ("typescript-language-server" "--stdio")))))
> +        (goto-char (point-max))
> +        (eglot--sniffing (:server-notifications
> +                          s-notifs
> +                          :client-notifications
> +                          c-notifs)
> +          (should (eglot--tests-connect))
> +          (eglot--wait-for (s-notifs 2) (&key method &allow-other-keys)
> +            (string= method "textDocument/publishDiagnostics"))
> +          (should (not (eq 'flymake-error (face-at-point))))
> +          (insert "{")
> +          (eglot--signal-textDocument/didChange)
> +          (eglot--wait-for (c-notifs 1) (&key method &allow-other-keys)
> +            (string= method "textDocument/didChange"))
> +          (eglot--wait-for (s-notifs 2) (&key params method &allow-other-keys)
> +            (and (string= method "textDocument/publishDiagnostics")
> +                 (cl-destructuring-bind (&key _uri diagnostics) params
> +                   (cl-find-if (jsonrpc-lambda (&key severity &allow-other-keys)
> +                                 (= severity 1))
> +                               diagnostics)))))))))
> +
> +(ert-deftest project-wide-diagnostics-typescript ()
> +  "Test diagnostics through multiple files in a TypeScript LSP."
> +  (skip-unless (executable-find "typescript-language-server"))
> +  (eglot--with-fixture
> +      '(("project" . (("hello.ts" . "const thing = 5;\nexport { thin }")
> +                      ("hello2.ts" . "import { thing } from './hello'"))))
> +    (eglot--make-file-or-dir '(".git"))
> +    (let ((eglot-server-programs
> +           '((typescript-mode . ("typescript-language-server" "--stdio")))))
> +      ;; Check both files because typescript-language-server doesn't
> +      ;; report all errors on startup, at least not with such a simple
> +      ;; setup.
> +      (with-current-buffer (eglot--find-file-noselect "project/hello2.ts")
> +        (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"))
> +          (should (= 2 (length (flymake--project-diagnostics)))))
> +        (with-current-buffer (eglot--find-file-noselect "hello.ts")
> +          (eglot--sniffing (:server-notifications s-notifs)
> +            (flymake-start)
> +            (eglot--wait-for (s-notifs 10)
> +                (&key _id method &allow-other-keys)
> +              (string= method "textDocument/publishDiagnostics"))
> +            (should (= 4 (length (flymake--project-diagnostics))))))))))
> +
> +(ert-deftest project-wide-diagnostics-rust-analyzer ()
> +  "Test diagnostics through multiple files in a TypeScript LSP."
> +  (skip-unless (executable-find "rust-analyzer"))
> +  (eglot--with-fixture
> +      '(("project" .
> +         (("main.rs" .
> +           "fn main() -> () { let test=3; }")
> +          ("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
> +      (with-current-buffer (eglot--find-file-noselect "project/other-file.rs")
> +        (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))))))))))
> +
> +(ert-deftest json-basic ()
> +  "Test basic autocompletion in vscode-json-languageserver."
> +  (skip-unless (executable-find "vscode-json-languageserver"))
> +  (eglot--with-fixture
> +      '(("project" .
> +         (("p.json" . "{\"foo.b")
> +          ("s.json" . "{\"properties\":{\"foo.bar\":{\"default\":\"fb\"}}}")
> +          (".git" . nil))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/p.json")
> +      (yas-minor-mode)
> +      (goto-char 2)
> +      (insert "\"$schema\": \"file://"
> +              (file-name-directory buffer-file-name) "s.json\",")
> +      (let ((eglot-server-programs
> +             '((js-mode . ("vscode-json-languageserver" "--stdio")))))
> +        (goto-char (point-max))
> +        (should (eglot--tests-connect))
> +        (completion-at-point)
> +        (should (looking-back "\"foo.bar\": \""))
> +        (should (looking-at "fb\"$"))))))
> +
> +(defun eglot-tests--lsp-abiding-column-1 ()
> +  (eglot--with-fixture
> +      '(("project" .
> +         (("foo.c" . "const char write_data[] = u8\"🚂🚃🚄🚅🚆🚈🚇🚈🚉🚊🚋🚌🚎🚝🚞🚟🚠🚡🛤🛲\";"))))
> +    (let ((eglot-server-programs
> +           '((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)
> +        (eglot--sniffing (:client-notifications c-notifs)
> +          (eglot--tests-connect)
> +          (end-of-line)
> +          (insert "p ")
> +          (eglot--signal-textDocument/didChange)
> +          (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 (looking-at "p")))))))
> +
> +(ert-deftest eglot-lsp-abiding-column ()
> +  "Test basic `eglot-lsp-abiding-column' and `eglot-move-to-lsp-abiding-column'."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot-tests--lsp-abiding-column-1))
> +
> +(ert-deftest eglot-ensure ()
> +  "Test basic `eglot-ensure' functionality."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("project" . (("foo.c" . "int foo() {return 42;}")
> +                      ("bar.c" . "int bar() {return 42;}")))
> +        (c-mode-hook (eglot-ensure)))
> +    (let (server)
> +      ;; need `ert-simulate-command' because `eglot-ensure'
> +      ;; relies on `post-command-hook'.
> +      (with-current-buffer
> +          (ert-simulate-command
> +           '(find-file "project/foo.c"))
> +        ;; FIXME: This test fails without this sleep on my machine.
> +        ;; Figure out why and solve this more cleanly.
> +        (sleep-for 0.1)
> +        (should (setq server (eglot-current-server))))
> +      (with-current-buffer
> +          (ert-simulate-command
> +           '(find-file "project/bar.c"))
> +        (should (eq server (eglot-current-server)))))))
> +
> +(ert-deftest slow-sync-connection-wait ()
> +  "Connect with `eglot-sync-connect' set to t."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.c" . "int foo() {return 42;}"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.c")
> +      (let ((eglot-sync-connect t)
> +            (eglot-server-programs
> +             `((c-mode . ("sh" "-c" "sleep 1 && clangd")))))
> +        (should (eglot--tests-connect 3))))))
> +
> +(ert-deftest slow-sync-connection-intime ()
> +  "Connect synchronously with `eglot-sync-connect' set to 2."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.c" . "int foo() {return 42;}"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.c")
> +      (let ((eglot-sync-connect 2)
> +            (eglot-server-programs
> +             `((c-mode . ("sh" "-c" "sleep 1 && clangd")))))
> +        (should (eglot--tests-connect 3))))))
> +
> +(ert-deftest slow-async-connection ()
> +  "Connect asynchronously with `eglot-sync-connect' set to 2."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.c" . "int foo() {return 42;}"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.c")
> +      (let ((eglot-sync-connect 1)
> +            (eglot-server-programs
> +             `((c-mode . ("sh" "-c" "sleep 2 && clangd")))))
> +        (should-not (apply #'eglot--connect (eglot--guess-contact)))
> +        (eglot--with-timeout 3
> +          (while (not (eglot-current-server))
> +            (accept-process-output nil 0.2))
> +          (should (eglot-current-server)))))))
> +
> +(ert-deftest slow-sync-timeout ()
> +  "Failed attempt at connection synchronously."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--with-fixture
> +      `(("project" . (("something.c" . "int foo() {return 42;}"))))
> +    (with-current-buffer
> +        (eglot--find-file-noselect "project/something.c")
> +      (let ((eglot-sync-connect t)
> +            (eglot-connect-timeout 1)
> +            (eglot-server-programs
> +             `((c-mode . ("sh" "-c" "sleep 2 && clangd")))))
> +        (should-error (apply #'eglot--connect (eglot--guess-contact)))))))
> +
> +(ert-deftest eglot-capabilities ()
> +  "Unit test for `eglot--server-capable'."
> +  (cl-letf (((symbol-function 'eglot--capabilities)
> +             (lambda (_dummy)
> +               ;; test data lifted from Golangserver example at
> +               ;; https://github.com/joaotavora/eglot/pull/74
> +               (list :textDocumentSync 2 :hoverProvider t
> +                     :completionProvider '(:triggerCharacters ["."])
> +                     :signatureHelpProvider '(:triggerCharacters ["(" ","])
> +                     :definitionProvider t :typeDefinitionProvider t
> +                     :referencesProvider t :documentSymbolProvider t
> +                     :workspaceSymbolProvider t :implementationProvider t
> +                     :documentFormattingProvider t :xworkspaceReferencesProvider t
> +                     :xdefinitionProvider t :xworkspaceSymbolByProperties t)))
> +            ((symbol-function 'eglot--current-server-or-lose)
> +             (lambda () nil)))
> +    (should (eql 2 (eglot--server-capable :textDocumentSync)))
> +    (should (eglot--server-capable :completionProvider :triggerCharacters))
> +    (should (equal '(:triggerCharacters ["."]) (eglot--server-capable :completionProvider)))
> +    (should-not (eglot--server-capable :foobarbaz))
> +    (should-not (eglot--server-capable :textDocumentSync :foobarbaz))))
> +
> +(defmacro eglot--without-interface-warnings (&rest body)
> +  (let ((eglot-strict-mode nil))
> +    (macroexpand-all (macroexp-progn body) macroexpand-all-environment)))
> +
> +(ert-deftest eglot-strict-interfaces ()
> +  (let ((eglot--lsp-interface-alist
> +         `((FooObject . ((:foo :bar) (:baz))))))
> +    (eglot--without-interface-warnings
> +     (should
> +      (equal '("foo" . "bar")
> +             (let ((eglot-strict-mode nil))
> +               (eglot--dbind (foo bar) `(:foo "foo" :bar "bar")
> +                 (cons foo bar)))))
> +     (should-error
> +      (let ((eglot-strict-mode '(disallow-non-standard-keys)))
> +        (eglot--dbind (foo bar) `(:foo "foo" :bar "bar" :fotrix bargh)
> +          (cons foo bar))))
> +     (should
> +      (equal '("foo" . "bar")
> +             (let ((eglot-strict-mode nil))
> +               (eglot--dbind (foo bar) `(:foo "foo" :bar "bar" :fotrix bargh)
> +                 (cons foo bar)))))
> +     (should-error
> +      (let ((eglot-strict-mode '(disallow-non-standard-keys)))
> +        (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :bar "bar" :fotrix bargh)
> +          (cons foo bar))))
> +     (should
> +      (equal '("foo" . "bar")
> +             (let ((eglot-strict-mode '(disallow-non-standard-keys)))
> +               (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :bar "bar" :baz bargh)
> +                 (cons foo bar)))))
> +     (should
> +      (equal '("foo" . nil)
> +             (let ((eglot-strict-mode nil))
> +               (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :baz bargh)
> +                 (cons foo bar)))))
> +     (should
> +      (equal '("foo" . "bar")
> +             (let ((eglot-strict-mode '(enforce-required-keys)))
> +               (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :bar "bar" :baz bargh)
> +                 (cons foo bar)))))
> +     (should-error
> +      (let ((eglot-strict-mode '(enforce-required-keys)))
> +        (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :baz bargh)
> +          (cons foo bar)))))))
> +
> +(ert-deftest eglot-dcase ()
> +  (eglot--without-interface-warnings
> +   (let ((eglot--lsp-interface-alist
> +          `((FooObject . ((:foo :bar) (:baz)))
> +            (CodeAction (:title) (:kind :diagnostics :edit :command))
> +            (Command ((:title . string) (:command . string)) (:arguments)))))
> +     (should
> +      (equal
> +       "foo"
> +       (eglot--dcase `(:foo "foo" :bar "bar")
> +         (((FooObject) foo)
> +          foo))))
> +     (should
> +      (equal
> +       (list "foo" '(:title "hey" :command "ho") "some edit")
> +       (eglot--dcase '(:title "foo"
> +                              :command (:title "hey" :command "ho")
> +                              :edit "some edit")
> +         (((Command) _title _command _arguments)
> +          (ert-fail "Shouldn't have destructured this object as a Command"))
> +         (((CodeAction) title edit command)
> +          (list title command edit)))))
> +     (should
> +      (equal
> +       (list "foo" "some command" nil)
> +       (eglot--dcase '(:title "foo" :command "some command")
> +         (((Command) title command arguments)
> +          (list title command arguments))
> +         (((CodeAction) _title _edit _command)
> +          (ert-fail "Shouldn't have destructured this object as a CodeAction"))))))))
> +
> +(ert-deftest eglot-dcase-issue-452 ()
> +  (let ((eglot--lsp-interface-alist
> +         `((FooObject . ((:foo :bar) (:baz)))
> +           (CodeAction (:title) (:kind :diagnostics :edit :command))
> +           (Command ((string . :title) (:command . string)) (:arguments)))))
> +    (should
> +     (equal
> +      (list "foo" '(:command "cmd" :title "alsofoo"))
> +      (eglot--dcase '(:title "foo" :command (:command "cmd" :title "alsofoo"))
> +        (((Command) _title _command _arguments)
> +         (ert-fail "Shouldn't have destructured this object as a Command"))
> +        (((CodeAction) title command)
> +         (list title command)))))))
> +
> +(cl-defmacro eglot--guessing-contact ((interactive-sym
> +                                       prompt-args-sym
> +                                       guessed-class-sym guessed-contact-sym
> +                                       &optional guessed-lang-id-sym)
> +                                      &body body)
> +  "Guess LSP contact with `eglot--guessing-contact', evaluate BODY.
> +
> +BODY is evaluated twice, with INTERACTIVE bound to the boolean passed to
> +`eglot--guess-contact' each time.
> +
> +If the user would have been prompted, PROMPT-ARGS-SYM is bound to
> +the list of arguments that would have been passed to
> +`read-shell-command', else nil.  GUESSED-CLASS-SYM,
> +GUESSED-CONTACT-SYM and GUESSED-LANG-ID-SYM are bound to the
> +useful return values of `eglot--guess-contact'.  Unless the
> +server program evaluates to \"a-missing-executable.exe\", this
> +macro will assume it exists."
> +  (declare (indent 1) (debug t))
> +  (let ((i-sym (cl-gensym)))
> +    `(dolist (,i-sym '(nil t))
> +       (let ((,interactive-sym ,i-sym)
> +             (buffer-file-name "_")
> +             ,@(when prompt-args-sym `((,prompt-args-sym nil))))
> +         (cl-letf (((symbol-function 'executable-find)
> +                    (lambda (name &optional _remote)
> +                      (unless (string-equal name "a-missing-executable.exe")
> +                        (format "/totally-mock-bin/%s" name))))
> +                   ((symbol-function 'read-shell-command)
> +                    ,(if prompt-args-sym
> +                         `(lambda (&rest args) (setq ,prompt-args-sym args) "")
> +                       `(lambda (&rest _dummy) ""))))
> +           (cl-destructuring-bind
> +               (_ _ ,guessed-class-sym ,guessed-contact-sym
> +                  ,(or guessed-lang-id-sym '_))
> +               (eglot--guess-contact ,i-sym)
> +             ,@body))))))
> +
> +(ert-deftest eglot-server-programs-simple-executable ()
> +  (let ((eglot-server-programs '((foo-mode "some-executable")))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("some-executable"))))))
> +
> +(ert-deftest eglot-server-programs-simple-missing-executable ()
> +  (let ((eglot-server-programs '((foo-mode "a-missing-executable.exe")))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (interactive-p prompt-args guessed-class guessed-contact)
> +      (should (equal (not prompt-args) (not interactive-p)))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (or prompt-args
> +                  (equal guessed-contact '("a-missing-executable.exe")))))))
> +
> +(ert-deftest eglot-server-programs-executable-multiple-major-modes ()
> +  (let ((eglot-server-programs '(((bar-mode foo-mode) "some-executable")))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("some-executable"))))))
> +
> +(ert-deftest eglot-server-programs-executable-with-arg ()
> +  (let ((eglot-server-programs '((foo-mode "some-executable" "arg1")))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("some-executable" "arg1"))))))
> +
> +(ert-deftest eglot-server-programs-executable-with-args-and-autoport ()
> +  (let ((eglot-server-programs '((foo-mode "some-executable" "arg1"
> +                                           :autoport "arg2")))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("some-executable" "arg1"
> +                                       :autoport "arg2"))))))
> +
> +(ert-deftest eglot-server-programs-host-and-port ()
> +  (let ((eglot-server-programs '((foo-mode "somehost.example.com" 7777)))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("somehost.example.com" 7777))))))
> +
> +(ert-deftest eglot-server-programs-host-and-port-and-tcp-args ()
> +  (let ((eglot-server-programs '((foo-mode "somehost.example.com" 7777
> +                                           :type network)))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("somehost.example.com" 7777
> +                                       :type network))))))
> +
> +(ert-deftest eglot-server-programs-class-name-and-plist ()
> +  (let ((eglot-server-programs '((foo-mode bar-class :init-key init-val)))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'bar-class))
> +      (should (equal guessed-contact '(:init-key init-val))))))
> +
> +(ert-deftest eglot-server-programs-class-name-and-contact-spec ()
> +  (let ((eglot-server-programs '((foo-mode bar-class "some-executable" "arg1"
> +                                           :autoport "arg2")))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'bar-class))
> +      (should (equal guessed-contact '("some-executable" "arg1"
> +                                       :autoport "arg2"))))))
> +
> +(ert-deftest eglot-server-programs-function ()
> +  (let ((eglot-server-programs '((foo-mode . (lambda (&optional _)
> +                                               '("some-executable")))))
> +        (major-mode 'foo-mode))
> +    (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
> +      (should (not prompt-args))
> +      (should (equal guessed-class 'eglot-lsp-server))
> +      (should (equal guessed-contact '("some-executable"))))))
> +
> +(ert-deftest eglot-server-programs-guess-lang ()
> +  (let ((major-mode 'foo-mode))
> +    (let ((eglot-server-programs '((foo-mode . ("prog-executable")))))
> +      (eglot--guessing-contact (_ nil _ _ guessed-lang)
> +        (should (equal guessed-lang "foo"))))
> +    (let ((eglot-server-programs '(((foo-mode :language-id "bar")
> +                                    . ("prog-executable")))))
> +      (eglot--guessing-contact (_ nil _ _ guessed-lang)
> +        (should (equal guessed-lang "bar"))))
> +    (let ((eglot-server-programs '(((baz-mode (foo-mode :language-id "bar"))
> +                                    . ("prog-executable")))))
> +      (eglot--guessing-contact (_ nil _ _ guessed-lang)
> +        (should (equal guessed-lang "bar"))))))
> +
> +(defun eglot--glob-match (glob str)
> +  (funcall (eglot--glob-compile glob t t) str))
> +
> +(ert-deftest eglot--glob-test ()
> +  (should (eglot--glob-match "foo/**/baz" "foo/bar/baz"))
> +  (should (eglot--glob-match "foo/**/baz" "foo/baz"))
> +  (should-not (eglot--glob-match "foo/**/baz" "foo/bar"))
> +  (should (eglot--glob-match "foo/**/baz/**/quuz" "foo/baz/foo/quuz"))
> +  (should (eglot--glob-match "foo/**/baz/**/quuz" "foo/foo/foo/baz/foo/quuz"))
> +  (should-not (eglot--glob-match "foo/**/baz/**/quuz" "foo/foo/foo/ding/foo/quuz"))
> +  (should (eglot--glob-match "*.js" "foo.js"))
> +  (should-not (eglot--glob-match "*.js" "foo.jsx"))
> +  (should (eglot--glob-match "foo/**/*.js" "foo/bar/baz/foo.js"))
> +  (should-not (eglot--glob-match "foo/**/*.js" "foo/bar/baz/foo.jsx"))
> +  (should (eglot--glob-match "*.{js,ts}" "foo.js"))
> +  (should-not (eglot--glob-match "*.{js,ts}" "foo.xs"))
> +  (should (eglot--glob-match "foo/**/*.{js,ts}" "foo/bar/baz/foo.ts"))
> +  (should (eglot--glob-match "foo/**/*.{js,ts}x" "foo/bar/baz/foo.tsx"))
> +  (should (eglot--glob-match "?oo.js" "foo.js"))
> +  (should (eglot--glob-match "foo/**/*.{js,ts}?" "foo/bar/baz/foo.tsz"))
> +  (should (eglot--glob-match "foo/**/*.{js,ts}?" "foo/bar/baz/foo.tsz"))
> +  (should (eglot--glob-match "example.[!0-9]" "example.a"))
> +  (should-not (eglot--glob-match "example.[!0-9]" "example.0"))
> +  (should (eglot--glob-match "example.[0-9]" "example.0"))
> +  (should-not (eglot--glob-match "example.[0-9]" "example.a"))
> +  (should (eglot--glob-match "**/bar/" "foo/bar/"))
> +  (should-not (eglot--glob-match "foo.hs" "fooxhs"))
> +
> +  ;; Some more tests
> +  (should (eglot--glob-match "**/.*" ".git"))
> +  (should (eglot--glob-match ".?" ".o"))
> +  (should (eglot--glob-match "**/.*" ".hidden.txt"))
> +  (should (eglot--glob-match "**/.*" "path/.git"))
> +  (should (eglot--glob-match "**/.*" "path/.hidden.txt"))
> +  (should (eglot--glob-match "**/node_modules/**" "node_modules/"))
> +  (should (eglot--glob-match "{foo,bar}/**" "foo/test"))
> +  (should (eglot--glob-match "{foo,bar}/**" "bar/test"))
> +  (should (eglot--glob-match "some/**/*" "some/foo.js"))
> +  (should (eglot--glob-match "some/**/*" "some/folder/foo.js"))
> +
> +  ;; VSCode supposedly supports this, not sure if good idea.
> +  ;;
> +  ;; (should (eglot--glob-match "**/node_modules/**" "node_modules"))
> +  ;; (should (eglot--glob-match "{foo,bar}/**" "foo"))
> +  ;; (should (eglot--glob-match "{foo,bar}/**" "bar"))
> +
> +  ;; VSCode also supports nested blobs.  Do we care?
> +  ;;
> +  ;; (should (eglot--glob-match "{**/*.d.ts,**/*.js}" "/testing/foo.js"))
> +  ;; (should (eglot--glob-match "{**/*.d.ts,**/*.js}" "testing/foo.d.ts"))
> +  ;; (should (eglot--glob-match "{**/*.d.ts,**/*.js,foo.[0-9]}" "foo.5"))
> +  ;; (should (eglot--glob-match "prefix/{**/*.d.ts,**/*.js,foo.[0-9]}" "prefix/foo.8"))
> +  )
> +
> +(defun eglot--call-with-tramp-test (fn)
> +  ;; Set up a loopback 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))
> +        (tramp-histfile-override t)
> +        (tramp-methods '(("loopback"
> +                          (tramp-login-program "/bin/sh")
> +                          (tramp-remote-shell "/bin/sh")
> +                          (tramp-remote-shell-login ("-l"))
> +                          (tramp-remote-shell-args ("-c")))))
> +        (temporary-file-directory (concat "/loopback::"
> +                                          temporary-file-directory)))
> +    ;; With ‘temporary-file-directory’ bound to the ‘loopback’ TRAMP
> +    ;; method, fixtures will be automatically made “remote".
> +    (unwind-protect
> +        (funcall fn)
> +      ;; Tramp leave some buffers behind, and some time later,
> +      ;; `project-buffers' will trip over them causing a hard to debug
> +      ;; intermittent test failure somewhere else.
> +      (dolist (buf (buffer-list))
> +        (when (string-match-p "^\\*tramp" (buffer-name buf))
> +          (kill-buffer buf))))))
> +
> +(ert-deftest eglot--tramp-test ()
> +  "Ensure LSP servers can be used over TRAMP."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--call-with-tramp-test #'eglot-tests--auto-detect-running-server-1))
> +
> +(ert-deftest eglot--tramp-test-2 ()
> +  "Ensure LSP servers can be used over TRAMP."
> +  (skip-unless (executable-find "clangd"))
> +  (eglot--call-with-tramp-test #'eglot-tests--lsp-abiding-column-1))
> +
> +(ert-deftest eglot--path-to-uri-windows ()
> +  (skip-unless (eq system-type 'windows-nt))
> +  (should (string-prefix-p "file:///"
> +                             (eglot--path-to-uri "c:/Users/Foo/bar.lisp")))
> +  (should (string-suffix-p "c%3A/Users/Foo/bar.lisp"
> +                           (eglot--path-to-uri "c:/Users/Foo/bar.lisp"))))
> +
> +(ert-deftest eglot--same-server-multi-mode ()
> +  "Check single LSP instance manages multiple modes in same project."
> +  (skip-unless (executable-find "clangd"))
> +  (let (server)
> +    (eglot--with-fixture
> +        `(("project" . (("foo.cpp" .
> +                         "#include \"foolib.h\"
> +                        int main() { return foo(); }")
> +                        ("foolib.h" .
> +                         "#ifdef __cplusplus\nextern \"C\" {\n#endif
> +                        int foo();
> +                        #ifdef __cplusplus\n}\n#endif")
> +                        ("foolib.c" .
> +                         "#include \"foolib.h\"
> +                        int foo() {return 42;}"))))
> +      (with-current-buffer
> +          (eglot--find-file-noselect "project/foo.cpp")
> +        (should (setq server (eglot--tests-connect))))
> +      (with-current-buffer
> +          (eglot--find-file-noselect "project/foolib.h")
> +        (should (eq (eglot-current-server) server)))
> +      (with-current-buffer
> +          (eglot--find-file-noselect "project/foolib.c")
> +        (should (eq (eglot-current-server) server))))))
> +
> +(provide 'eglot-tests)
> +;;; eglot-tests.el ends here
> +
> +;; Local Variables:
> +;; checkdoc-force-docstrings-flag: nil
> +;; End:



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 10:32   ` Michael Albinus
@ 2022-12-12 10:37     ` João Távora
  2022-12-13 19:10       ` Michael Albinus
  0 siblings, 1 reply; 23+ messages in thread
From: João Távora @ 2022-12-12 10:37 UTC (permalink / raw)
  To: Michael Albinus; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 551 bytes --]

> Do you really need this?

Flymake tells me there are 4 compilation warnings if I don't:

 1243   9 warning  e-f-b-c  Unused lexical variable `tramp-remote-path'
 1243  56 warning  e-f-b-c  reference to free variable ‘tramp-remote-path’
 1244   9 warning  e-f-b-c  Unused lexical variable
`tramp-histfile-override'
 1245   9 warning  e-f-b-c  Unused lexical variable `tramp-methods'

Here, the flymake backend 'e-f-b-c' (elisp-flymake-byte-compile) compiles
with Emacs -Q
and no specially relevant load path in these cases.

João

[-- Attachment #2: Type: text/html, Size: 731 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
       [not found] ` <20221212001223.E9A9DC004BE@vcs2.savannah.gnu.org>
  2022-12-12  8:34   ` emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot Stefan Kangas
  2022-12-12 10:32   ` Michael Albinus
@ 2022-12-12 10:59   ` Michael Albinus
  2022-12-12 11:03     ` João Távora
  2 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-12 10:59 UTC (permalink / raw)
  To: emacs-devel; +Cc: João Távora

João Távora <joaotavora@gmail.com> writes:

Hi João,

(sorry for sending this email accidentially when not finished)

> +(require 'tramp-sh)

Do you really need this?

> +(defun eglot--call-with-tramp-test (fn)
> +  ;; Set up a loopback 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))
> +        (tramp-histfile-override t)
> +        (tramp-methods '(("loopback"
> +                          (tramp-login-program "/bin/sh")
> +                          (tramp-remote-shell "/bin/sh")
> +                          (tramp-remote-shell-login ("-l"))
> +                          (tramp-remote-shell-args ("-c")))))
> +        (temporary-file-directory (concat "/loopback::"
> +                                          temporary-file-directory)))

Pls don't do this. The better choice is to use

     (let* ((temporary-file-directory ert-remote-temporary-file-directory) ...)

See for example test/lisp/filenotify-tests.el, macro file-notify--deftest-remote

> +      ;; Tramp leave some buffers behind, and some time later,
> +      ;; `project-buffers' will trip over them causing a hard to debug
> +      ;; intermittent test failure somewhere else.
> +      (dolist (buf (buffer-list))
> +        (when (string-match-p "^\\*tramp" (buffer-name buf))
> +          (kill-buffer buf))))))

Just killing the buffers is not sufficient. The better way is to call
tramp-cleanup-connection, like done in that macro of filenotfy-tests.el

> +(ert-deftest eglot--tramp-test ()
> +  "Ensure LSP servers can be used over TRAMP."
> +  (skip-unless (executable-find "clangd"))

> +(ert-deftest eglot--tramp-test-2 ()
> +  "Ensure LSP servers can be used over TRAMP."
> +  (skip-unless (executable-find "clangd"))

Shouldn't the test be performed with bound remote default-directory? By
this, you could test Tramp + Eglot also in other variatioons, like a
real ssh connection.

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 10:59   ` Michael Albinus
@ 2022-12-12 11:03     ` João Távora
  2022-12-12 11:08       ` Michael Albinus
  0 siblings, 1 reply; 23+ messages in thread
From: João Távora @ 2022-12-12 11:03 UTC (permalink / raw)
  To: Michael Albinus; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2513 bytes --]

Michael,

Feel free to do all these changes in the test/lisp/progmodes/eglot-tests.el

FWIW I didn't write many of these tests, but I didn't care to
import the full Git history as I did in lisp/progmodes/eglot.el.

João

On Mon, Dec 12, 2022 at 10:59 AM Michael Albinus <michael.albinus@gmx.de>
wrote:

> João Távora <joaotavora@gmail.com> writes:
>
> Hi João,
>
> (sorry for sending this email accidentially when not finished)
>
> > +(require 'tramp-sh)
>
> Do you really need this?
>
> > +(defun eglot--call-with-tramp-test (fn)
> > +  ;; Set up a loopback 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))
> > +        (tramp-histfile-override t)
> > +        (tramp-methods '(("loopback"
> > +                          (tramp-login-program "/bin/sh")
> > +                          (tramp-remote-shell "/bin/sh")
> > +                          (tramp-remote-shell-login ("-l"))
> > +                          (tramp-remote-shell-args ("-c")))))
> > +        (temporary-file-directory (concat "/loopback::"
> > +                                          temporary-file-directory)))
>
> Pls don't do this. The better choice is to use
>
>      (let* ((temporary-file-directory ert-remote-temporary-file-directory)
> ...)
>
> See for example test/lisp/filenotify-tests.el, macro
> file-notify--deftest-remote
>
> > +      ;; Tramp leave some buffers behind, and some time later,
> > +      ;; `project-buffers' will trip over them causing a hard to debug
> > +      ;; intermittent test failure somewhere else.
> > +      (dolist (buf (buffer-list))
> > +        (when (string-match-p "^\\*tramp" (buffer-name buf))
> > +          (kill-buffer buf))))))
>
> Just killing the buffers is not sufficient. The better way is to call
> tramp-cleanup-connection, like done in that macro of filenotfy-tests.el
>
> > +(ert-deftest eglot--tramp-test ()
> > +  "Ensure LSP servers can be used over TRAMP."
> > +  (skip-unless (executable-find "clangd"))
>
> > +(ert-deftest eglot--tramp-test-2 ()
> > +  "Ensure LSP servers can be used over TRAMP."
> > +  (skip-unless (executable-find "clangd"))
>
> Shouldn't the test be performed with bound remote default-directory? By
> this, you could test Tramp + Eglot also in other variatioons, like a
> real ssh connection.
>
> Best regards, Michael.
>


-- 
João Távora

[-- Attachment #2: Type: text/html, Size: 3460 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 11:03     ` João Távora
@ 2022-12-12 11:08       ` Michael Albinus
  0 siblings, 0 replies; 23+ messages in thread
From: Michael Albinus @ 2022-12-12 11:08 UTC (permalink / raw)
  To: João Távora; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

> Michael,

Hi João,

> Feel free to do all these changes in the
> test/lisp/progmodes/eglot-tests.el

In other test files, I haven't declared Tramp-specific tests. Instead,
I've used a macro to declare a new test based on an existing local
test. This is more flexible, and it would allow us to test much more on
remote servers, if we like.

Will try to do the same here.

> João

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12  9:52     ` João Távora
@ 2022-12-12 11:26       ` Stefan Kangas
  2022-12-12 11:56         ` João Távora
  0 siblings, 1 reply; 23+ messages in thread
From: Stefan Kangas @ 2022-12-12 11:26 UTC (permalink / raw)
  To: João Távora; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

> I'm going to assume this was on your machine from now on.

This is on my machine (Debian GNU/Linux).

> Can you explain why you have a "rust-analyzer" executable, but not
> "cargo"? Is this a usual Rust setup?

I don't do Rust myself, but when working on Eglot, I installed various
bits and pieces ("rust-analyzer" was among them; "cargo" was not).

> In other tests, such as 'javascript-basic',
> 'project-wide-diagnostics-typescript', the error is strange:
>
> (error "[eglot] -32603: Request initialize failed with message:
>   Could not find a valid tsserver version. Exiting.")

This link suggested that "npm install -g typescript" might fix this
error:

https://github.com/typescript-language-server/typescript-language-server/issues/336

So I did that, and now I have a "tsserver" executable as well, and the
test itself seems to be working.  I therefore suggest a fix predicating
this test on the existence of that executable.

modified   test/lisp/progmodes/eglot-tests.el
@@ -722,7 +722,8 @@ rust-on-type-formatting

 (ert-deftest javascript-basic ()
   "Test basic autocompletion in a JavaScript LSP."
-  (skip-unless (executable-find "typescript-language-server"))
+  (skip-unless (and (executable-find "typescript-language-server")
+                    (executable-find "tsserver")))
   (eglot--with-fixture
       '(("project" . (("hello.js" . "console.log('Hello world!');"))))
     (with-current-buffer
@@ -751,7 +752,8 @@ javascript-basic

 (ert-deftest project-wide-diagnostics-typescript ()
   "Test diagnostics through multiple files in a TypeScript LSP."
-  (skip-unless (executable-find "typescript-language-server"))
+  (skip-unless (and (executable-find "typescript-language-server")
+                    (executable-find "tsserver")))
   (eglot--with-fixture
       '(("project" . (("hello.ts" . "const thing = 5;\nexport { thin }")
                       ("hello2.ts" . "import { thing } from './hello'"))))

> In general, I don't know if it's a good idea to keep these tests in
> the normal 'make check' run, since many Eglot tests are dependent on
> having correctly working servers.  Or at least what I and other test
> authors believe to be "correctly configured".  Which is generally the
> simplest installation method for said servers, with the fewest bells
> and whistles.

Yup; the simplest installation method is what I did in each case.

BTW, I think some of the tests should perhaps be marked :expensive, too.

> Let me know if you've investigated your setup for these servers
> (again, I'm assuming this is on your machine and not EMBA).

As for the autopep8 test, I'm just using the system packages for
autopep8 and pylsp on Debian GNU/Linux bookworm/testing:

    $ autopep8 --version
    autopep8 2.0.0 (pycodestyle: 2.9.1)
    $ pylsp --version
    pylsp v1.5.0

Please let me know if you need anything else.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 11:26       ` Stefan Kangas
@ 2022-12-12 11:56         ` João Távora
  2022-12-12 21:52           ` Stefan Kangas
  0 siblings, 1 reply; 23+ messages in thread
From: João Távora @ 2022-12-12 11:56 UTC (permalink / raw)
  To: Stefan Kangas; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 716 bytes --]

Hi Stefan,

I think the extra predicates you suggest are fine,
feel free to push the changes.  I haven't actually
pushed the (skip-unless ... "cargo") commit because
of the ongoing repo issues.  If you can push, I invite
you to push that, too.

Maybe also adjust the autopep8 test expectation
to match your result.  Or maybe write the reformat
test using something stabler, like "clangd" if you have it.

As for the :expensive, what do you think makes a test
expensive.  The longest-running tests in my machine
take less than 3 seconds.  But I'm not running the
jtdls one (and I seem to remember that takes a bit
more time).  So here, too, fell free to add :expensive
tags to those tests.

João

[-- Attachment #2: Type: text/html, Size: 985 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 11:56         ` João Távora
@ 2022-12-12 21:52           ` Stefan Kangas
  2022-12-13  8:49             ` Michael Albinus
  0 siblings, 1 reply; 23+ messages in thread
From: Stefan Kangas @ 2022-12-12 21:52 UTC (permalink / raw)
  To: João Távora; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

> I think the extra predicates you suggest are fine,
> feel free to push the changes.

Thanks, done.

> Maybe also adjust the autopep8 test expectation
> to match your result.  Or maybe write the reformat
> test using something stabler, like "clangd" if you have it.

I changed it to accept both the new and the old output.

> As for the :expensive, what do you think makes a test
> expensive.  The longest-running tests in my machine
> take less than 3 seconds.  But I'm not running the
> jtdls one (and I seem to remember that takes a bit
> more time).  So here, too, fell free to add :expensive
> tags to those tests.

I'd basically consider any test over half a second or a second or so as
a candidate for being marked as :expensive.  But we should do it
judiciously, of course.  (And even better if we can find a way to make
slow tests faster.)

Below are the tests that take more than 1 second on my slow machine.
Perhaps `javascript-basic' is both unimportant and slow enough to be
worth changing to :expensive?  Maybe some others too; I don't know.

   passed   1/50  auto-detect-running-server (2.753144 sec)
   passed   2/50  auto-reconnect (2.349814 sec)
   FAILED   4/50  basic-completions (10.155676 sec) at
lisp/progmodes/eglot-tests.el:522
   passed   6/50  basic-xref (4.709764 sec)
   passed  12/50  eglot--tramp-test (2.158451 sec)
   passed  17/50  eglot-eldoc-after-completions (5.457839 sec)
   passed  20/50  eglot-multiline-eldoc (2.512865 sec)
   passed  32/50  eglot-single-line-eldoc (5.273028 sec)
-> passed  34/50  javascript-basic (5.474341 sec)
   passed  36/50  non-unique-completions (3.871629 sec)
   passed  38/50  project-wide-diagnostics-typescript (2.126554 sec)
   FAILED  39/50  python-autopep-formatting (1.370773 sec) at
lisp/progmodes/eglot-tests.el:659
   passed  45/50  slow-async-connection (2.220646 sec)
   passed  46/50  slow-sync-connection-intime (1.153952 sec)
   passed  47/50  slow-sync-connection-wait (1.158264 sec)
   passed  48/50  slow-sync-timeout (1.130622 sec)



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 21:52           ` Stefan Kangas
@ 2022-12-13  8:49             ` Michael Albinus
  2022-12-13 19:23               ` Stefan Kangas
  0 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-13  8:49 UTC (permalink / raw)
  To: Stefan Kangas; +Cc: João Távora, emacs-devel

Stefan Kangas <stefankangas@gmail.com> writes:

Hi Stefan,

>> As for the :expensive, what do you think makes a test
>> expensive.  The longest-running tests in my machine
>> take less than 3 seconds.  But I'm not running the
>> jtdls one (and I seem to remember that takes a bit
>> more time).  So here, too, fell free to add :expensive
>> tags to those tests.
>
> I'd basically consider any test over half a second or a second or so as
> a candidate for being marked as :expensive.  But we should do it
> judiciously, of course.  (And even better if we can find a way to make
> slow tests faster.)

That's a very restrictive rule. IIRC, we've discussed this a while ago,
and a recommendation was to regard tests lasting more than 10 seconds
being expensive. But I might remeber this wrong.

>    passed  12/50  eglot--tramp-test (2.158451 sec)

All eglot Tramp tests could be regarded as :expensive by default, I
guess.

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-12 10:37     ` João Távora
@ 2022-12-13 19:10       ` Michael Albinus
  2022-12-13 20:18         ` João Távora
  0 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-13 19:10 UTC (permalink / raw)
  To: João Távora; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

Hi João,

>> Do you really need this?
>
> Flymake tells me there are 4 compilation warnings if I don't:
>
>  1243   9 warning  e-f-b-c  Unused lexical variable
> `tramp-remote-path'
>  1243  56 warning  e-f-b-c  reference to free variable
> ‘tramp-remote-path’
>  1244   9 warning  e-f-b-c  Unused lexical variable
> `tramp-histfile-override'
>  1245   9 warning  e-f-b-c  Unused lexical variable `tramp-methods'
>
> Here, the flymake backend 'e-f-b-c' (elisp-flymake-byte-compile)
> compiles with Emacs -Q
> and no specially relevant load path in these cases.

I've fixed this in eglot-tests.el. I've also adapted the two tests wrt
Tramp. Pushed to the emacs-29 branch.

FTR, I'm suprised about the names of the test cases. Would it be
possible to give them a unique prefix, like `eglot-test-'?

> João

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-13  8:49             ` Michael Albinus
@ 2022-12-13 19:23               ` Stefan Kangas
  2022-12-14  9:55                 ` Michael Albinus
  0 siblings, 1 reply; 23+ messages in thread
From: Stefan Kangas @ 2022-12-13 19:23 UTC (permalink / raw)
  To: Michael Albinus; +Cc: João Távora, emacs-devel

Michael Albinus <michael.albinus@gmx.de> writes:

>> I'd basically consider any test over half a second or a second or so as
>> a candidate for being marked as :expensive.  But we should do it
>> judiciously, of course.  (And even better if we can find a way to make
>> slow tests faster.)
>
> That's a very restrictive rule. IIRC, we've discussed this a while ago,
> and a recommendation was to regard tests lasting more than 10 seconds
> being expensive. But I might remeber this wrong.

It's just a rule of thumb that I use to decide which tests to consider
marking.  We should of course not do that in a blanket fashion, because
we really do want some tests to always run, even if they take a long
time.

But the value of the test suite is decreased if it is slow by default,
as people will avoid running it.  We are already seeing a not
insignificant number of new test failures regularly introduced on master
and the release branch.  This suggests to me that this might already be
taking place.

(When dealing with automatic merging, as I do, a broken test suite is a
nuisance.  I don't have any hard data unfortunately, but over the last
3-6 months it feels to me like the test suite has been broken more often
than it's not.  Perhaps this observation could be checked against the
EMBA logs or similar.)

>>    passed  12/50  eglot--tramp-test (2.158451 sec)
>
> All eglot Tramp tests could be regarded as :expensive by default, I
> guess.

I don't know enough about Tramp to have an opinion, myself.  But please
do it if you think it makes sense, thanks.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-13 19:10       ` Michael Albinus
@ 2022-12-13 20:18         ` João Távora
  2022-12-14  9:57           ` Michael Albinus
  0 siblings, 1 reply; 23+ messages in thread
From: João Távora @ 2022-12-13 20:18 UTC (permalink / raw)
  To: Michael Albinus; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1321 bytes --]

On Tue, Dec 13, 2022, 19:10 Michael Albinus <michael.albinus@gmx.de> wrote:

> João Távora <joaotavora@gmail.com> writes:
>
> Hi João,
>
> >> Do you really need this?
> >
> > Flymake tells me there are 4 compilation warnings if I don't:
> >
> >  1243   9 warning  e-f-b-c  Unused lexical variable
> > `tramp-remote-path'
> >  1243  56 warning  e-f-b-c  reference to free variable
> > ‘tramp-remote-path’
> >  1244   9 warning  e-f-b-c  Unused lexical variable
> > `tramp-histfile-override'
> >  1245   9 warning  e-f-b-c  Unused lexical variable `tramp-methods'
> >
> > Here, the flymake backend 'e-f-b-c' (elisp-flymake-byte-compile)
> > compiles with Emacs -Q
> > and no specially relevant load path in these cases.
>
> I've fixed this in eglot-tests.el. I've also adapted the two tests wrt
> Tramp. Pushed to the emacs-29 branch.
>

Thanks. I'll have a look later.


> FTR, I'm suprised about the names of the test cases. Would it be
> possible to give them a unique prefix, like `eglot-test-'?
>

Yes, do that please. I think this makes sense, although the test symbols
aren't exactly bound to functions and it is a nuisance to always type this
long boring prefix. If only there was some namespacing mechanism in Emacs
that could alleviate that.. oh wait ;-)

João

>

[-- Attachment #2: Type: text/html, Size: 2319 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-13 19:23               ` Stefan Kangas
@ 2022-12-14  9:55                 ` Michael Albinus
  0 siblings, 0 replies; 23+ messages in thread
From: Michael Albinus @ 2022-12-14  9:55 UTC (permalink / raw)
  To: Stefan Kangas; +Cc: João Távora, emacs-devel

Stefan Kangas <stefankangas@gmail.com> writes:

Hi Stefan,

>> All eglot Tramp tests could be regarded as :expensive by default, I
>> guess.
>
> I don't know enough about Tramp to have an opinion, myself.  But please
> do it if you think it makes sense, thanks.

Tramp tests need to establish a remote connection first, so they run
slower by default. And in general, Tramp tests are not the essential
part of a test library.

I've tagged them accordingly in eglot-tests.el.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-13 20:18         ` João Távora
@ 2022-12-14  9:57           ` Michael Albinus
  2022-12-14 12:58             ` João Távora
  0 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-14  9:57 UTC (permalink / raw)
  To: João Távora; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

Hi João,

>     FTR, I'm suprised about the names of the test cases. Would it be
>     possible to give them a unique prefix, like `eglot-test-'?
>
> Yes, do that please. I think this makes sense, although the test
> symbols aren't exactly bound to functions and it is a nuisance to
> always type this long boring prefix. If only there was some
> namespacing mechanism in Emacs that could alleviate that.. oh wait ;-)

I've renamed the tests. But there is no need to type the long test
names, use completion. Or use a regexp for a selection of tests.

> João

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-14  9:57           ` Michael Albinus
@ 2022-12-14 12:58             ` João Távora
  2022-12-14 14:11               ` Michael Albinus
  0 siblings, 1 reply; 23+ messages in thread
From: João Távora @ 2022-12-14 12:58 UTC (permalink / raw)
  To: Michael Albinus; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 3654 bytes --]

On Wed, Dec 14, 2022 at 9:57 AM Michael Albinus <michael.albinus@gmx.de>
wrote:
> > >  Is it possible to give them a unique prefix, like `eglot-test-'?
> >
> > Yes, do that please. I think this makes sense, although the test
> > symbols aren't exactly bound to functions and it is a nuisance to
> > always type this long boring prefix. If only there was some
> > namespacing mechanism in Emacs that could alleviate that.. oh wait ;-)
>
> I've renamed the tests.

Thanks.  But shouldn't it be "eglot-tests" (with trailing 's') to match the
file name eglot-tests.el?  I'm sorry I didn't notice this before.

Also another comment about your earlier commit.

+        (temporary-file-directory ert-remote-temporary-file-directory))
+    ;; We must check the remote LSP server.  So far, just "clangd" is used.
+    (let ((default-directory temporary-file-directory))
+      (unless (executable-find "clangd" 'remote)
+        (ert-skip "Remote clangd not found")))
+    (funcall fn)))

I don't think it's correct to check for "clangd" specifically in
'eglot--call-with-tramp-test', because that restricts its usefulness.

Isn't the following better and still correct?

diff --git a/test/lisp/progmodes/eglot-tests.el
b/test/lisp/progmodes/eglot-tests.el
index d8c9560f5b..509e7e7d82 100644
--- a/test/lisp/progmodes/eglot-tests.el
+++ b/test/lisp/progmodes/eglot-tests.el
@@ -1265,20 +1265,25 @@ eglot--call-with-tramp-test
          (tramp-verbose 1)
          (temporary-file-directory ert-remote-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)))

+(cl-defmacro eglot--with-tramp-test (() &body body)
+  (declare (indent 1) (debug t))
+  `(eglot--call-with-tramp-test (lambda () ,@body)))
+
 (ert-deftest eglot-test-tramp-test ()
   "Ensure LSP servers can be used over TRAMP."
   :tags '(:expensive-test)
-  (eglot--call-with-tramp-test
#'eglot-tests--auto-detect-running-server-1))
+  (eglot--with-tramp-test ()
+    (skip-unless (executable-find "clangd" 'remote))
+    (eglot-tests--auto-detect-running-server-1)))

 (ert-deftest eglot-test-tramp-test-2 ()
   "Ensure LSP servers can be used over TRAMP."
   :tags '(:expensive-test)
-  (eglot--call-with-tramp-test #'eglot-tests--lsp-abiding-column-1))
+  (eglot--with-tramp-test ()
+    (skip-unless (executable-find "clangd" 'remote))
+    (eglot-tests--lsp-abiding-column-1)))

 (ert-deftest eglot-test-path-to-uri-windows ()
   (skip-unless (eq system-type 'windows-nt))

> But there is no need to type the long test
> names, use completion.

In general completion systems can't guess that I want to make
a test `eglot-test-a-new-test-im-about-to-write`.  And there's also
the clutter of overly long names which seriously impairs readability.
This is a longstanding problem and recurring argument here.  I won't
go into it except to note that if you were to apply this patch:

@@ -1317,4 +1317,5 @@ eglot-test-same-server-multi-mode

 ;; Local Variables:
 ;; checkdoc-force-docstrings-flag: nil
+;; read-symbol-shorthands: (("et-" . "eglot-tests-"))
 ;; End:

then you could just type _and_ read `et-` every time and
the symbol et-foo would be read and interned as
eglot-tests-foo.  And you could keep longhand mentions
to eglot-tests-foo in there as well, and they would also map
to the same symbol.

But this is not allowed practice yet in the Emacs repo.
Unfortunately, IMO.

João

[-- Attachment #2: Type: text/html, Size: 4301 bytes --]

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-14 12:58             ` João Távora
@ 2022-12-14 14:11               ` Michael Albinus
  2022-12-14 15:46                 ` João Távora
  0 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-14 14:11 UTC (permalink / raw)
  To: João Távora; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

Hi João,

> Thanks.  But shouldn't it be "eglot-tests" (with trailing 's') to
> match the 
> file name eglot-tests.el?  I'm sorry I didn't notice this before.

Both conventions are used: <package>-test-FOO and <package>-tests-FOO.
In the test/ subdirectory, (dired-do-find-regexp "deftest.*-tests-")
returns 1073 lines, and (dired-do-find-regexp "deftest.*-test-") returns
1245 lines. If you prefer the "eglot-tests" prefix, rename it (I really
don't care :-)

> Also another comment about your earlier commit.
>
> +        (temporary-file-directory
> ert-remote-temporary-file-directory))
> +    ;; We must check the remote LSP server.  So far, just "clangd" is
> used.
> +    (let ((default-directory temporary-file-directory))
> +      (unless (executable-find "clangd" 'remote)
> +        (ert-skip "Remote clangd not found")))
> +    (funcall fn)))
>
> I don't think it's correct to check for "clangd" specifically in
> 'eglot--call-with-tramp-test', because that restricts its usefulness.

Yes. That's why I have added the comment. As soon as we check for other
LSP servers but "clangd", it must be changed.

> Isn't the following better and still correct?
>
> diff --git a/test/lisp/progmodes/eglot-tests.el
> b/test/lisp/progmodes/eglot-tests.el
> index d8c9560f5b..509e7e7d82 100644
> --- a/test/lisp/progmodes/eglot-tests.el
> +++ b/test/lisp/progmodes/eglot-tests.el
> @@ -1265,20 +1265,25 @@ eglot--call-with-tramp-test
>           (tramp-verbose 1)
>           (temporary-file-directory
> ert-remote-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)))
>  
> +(cl-defmacro eglot--with-tramp-test (() &body body)
> +  (declare (indent 1) (debug t))
> +  `(eglot--call-with-tramp-test (lambda () ,@body)))
> +
>  (ert-deftest eglot-test-tramp-test ()
>    "Ensure LSP servers can be used over TRAMP."
>    :tags '(:expensive-test)
> -  (eglot--call-with-tramp-test
> #'eglot-tests--auto-detect-running-server-1))
> +  (eglot--with-tramp-test ()
> +    (skip-unless (executable-find "clangd" 'remote))
> +    (eglot-tests--auto-detect-running-server-1)))
>  
>  (ert-deftest eglot-test-tramp-test-2 ()
>    "Ensure LSP servers can be used over TRAMP."
>    :tags '(:expensive-test)
> -  (eglot--call-with-tramp-test #'eglot-tests--lsp-abiding-column-1))
> +  (eglot--with-tramp-test ()
> +    (skip-unless (executable-find "clangd" 'remote))
> +    (eglot-tests--lsp-abiding-column-1)))
>  
>  (ert-deftest eglot-test-path-to-uri-windows ()
>    (skip-unless (eq system-type 'windows-nt))

Might work. You could also remove function eglot--call-with-tramp-test,
and move its body into the macro eglot--with-tramp-test. I don't know
whether skip-unless then still works (it is applicable inside
ert-deftest only), but you could replace its call by ert-skip as I have done.

> I won't go into it except to note that if you were to apply this
> patch:
>
> @@ -1317,4 +1317,5 @@ eglot-test-same-server-multi-mode
>  
>  ;; Local Variables:
>  ;; checkdoc-force-docstrings-flag: nil
> +;; read-symbol-shorthands: (("et-" . "eglot-tests-"))
>  ;; End:
>
> then you could just type _and_ read `et-` every time and
> the symbol et-foo would be read and interned as 
> eglot-tests-foo.  And you could keep longhand mentions
> to eglot-tests-foo in there as well, and they would also map
> to the same symbol.
>
> But this is not allowed practice yet in the Emacs repo.
> Unfortunately, IMO.

I've followed the discussion about shorthands a little bit, but I have
no opinion about it.

> João

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-14 14:11               ` Michael Albinus
@ 2022-12-14 15:46                 ` João Távora
  2022-12-14 17:41                   ` Stefan Monnier
  2022-12-14 21:42                   ` Stefan Kangas
  0 siblings, 2 replies; 23+ messages in thread
From: João Távora @ 2022-12-14 15:46 UTC (permalink / raw)
  To: Michael Albinus; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1677 bytes --]

On Wed, Dec 14, 2022 at 2:11 PM Michael Albinus <michael.albinus@gmx.de>
wrote:

> Both conventions are used: <package>-test-FOO and <package>-tests-FOO.
> In the test/ subdirectory, (dired-do-find-regexp "deftest.*-tests-")
> returns 1073 lines, and (dired-do-find-regexp "deftest.*-test-") returns
> 1245 lines. If you prefer the "eglot-tests" prefix, rename it (I really
> don't care :-)

OK.  I don't know either, I'll decide later.  I'll just mention if we
used shorthands, we could align conventions effortlessly
:-D.  That's another annoyance about these prefix systems:
near-endless repetition and hard maintenance.

> > -  (eglot--call-with-tramp-test #'eglot-tests--lsp-abiding-column-1))
> > +  (eglot--with-tramp-test ()
> > +    (skip-unless (executable-find "clangd" 'remote))
> > +    (eglot-tests--lsp-abiding-column-1)))
> >
> >  (ert-deftest eglot-test-path-to-uri-windows ()
> >    (skip-unless (eq system-type 'windows-nt))
>
> Might work. You could also remove function eglot--call-with-tramp-test,
> and move its body into the macro eglot--with-tramp-test.

I could, but I really do like the WITH-FOO/CALL-WITH-FOO
convention. Among other benefits like easier hygiene it
means that recompiling the function is all I need to have it
take effect everywhere.  With macros you have to recompile
all the expanders.  Only worth it for very performance sensitive
code where you can't afford the funcall. That's quite rare IME.

> I don't know
> whether skip-unless then still works (it is applicable inside
> ert-deftest only), but you could replace its call by ert-skip as I have
done.

Yes, probably.  No biggie.

João

[-- Attachment #2: Type: text/html, Size: 2121 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-14 15:46                 ` João Távora
@ 2022-12-14 17:41                   ` Stefan Monnier
  2022-12-14 21:42                   ` Stefan Kangas
  1 sibling, 0 replies; 23+ messages in thread
From: Stefan Monnier @ 2022-12-14 17:41 UTC (permalink / raw)
  To: João Távora; +Cc: Michael Albinus, emacs-devel

> I could, but I really do like the WITH-FOO/CALL-WITH-FOO
> convention. Among other benefits like easier hygiene it
> means that recompiling the function is all I need to have it
> take effect everywhere.  With macros you have to recompile
> all the expanders.  Only worth it for very performance sensitive
> code where you can't afford the funcall. That's quite rare IME.

+1


        Stefan




^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-14 15:46                 ` João Távora
  2022-12-14 17:41                   ` Stefan Monnier
@ 2022-12-14 21:42                   ` Stefan Kangas
  2022-12-15  9:12                     ` Michael Albinus
  1 sibling, 1 reply; 23+ messages in thread
From: Stefan Kangas @ 2022-12-14 21:42 UTC (permalink / raw)
  To: João Távora, Michael Albinus; +Cc: emacs-devel

João Távora <joaotavora@gmail.com> writes:

> On Wed, Dec 14, 2022 at 2:11 PM Michael Albinus <michael.albinus@gmx.de>
> wrote:
>
>> Both conventions are used: <package>-test-FOO and <package>-tests-FOO.
>> In the test/ subdirectory, (dired-do-find-regexp "deftest.*-tests-")
>> returns 1073 lines, and (dired-do-find-regexp "deftest.*-test-") returns
>> 1245 lines. If you prefer the "eglot-tests" prefix, rename it (I really
>> don't care :-)
>
> OK.  I don't know either, I'll decide later.

More conventions are in use than that, and I couldn't confidently say
which is the preferred one.  Probably it's not that important, as long
as every file consistently uses one or the other.

FWIW, I don't see any major benefit to having "-test" in the test name,
as the fact that it's a test is immediately obvious in all contexts
where you see them.  So it mostly just makes the test names longer.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-14 21:42                   ` Stefan Kangas
@ 2022-12-15  9:12                     ` Michael Albinus
  2022-12-15 12:08                       ` João Távora
  0 siblings, 1 reply; 23+ messages in thread
From: Michael Albinus @ 2022-12-15  9:12 UTC (permalink / raw)
  To: Stefan Kangas; +Cc: João Távora, emacs-devel

Stefan Kangas <stefankangas@gmail.com> writes:

Hi Stefan,

> More conventions are in use than that, and I couldn't confidently say
> which is the preferred one.  Probably it's not that important, as long
> as every file consistently uses one or the other.

Yes.

> FWIW, I don't see any major benefit to having "-test" in the test name,
> as the fact that it's a test is immediately obvious in all contexts
> where you see them.  So it mostly just makes the test names longer.

For me, who doesn't know the eglot codebase, it is helpful to see,
whether a Lisp object belongs to eglot or eglot-tests.

Prior my changes, there were even the defun `eglot-lsp-abiding-column'
and the ert-deftest `eglot-lsp-abiding-column'. Terrible!

Best regards, Michael.



^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot
  2022-12-15  9:12                     ` Michael Albinus
@ 2022-12-15 12:08                       ` João Távora
  0 siblings, 0 replies; 23+ messages in thread
From: João Távora @ 2022-12-15 12:08 UTC (permalink / raw)
  To: Michael Albinus; +Cc: Stefan Kangas, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1063 bytes --]

On Thu, Dec 15, 2022 at 9:12 AM Michael Albinus <michael.albinus@gmx.de>
wrote:

> FWIW, I don't see any major benefit to having "-test" in the test name,
> > as the fact that it's a test is immediately obvious in all contexts
> > where you see them.  So it mostly just makes the test names longer.
>
> For me, who doesn't know the eglot codebase, it is helpful to see,
> whether a Lisp object belongs to eglot or eglot-tests.
>
> Prior my changes, there were even the defun `eglot-lsp-abiding-column'
> and the ert-deftest `eglot-lsp-abiding-column'. Terrible!


FWIW this doesn't seem "terrible!" to me at all.  The way I (and those
who teached me) see Lisp, symbols can have multiple bindings in
different domains.  So the same symbol may have a function binding,
a value binding and why not a test binding? A unit-test for testing a
given function may well be named after the function it is testing.

Namespacing is a good alternative to separate domains, sure, but
namespacing in Emacs is... err... not without its challenges.

João

[-- Attachment #2: Type: text/html, Size: 1554 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2022-12-15 12:08 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <167080394233.4339.5863353994038854528@vcs2.savannah.gnu.org>
     [not found] ` <20221212001223.E9A9DC004BE@vcs2.savannah.gnu.org>
2022-12-12  8:34   ` emacs-29 9c0d7bb73b 2/2: Add automated tests for Eglot Stefan Kangas
2022-12-12  9:52     ` João Távora
2022-12-12 11:26       ` Stefan Kangas
2022-12-12 11:56         ` João Távora
2022-12-12 21:52           ` Stefan Kangas
2022-12-13  8:49             ` Michael Albinus
2022-12-13 19:23               ` Stefan Kangas
2022-12-14  9:55                 ` Michael Albinus
2022-12-12 10:32   ` Michael Albinus
2022-12-12 10:37     ` João Távora
2022-12-13 19:10       ` Michael Albinus
2022-12-13 20:18         ` João Távora
2022-12-14  9:57           ` Michael Albinus
2022-12-14 12:58             ` João Távora
2022-12-14 14:11               ` Michael Albinus
2022-12-14 15:46                 ` João Távora
2022-12-14 17:41                   ` Stefan Monnier
2022-12-14 21:42                   ` Stefan Kangas
2022-12-15  9:12                     ` Michael Albinus
2022-12-15 12:08                       ` João Távora
2022-12-12 10:59   ` Michael Albinus
2022-12-12 11:03     ` João Távora
2022-12-12 11:08       ` Michael Albinus

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.