Skip to content

Commit 15eaf42

Browse files
ikappakichaos
andauthored
Correct signal descriptions in nrepl server sentinel (#2984) (#2984)
This is a workaround for Emacs 27.1 bug#46284 on Windows. Co-authored-by: chaos <chaos@localhost>
1 parent ae03e85 commit 15eaf42

File tree

5 files changed

+296
-2
lines changed

5 files changed

+296
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
### Bugs fixed
1010

11+
* [#2983](https://github.com/clojure-emacs/cider/issues/2983): update signal description in nrepl server sentinel as a workaround for emacs bug #46284 affecting v27.1 on windows
1112
* [#2941](https://github.com/clojure-emacs/cider/issues/2941): Use main args in alias for clojure cli
1213
* [#2953](https://github.com/clojure-emacs/cider/issues/2953): Don't font-lock function/macro vars as vars.
1314
* [#2964](https://github.com/clojure-emacs/cider/issues/2964): Fixes issue with `cider-company-enable-fuzzy-completion` and Helm

nrepl-client.el

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,13 @@ been determined."
10631063
(when nrepl-on-port-callback
10641064
(funcall nrepl-on-port-callback (process-buffer process)))))))))
10651065

1066+
(defmacro emacs-bug-46284/when-27.1-windows-nt (&rest body)
1067+
"Only evaluate BODY when Emacs bug #46284 has been detected."
1068+
(when (and (eq system-type 'windows-nt)
1069+
(string= emacs-version "27.1"))
1070+
(cons 'progn body)))
1071+
1072+
10661073
(declare-function cider--close-connection "cider-connection")
10671074
(defun nrepl-server-sentinel (process event)
10681075
"Handle nREPL server PROCESS EVENT."
@@ -1075,6 +1082,19 @@ been determined."
10751082
(with-current-buffer server-buffer
10761083
(buffer-substring (point-min) (point-max)))
10771084
"")))
1085+
(emacs-bug-46284/when-27.1-windows-nt
1086+
;; There is a bug in emacs 27.1 (since fixed) that sets all EVENT
1087+
;; descriptions for signals to "unknown signal". We correct this by
1088+
;; reseting it back to its canonical value.
1089+
(when (eq (process-status process) 'signal)
1090+
(cl-case (process-exit-status process)
1091+
;; SIGHUP==1 emacs nt/inc/ms-w32.h
1092+
(1 (setq event "Hangup"))
1093+
;; SIGINT==2 x86_64-w64-mingw32/include/signal.h
1094+
(2 (setq event "Interrupt"))
1095+
;; SIGKILL==9 emacs nt/inc/ms-w32.h
1096+
(9 (setq event "Killed")))))
1097+
10781098
(when server-buffer
10791099
(kill-buffer server-buffer))
10801100
(cond

test/nrepl-client-tests.el

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,20 @@
2929

3030
(require 'buttercup)
3131
(require 'nrepl-client)
32+
(require 'nrepl-tests-utils)
3233

3334
(describe "nrepl-server-buffer-name"
34-
:var (nrepl-hide-special-buffers params default-directory
35+
:var (nrepl-hide-special-buffers params default-directory-backup
3536
cider-session-name-template)
3637
(before-all
38+
(setq default-directory-backup default-directory)
3739
(setq default-directory "/path/to/dirA/")
3840
(setq params '(:host "localhost" :port 1))
3941
(setq cider-session-name-template "%J:%h:%p"))
4042

43+
(after-all
44+
(setq default-directory default-directory-backup))
45+
4146
(describe "when nrepl-hide-special-buffers is t"
4247
(it "returns the name of the server buffer, which hides it in buffer changing commands"
4348
(setq nrepl-hide-special-buffers t
@@ -68,10 +73,15 @@
6873
'("2" "39f630b9-9545-4ea0-860e-9846681d0741" ("done")))))
6974

7075
(describe "nrepl-make-buffer-name"
71-
:var (default-directory cider-session-name-template)
76+
:var (default-directory-backup cider-session-name-template)
7277
(before-all
78+
(setq default-directory-backup default-directory)
7379
(setq default-directory "/path/to/dirA/")
7480
(setq cider-session-name-template "%J:%h:%p"))
81+
82+
(after-all
83+
(setq default-directory default-directory-backup))
84+
7585
(it "generates a buffer name from the given template"
7686
(let ((params '(:host "localhost" :port 1)))
7787
(expect (nrepl-make-buffer-name "*buff-name %s*" params)
@@ -100,3 +110,59 @@
100110
:to-equal "*buff-name (cljs)*")
101111
(expect (nrepl-make-buffer-name "*buff-name %r:%S*" params)
102112
:to-equal "*buff-name cljs*")))))
113+
114+
(describe "nrepl-client-lifecycle"
115+
(it "start and stop nrepl client process"
116+
117+
;; start mock server
118+
(let* ((server-buffer (get-buffer-create ":nrepl-lifecycle/server"))
119+
(server-endpoint nil)
120+
(server-process (nrepl-start-server-process
121+
default-directory
122+
(nrepl-server-mock-invocation-string)
123+
124+
(lambda (endpoint)
125+
(setq server-endpoint nrepl-endpoint)
126+
server-buffer))))
127+
128+
;; server up and running
129+
(nrepl-tests-sleep-until 2 (eq (process-status server-process) 'run))
130+
(expect (process-status server-process)
131+
:to-equal 'run)
132+
133+
;; server has reported its endpoint
134+
(nrepl-tests-sleep-until 2 server-endpoint)
135+
(expect server-endpoint :not :to-be nil)
136+
137+
(condition-case error-details
138+
;; start client process
139+
(let* ((client-buffer (get-buffer-create ":nrepl-lifecycle/client"))
140+
(process-client (nrepl-start-client-process
141+
(plist-get server-endpoint :host)
142+
(plist-get server-endpoint :port)
143+
server-process
144+
(lambda (client-endpoint)
145+
client-buffer))))
146+
147+
;; client connection is open
148+
(expect (process-status process-client)
149+
:to-equal 'open)
150+
151+
;; provide some slack for server process to settle down
152+
(sleep-for 0.2)
153+
154+
;; exit client
155+
(delete-process process-client)
156+
157+
;; server process has been signalled
158+
(nrepl-tests-sleep-until 4 (eq (process-status server-process)
159+
'signal))
160+
(expect (process-status server-process)
161+
:to-equal 'signal))
162+
(error
163+
;; there may be some useful information in the nrepl buffer on error
164+
(when-let ((nrepl-error-buffer (get-buffer "*nrepl-error*")))
165+
(with-current-buffer nrepl-error-buffer
166+
(message ":nrepl-lifecycle/error %s" (buffer-string))))
167+
(error error-details))))))
168+

test/nrepl-server-mock.el

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
;; nrepl-server-mock.el
2+
3+
;; Copyright © 2021 Ioannis Kappas
4+
5+
;; This file is NOT part of GNU Emacs.
6+
7+
;; This program is free software: you can redistribute it and/or
8+
;; modify it under the terms of the GNU General Public License as
9+
;; published by the Free Software Foundation, either version 3 of the
10+
;; License, or (at your option) any later version.
11+
;;
12+
;; This program is distributed in the hope that it will be useful, but
13+
;; WITHOUT ANY WARRANTY; without even the implied warranty of
14+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
;; General Public License for more details.
16+
;;
17+
;; You should have received a copy of the GNU General Public License
18+
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
19+
20+
;;; Commentary:
21+
22+
;; This file is part of CIDER
23+
;;
24+
;; A mock nREPL server that sends dummy replies back to clients with just enough
25+
;; information onboard to accommodate testing requirements.
26+
;;
27+
;; Meant to be invoked as the top-level fn of an emacs subprocess.
28+
29+
;;; Code:
30+
31+
(require 'nrepl-client)
32+
(require 'nrepl-tests-utils)
33+
(require 'queue)
34+
35+
(defun nrepl-server-mock-filter (proc output)
36+
"Handle the nREPL message found in OUTPUT sent by the client
37+
PROC. Minimal implementation, just enough for fulfilling clients' testing
38+
requirements."
39+
(mock/log! ":mock.filter/output %s :msg %s" proc output)
40+
41+
(condition-case error-details
42+
(let* ((msg (queue-dequeue (cdr (nrepl-bdecode output))))
43+
(response (pcase msg
44+
(`(dict "op" "clone" "id" ,id)
45+
`(dict "id" ,id
46+
"session" "a-session"
47+
"status" ("done")
48+
"new-session" "a-new-session"))
49+
50+
(`(dict "op" "describe" "session" ,session "id" ,id)
51+
`(dict "id" ,id "session" ,session "status"
52+
("done"))))))
53+
54+
(mock/log! ":mock.filter/msg :in %s :out %s" msg response)
55+
(if (not response)
56+
(progn
57+
(mock/log! ":mock.filter/unsupported-msg :in %s :msg %s"
58+
output msg)
59+
(error ":mock.filter/unsupported-msg %s" output))
60+
61+
(progn
62+
(mock/log! ":mock.filter/response-sending... %s" response)
63+
(process-send-string proc (nrepl-bencode response)))))
64+
65+
(error
66+
(mock/log! ":mock.filter/fatal-error %s" error-details)
67+
(error error-details))
68+
69+
70+
))
71+
72+
(defun nrepl-server-mock-start ()
73+
"Start a mock nREPL server process. Prints out nREPL welcome message of
74+
the port and host it is started on. Exits after a 10 secs"
75+
76+
;; change first argument to non-nil to enable logging to file
77+
(nrepl-tests-log/init! nil mock "./nrepl-server-mock.log" 'new)
78+
(mock/log! ":mock/starting...")
79+
80+
(let* ((server-process (make-network-process
81+
:name "server-mock/process"
82+
:server 't
83+
:host 'local
84+
;; listen to an unoccupied port
85+
:service 't
86+
:buffer "server-mock/buffer"
87+
:filter 'nrepl-server-mock-filter
88+
:sentinel
89+
(lambda (proc status-change-descr)
90+
(mock/log! ":mock/process-status %s" status-change-descr))))
91+
(contact (process-contact server-process 't))
92+
(mock-message (format "nREPL server started on port %d on host %s"
93+
(plist-get contact :service)
94+
(plist-get contact :host))))
95+
;; print welcome message
96+
(message "%s%s" mock-message
97+
(when (eq system-type 'windows-nt)
98+
;; emacs bug #46388, emacs --batch's stderr is buffered under
99+
;; windows when not attached directly to the console; feed enough
100+
;; padding chars to flush the message out.
101+
;;
102+
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=46388
103+
(make-string (- 4096 (length mock-message)) ?*)))
104+
(sleep-for 10)
105+
(mock/log! ":mock/exiting...")))

test/utils/nrepl-tests-utils.el

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
;;; nrepl-test-utils.el
2+
3+
;; Copyright © 2021 Ioannis Kappas
4+
5+
;; This file is NOT part of GNU Emacs.
6+
7+
;; This program is free software: you can redistribute it and/or
8+
;; modify it under the terms of the GNU General Public License as
9+
;; published by the Free Software Foundation, either version 3 of the
10+
;; License, or (at your option) any later version.
11+
;;
12+
;; This program is distributed in the hope that it will be useful, but
13+
;; WITHOUT ANY WARRANTY; without even the implied warranty of
14+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
;; General Public License for more details.
16+
;;
17+
;; You should have received a copy of the GNU General Public License
18+
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
19+
20+
;;; Commentary:
21+
22+
;; useful utils for nREPL testing
23+
24+
;; This file is part of CIDER
25+
26+
;;; Code:
27+
28+
(defmacro nrepl-tests-log/init! (enable? name log-filename &optional clean?)
29+
"When ENABLE? is true, create a NAME/log! elisp function to log messages to LOG-FILENAME,
30+
taking the same arguments as `message'. Messages are appended to
31+
LOG-FILENAME, beginning with a :timestamp and followed up with :NAME. When
32+
CLEAN? is true remove LOG-FILENAME.
33+
34+
If ENABLE? is nil, NAME/log! function is a nil macro discarding all
35+
arguments unevaluated.
36+
37+
This logger fn is written in mind with multiple processes writing to the
38+
the same file, each having a unique name, in order to capture the order of
39+
events (e.g. a nREPL client process and mock server process writing to the
40+
same file).
41+
"
42+
(let* ((log-file-path (file-truename log-filename))
43+
(name-string (symbol-name name))
44+
(log-symbol (intern (concat name-string "/log!"))))
45+
(if enable?
46+
`(progn
47+
(when ,clean?
48+
(delete-file ,log-file-path))
49+
(defun ,log-symbol (fmt &rest rest)
50+
(let ((create-lockfiles nil)) ;; don't create lock files
51+
(write-region (apply 'format (concat "%s :%s " fmt "\n")
52+
(format-time-string "%H:%M:%S.%6N")
53+
,name-string
54+
rest)
55+
nil ,log-file-path 'append))))
56+
57+
;; send to the abyss!
58+
`(defmacro ,log-symbol (fmt &rest rest)
59+
'()))))
60+
61+
(defmacro nrepl-tests-sleep-until (timeout-secs condition)
62+
"Sleep for up to TIMEOUT-SECS or until CONDITION becomes true. It wakes
63+
up every 0.2 seconds to check for CONDITION."
64+
(let* ((interval-secs 0.2)
65+
(count (truncate (/ timeout-secs interval-secs))))
66+
`(cl-loop repeat ,count
67+
until ,condition
68+
do (sleep-for ,interval-secs))))
69+
70+
(defun nrepl-server-mock-invocation-string ()
71+
"Return a shell command that can be used by nrepl-start-srever-process to
72+
invoke the mock nREPL server. The command will invoke emacs in --batch mode
73+
using the same load path, version and user package as the parent emacs
74+
calling process."
75+
;; try to use the same executable and user dirs as eldev
76+
(concat "\"" (substring-no-properties (car command-line-args)) "\""
77+
" -Q --batch"
78+
79+
;; make sure to initialise packages
80+
;; so that the server can reference them.
81+
" --eval \""
82+
"(progn "
83+
" (setf package-user-dir"
84+
" \\\"" package-user-dir "\\\""
85+
86+
" load-path "
87+
;; maintain double quotes around paths,
88+
;; and also escape them with \
89+
" '" (replace-regexp-in-string
90+
"\"" (regexp-quote "\\\"") (prin1-to-string load-path))
91+
92+
" user-emacs-directory"
93+
" \\\"" user-emacs-directory "\\\""
94+
" )"
95+
" (package-initialize))"
96+
"\""
97+
98+
;; invoke mock server
99+
" -l test/nrepl-server-mock.el -f nrepl-server-mock-start"))
100+
101+
102+
(provide 'nrepl-tests-utils)

0 commit comments

Comments
 (0)