mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-04 15:41:37 -05:00
Update callback interface
- Rename `jupyter-received-message-types` to `jupyter-message-types` and add message types for requests as well. Also change the keys to keywords instead of symbols. Using plists with keywords is in line with `jupyter-messages` and the arguments of the jupyter request functions. - Rename `jupyter-request-run-callbacks` to `jupyter--run-callbacks`. This is more of an internal function so mark it as such. - Change the order of the first two arguments in `jupyter-add-callback` and `jupyter-wait-until`. In both cases you are adding a callback to a request or waiting for some condition to be satisfied on the request not on the message type. This is also the reason why `jupyter-wait-until-received` keeps the message type as the first argument. We are waiting until a message of a certain type is received for a request, but the more important object in this case is the message type. - Update other files to take into account these changes.
This commit is contained in:
parent
d1f57831fe
commit
4944a5d75a
4 changed files with 141 additions and 140 deletions
|
@ -15,30 +15,37 @@
|
|||
:control zmq-DEALER)
|
||||
"The socket types for the various channels used by `jupyter'.")
|
||||
|
||||
(defconst jupyter-received-message-types
|
||||
(list 'execute-result "execute_result"
|
||||
'execute-reply "execute_reply"
|
||||
'inspect-reply "inspect_reply"
|
||||
'complete-reply "complete_reply"
|
||||
'history-reply "history_reply"
|
||||
'is-complete-reply "is_complete_reply"
|
||||
'comm-info-reply "comm_info_reply"
|
||||
'kernel-info-reply "kernel_info_reply"
|
||||
'shutdown-reply "shutdown_reply"
|
||||
'interrupt-reply "interrupt_reply"
|
||||
'stream "stream"
|
||||
'display-data "display_data"
|
||||
'update-display-data "update_display_data"
|
||||
'execute-input "execute_input"
|
||||
'error "error"
|
||||
'status "status"
|
||||
'clear-output "clear_output"
|
||||
'input-reply "input_reply")
|
||||
"A plist mapping symbols to received message types.
|
||||
This is used to give some protection against invalid message
|
||||
types in `jupyter-add-callback'. If the MSG-TYPE argument of
|
||||
`jupyter-add-callback' does not match one of the keys in this
|
||||
plist, an error is thrown.")
|
||||
(defconst jupyter-message-types
|
||||
(list :execute-result "execute_result"
|
||||
:execute-request "execute_request"
|
||||
:execute-reply "execute_reply"
|
||||
:inspect-request "inspect_request"
|
||||
:inspect-reply "inspect_reply"
|
||||
:complete-request "complete_request"
|
||||
:complete-reply "complete_reply"
|
||||
:history-request "history_request"
|
||||
:history-reply "history_reply"
|
||||
:is-complete-request "is_complete_request"
|
||||
:is-complete-reply "is_complete_reply"
|
||||
:comm-info-request "comm_info_request"
|
||||
:comm-info-reply "comm_info_reply"
|
||||
:kernel-info-request "kernel_info_request"
|
||||
:kernel-info-reply "kernel_info_reply"
|
||||
:shutdown-request "shutdown_request"
|
||||
:shutdown-reply "shutdown_reply"
|
||||
:interupt-request "interrupt_request"
|
||||
:interrupt-reply "interrupt_reply"
|
||||
:stream "stream"
|
||||
:display-data "display_data"
|
||||
:update-display-data "update_display_data"
|
||||
:execute-input "execute_input"
|
||||
:error "error"
|
||||
:status "status"
|
||||
:clear-output "clear_output"
|
||||
:input-reply "input_reply")
|
||||
"A plist mapping keywords to Jupyter message type strings.
|
||||
The plist values are the message types either sent or received
|
||||
from the kernel.")
|
||||
|
||||
;; https://tools.ietf.org/html/rfc4868
|
||||
(defun sha256 (object)
|
||||
|
|
|
@ -357,8 +357,9 @@ for the heartbeat channel."
|
|||
|
||||
;;; Message callbacks
|
||||
|
||||
(defun jupyter-request-run-callbacks (req msg)
|
||||
"Run the MSG callbacks of REQ."
|
||||
(defun jupyter--run-callbacks (req msg)
|
||||
"Run REQ's MSG callbacks.
|
||||
See `jupyter-add-callback'."
|
||||
(when req
|
||||
(let* ((callbacks (jupyter-request-callbacks req))
|
||||
(cb-all-types (cdr (assoc t callbacks)))
|
||||
|
@ -366,80 +367,73 @@ for the heartbeat channel."
|
|||
(and cb-all-types (funcall cb-all-types msg))
|
||||
(and cb-for-type (funcall cb-for-type msg)))))
|
||||
|
||||
(defun jupyter-add-callback (msg-type req function)
|
||||
"Add a callback FUNCTION to a kernel REQuest.
|
||||
(defun jupyter--add-callback (req msg-type cb)
|
||||
"Add REQ MSG-TYPE callback, CB."
|
||||
(setq msg-type (or (plist-get jupyter-message-types msg-type)
|
||||
;; A msg-type of t means that FUNCTION is run for all
|
||||
;; messages associated with a request.
|
||||
(eq msg-type t)))
|
||||
(unless msg-type
|
||||
(error "Not a valid message type (`%s')" msg-type))
|
||||
(let ((callbacks (jupyter-request-callbacks req)))
|
||||
(if (null callbacks)
|
||||
(setf (jupyter-request-callbacks req)
|
||||
(list (cons msg-type cb)))
|
||||
(let ((cb-for-type (assoc msg-type callbacks)))
|
||||
(if (not cb-for-type)
|
||||
(nconc callbacks (list (cons msg-type cb)))
|
||||
(setq cb (apply-partially
|
||||
(lambda (cb1 cb2 msg)
|
||||
(funcall cb1 msg)
|
||||
(funcall cb2 msg))
|
||||
(cdr cb-for-type)
|
||||
cb))
|
||||
(setcdr cb-for-type cb))))))
|
||||
|
||||
MSG-TYPE is a symbol representing which message type to run
|
||||
FUNCTION on when messages of that type are received for REQ. The
|
||||
symbol should be the same as one of the message types which are
|
||||
expected to be received from a kernel where underscore characters
|
||||
are replaced with hyphens, see
|
||||
http://jupyter-client.readthedocs.io/en/latest/messaging.html.
|
||||
(defun jupyter-add-callback (req msg-type cb &rest callbacks)
|
||||
"Add a callback to run when a message is received for a request.
|
||||
REQ is a `jupyter-request' returned by one of the request methods
|
||||
of a `jupyter-kernel-client'. MSG-TYPE is a keyword corresponding
|
||||
to one of the keys in `jupyter-message-types'. MSG-TYPE can also
|
||||
be a list, in which case run CB for every MSG-TYPE in the list.
|
||||
If MSG-TYPE is t, then run CB for every message received for REQ.
|
||||
CB is the callback function which will run with a single
|
||||
argument, a message whose `jupyter-message-parent-id' is the same
|
||||
as the `jupyter-request-id' of REQ and whose
|
||||
`jupyter-message-type' corresponds to the value of MSG-TYPE in
|
||||
the `jupyter-message-types' plist. Any additional arguments to
|
||||
`jupyter-add-callback' are interpreted as additional CALLBACKS to
|
||||
add to REQ. So to add multiple callbacks to a request you would
|
||||
do
|
||||
|
||||
So to add a callback which fires for an \"execute_reply\",
|
||||
MSG-TYPE should be the symbol `execute-reply'. As a special case
|
||||
if MSG-TYPE is t, FUNCTION is run for all received messages of
|
||||
REQ. If MSG-TYPE is a list, then it should be a list of symbols
|
||||
as described above. FUNCTION will then run for every type of
|
||||
message in the list.
|
||||
(jupyter-add-callback
|
||||
(jupyter-execute-request client :code \"1 + 2\")
|
||||
:status (lambda (msg) ...)
|
||||
:execute-reply (lambda (msg) ...)
|
||||
:execute-result (lambda (msg) ...))"
|
||||
(declare (indent 1))
|
||||
(if (jupyter-request-idle-received-p req)
|
||||
(error "Request already received idle message")
|
||||
(setq callbacks (append (list msg-type cb) callbacks))
|
||||
(cl-loop for (msg-type cb) on callbacks by 'cddr
|
||||
if (listp msg-type)
|
||||
do (mapc (lambda (mt) (jupyter--add-callback req mt cb)) msg-type)
|
||||
else do (jupyter--add-callback req msg-type cb))))
|
||||
|
||||
REQ is a `jupyter-request' object as returned by the kernel
|
||||
request methods of a `jupyter-kernel-client'.
|
||||
|
||||
FUNCTION is the callback function to be run when a message with
|
||||
MSG-TYPE is received for REQ. It should accept one argument which
|
||||
will be the message that has type, MSG-TYPE, and is a message
|
||||
associated with the request, REQ. Note that if multiple callbacks
|
||||
are added for the same MSG-TYPE, they will be called in the order
|
||||
in which they were added.
|
||||
|
||||
As an example, suppose you want to register a callback when you
|
||||
recieve an `execute-reply' after sending an execute request. This
|
||||
can be done like so:
|
||||
|
||||
(jupyter-add-callback 'execute-reply
|
||||
(jupyter-request-execute client :code \"y = 1 + 2\")
|
||||
(lambda (msg) ...))"
|
||||
(declare (indent 2))
|
||||
(if (listp msg-type)
|
||||
(mapc (lambda (mt) (jupyter-add-callback mt req function)) msg-type)
|
||||
(setq msg-type (or (plist-get jupyter--received-message-types msg-type)
|
||||
;; A msg-type of t means that FUNCTION is run for all
|
||||
;; messages associated with a request.
|
||||
(eq msg-type t)))
|
||||
(unless msg-type
|
||||
(error "Not a valid received message type (`%s')" msg-type))
|
||||
(if (jupyter-request-idle-received-p req)
|
||||
(error "Request already received idle message.")
|
||||
(let ((callbacks (jupyter-request-callbacks req)))
|
||||
(if (null callbacks)
|
||||
(setf (jupyter-request-callbacks req)
|
||||
(list (cons msg-type function)))
|
||||
(let ((cb-for-type (assoc msg-type callbacks)))
|
||||
(if cb-for-type
|
||||
(setcdr cb-for-type (apply-partially
|
||||
(lambda (cb1 cb2 msg)
|
||||
(funcall cb1 msg)
|
||||
(funcall cb2 msg))
|
||||
(cdr cb-for-type)
|
||||
function))
|
||||
(nconc callbacks
|
||||
(list (cons msg-type function))))))))))
|
||||
|
||||
(defun jupyter-wait-until (msg-type req timeout fun)
|
||||
(defun jupyter-wait-until (req msg-type fun &optional timeout)
|
||||
"Wait until FUN returns non-nil for a received message.
|
||||
FUN is run on every received message for request, REQ, that has
|
||||
type, MSG-TYPE. If FUN does not return a non-nil value before
|
||||
TIMEOUT, return nil. Otherwise return the message which caused
|
||||
FUN to return a non-nil value. Note that if TIMEOUT is nil, it
|
||||
defaults to `jupyter-default-timeout'."
|
||||
(declare (indent 3))
|
||||
(declare (indent 1))
|
||||
(setq timeout (or timeout jupyter-default-timeout))
|
||||
(cl-check-type timeout number)
|
||||
(lexical-let ((msg nil)
|
||||
(fun fun))
|
||||
(jupyter-add-callback msg-type req
|
||||
(lambda (m) (setq msg (when (funcall fun m) m))))
|
||||
(jupyter-add-callback req
|
||||
msg-type (lambda (m) (setq msg (when (funcall fun m) m))))
|
||||
(with-timeout (timeout nil)
|
||||
(while (null msg)
|
||||
(sleep-for 0.01))
|
||||
|
@ -448,7 +442,7 @@ defaults to `jupyter-default-timeout'."
|
|||
(defun jupyter-wait-until-idle (req &optional timeout)
|
||||
"Wait until TIMEOUT for REQ to receive an idle message.
|
||||
If TIMEOUT is non-nil, it defaults to `jupyter-default-timeout'."
|
||||
(jupyter-wait-until 'status req timeout #'jupyter-message-status-idle-p))
|
||||
(jupyter-wait-until req :status #'jupyter-message-status-idle-p timeout))
|
||||
|
||||
(defun jupyter-wait-until-received (msg-type req &optional timeout)
|
||||
"Wait for a message with MSG-TYPE to be received by CLIENT.
|
||||
|
@ -474,7 +468,7 @@ sending one. For example you would not be expecting an
|
|||
more info
|
||||
http://jupyter-client.readthedocs.io/en/latest/messaging.html"
|
||||
(declare (indent 1))
|
||||
(jupyter-wait-until msg-type req timeout #'identity))
|
||||
(jupyter-wait-until req msg-type #'identity timeout))
|
||||
|
||||
(cl-defmethod jupyter-handle-message ((client jupyter-kernel-client) channel)
|
||||
"Process a message on CLIENT's CHANNEL.
|
||||
|
@ -510,7 +504,7 @@ are taken:
|
|||
(when (eq (oref channel type) :iopub)
|
||||
(jupyter-handle-message channel client nil msg))
|
||||
(unwind-protect
|
||||
(jupyter-request-run-callbacks req msg)
|
||||
(jupyter--run-callbacks req msg)
|
||||
(unwind-protect
|
||||
(when (jupyter-request-run-handlers-p req)
|
||||
(jupyter-handle-message channel client req msg))
|
||||
|
|
|
@ -644,7 +644,7 @@ The first character of the cell code corresponds to position 1."
|
|||
;; TODO: Not all kernels will respond to an is_complete_request. The
|
||||
;; jupyter console will switch to its own internal handler when the
|
||||
;; request times out.
|
||||
(let ((res (jupyter-wait-until-received 'is-complete-reply
|
||||
(let ((res (jupyter-wait-until-received :is-complete-reply
|
||||
(jupyter-is-complete-request
|
||||
jupyter-repl-current-client
|
||||
:code (jupyter-repl-cell-code)))))
|
||||
|
@ -717,7 +717,7 @@ The first character of the cell code corresponds to position 1."
|
|||
(lambda (cb)
|
||||
(let ((client jupyter-repl-current-client))
|
||||
(with-jupyter-repl-buffer client
|
||||
(jupyter-add-callback 'complete-reply
|
||||
(jupyter-add-callback
|
||||
(jupyter-complete-request
|
||||
client
|
||||
:code (jupyter-repl-cell-code)
|
||||
|
@ -729,21 +729,22 @@ The first character of the cell code corresponds to position 1."
|
|||
;; the cursor is at, but the cell code will only be 3
|
||||
;; characters long.
|
||||
:pos (1- (jupyter-repl-cell-code-position)))
|
||||
:complete-reply
|
||||
(apply-partially
|
||||
(lambda (cb msg)
|
||||
(cl-destructuring-bind (&key status matches &allow-other-keys)
|
||||
(cl-destructuring-bind (&key status matches
|
||||
&allow-other-keys)
|
||||
(jupyter-message-content msg)
|
||||
(if (equal status "ok") (funcall cb matches)
|
||||
(funcall cb '()))))
|
||||
cb))))))))
|
||||
(sorted t)
|
||||
(doc-buffer
|
||||
(let* ((client jupyter-repl-current-client)
|
||||
(msg (jupyter-wait-until-received 'inspect-reply
|
||||
(let ((msg (jupyter-wait-until-received :inspect-reply
|
||||
(jupyter-request-inhibit-handlers
|
||||
(jupyter-inspect-request
|
||||
client
|
||||
:code arg :pos (length arg))))
|
||||
(doc nil))
|
||||
jupyter-repl-current-client
|
||||
:code arg :pos (length arg))))))
|
||||
(when msg
|
||||
(cl-destructuring-bind (&key status found data &allow-other-keys)
|
||||
(jupyter-message-content msg)
|
||||
|
@ -797,15 +798,14 @@ it."
|
|||
(let ((req (jupyter-execute-request jupyter-repl-current-client
|
||||
:code "" :silent t)))
|
||||
(jupyter-request-inhibit-handlers req)
|
||||
(jupyter-add-callback 'execute-reply req
|
||||
(apply-partially
|
||||
(lambda (client msg)
|
||||
(message "foo")
|
||||
(oset client execution-count
|
||||
(1+ (jupyter-message-get msg :execution_count)))
|
||||
(with-jupyter-repl-buffer client
|
||||
(jupyter-repl-insert-prompt 'in)))
|
||||
jupyter-repl-current-client))
|
||||
(jupyter-add-callback req
|
||||
:execute-reply (apply-partially
|
||||
(lambda (client msg)
|
||||
(oset client execution-count
|
||||
(1+ (jupyter-message-get msg :execution_count)))
|
||||
(with-jupyter-repl-buffer client
|
||||
(jupyter-repl-insert-prompt 'in)))
|
||||
jupyter-repl-current-client))
|
||||
(jupyter-wait-until-idle req)))
|
||||
|
||||
(defun run-jupyter-repl (kernel-name)
|
||||
|
|
|
@ -115,36 +115,36 @@ testing the callback functionality of a
|
|||
(should (jupyter-wait-until-idle req))
|
||||
(should (jupyter-request-idle-received-p req))
|
||||
;; Can't add callbacks after an idle message has been received
|
||||
(should-error (jupyter-add-callback 'status req #'identity))))
|
||||
(should-error (jupyter-add-callback req :status #'identity))))
|
||||
(ert-info ("Callback runs for the right message")
|
||||
(lexical-let ((ran-callbacks nil)
|
||||
(req1 (jupyter-execute-request client :code "foo"))
|
||||
(req2 (jupyter-kernel-info-request client)))
|
||||
;; callback for all message types received from a request
|
||||
(jupyter-add-callback t req1
|
||||
(lambda (msg)
|
||||
(push 1 ran-callbacks)
|
||||
(should (member (jupyter-message-type msg)
|
||||
'("execute_reply" "status")))
|
||||
(should (equal (jupyter-message-parent-id msg)
|
||||
(jupyter-request-id req1)))))
|
||||
(jupyter-add-callback t req2
|
||||
(lambda (msg)
|
||||
(push 2 ran-callbacks)
|
||||
(should (member (jupyter-message-type msg)
|
||||
'("kernel_info_reply" "status")))
|
||||
(should (equal (jupyter-message-parent-id msg)
|
||||
(jupyter-request-id req2)))))
|
||||
(jupyter-add-callback req1
|
||||
t (lambda (msg)
|
||||
(push 1 ran-callbacks)
|
||||
(should (member (jupyter-message-type msg)
|
||||
'("execute_reply" "status")))
|
||||
(should (equal (jupyter-message-parent-id msg)
|
||||
(jupyter-request-id req1)))))
|
||||
(jupyter-add-callback req2
|
||||
t (lambda (msg)
|
||||
(push 2 ran-callbacks)
|
||||
(should (member (jupyter-message-type msg)
|
||||
'("kernel_info_reply" "status")))
|
||||
(should (equal (jupyter-message-parent-id msg)
|
||||
(jupyter-request-id req2)))))
|
||||
(should (jupyter-wait-until-idle req2))
|
||||
(setq ran-callbacks (nreverse ran-callbacks))
|
||||
(should (equal ran-callbacks '(1 1 1 2 2 2)))))
|
||||
(ert-info ("Multiple callbacks for a single message type")
|
||||
(lexical-let* ((ran-callbacks nil)
|
||||
(req (jupyter-execute-request client :code "foo")))
|
||||
(jupyter-add-callback 'execute-reply req
|
||||
(lambda (msg) (push 1 ran-callbacks)))
|
||||
(jupyter-add-callback 'execute-reply req
|
||||
(lambda (msg) (push 2 ran-callbacks)))
|
||||
(jupyter-add-callback req
|
||||
:execute-reply (lambda (msg) (push 1 ran-callbacks)))
|
||||
(jupyter-add-callback req
|
||||
:execute-reply (lambda (msg) (push 2 ran-callbacks)))
|
||||
(jupyter-wait-until-idle req)
|
||||
(setq ran-callbacks (nreverse ran-callbacks))
|
||||
(should (equal ran-callbacks '(1 2))))))))
|
||||
|
@ -301,19 +301,19 @@ testing the callback functionality of a
|
|||
(unwind-protect
|
||||
(progn
|
||||
(ert-info ("Kernel info")
|
||||
(let ((res (jupyter-wait-until-received 'kernel-info-reply
|
||||
(let ((res (jupyter-wait-until-received :kernel-info-reply
|
||||
(jupyter-kernel-info-request client))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
(should (equal (jupyter-message-type res) "kernel_info_reply"))))
|
||||
(ert-info ("Comm info")
|
||||
(let ((res (jupyter-wait-until-received 'comm-info-reply
|
||||
(let ((res (jupyter-wait-until-received :comm-info-reply
|
||||
(jupyter-comm-info-request client))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
(should (equal (jupyter-message-type res) "comm_info_reply"))))
|
||||
(ert-info ("Execute")
|
||||
(let ((res (jupyter-wait-until-received 'execute-reply
|
||||
(let ((res (jupyter-wait-until-received :execute-reply
|
||||
(jupyter-execute-request client :code "y = 1 + 2"))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
|
@ -321,7 +321,7 @@ testing the callback functionality of a
|
|||
(ert-info ("Input")
|
||||
(cl-letf (((symbol-function 'read-from-minibuffer)
|
||||
(lambda (prompt &rest args) "foo")))
|
||||
(let ((res (jupyter-wait-until-received 'execute-result
|
||||
(let ((res (jupyter-wait-until-received :execute-result
|
||||
(jupyter-execute-request client :code "input('')"))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
|
@ -330,7 +330,7 @@ testing the callback functionality of a
|
|||
(plist-get res :content)
|
||||
(should (equal (plist-get data :text/plain) "'foo'"))))))
|
||||
(ert-info ("Inspect")
|
||||
(let ((res (jupyter-wait-until-received 'inspect-reply
|
||||
(let ((res (jupyter-wait-until-received :inspect-reply
|
||||
(jupyter-inspect-request
|
||||
client
|
||||
:code "list((1, 2, 3))"
|
||||
|
@ -340,7 +340,7 @@ testing the callback functionality of a
|
|||
(should (json-plist-p res))
|
||||
(should (equal (jupyter-message-type res) "inspect_reply"))))
|
||||
(ert-info ("Complete")
|
||||
(let ((res (jupyter-wait-until-received 'complete-reply
|
||||
(let ((res (jupyter-wait-until-received :complete-reply
|
||||
(jupyter-complete-request
|
||||
client
|
||||
:code "foo = lis"
|
||||
|
@ -349,14 +349,14 @@ testing the callback functionality of a
|
|||
(should (json-plist-p res))
|
||||
(should (equal (jupyter-message-type res) "complete_reply"))))
|
||||
(ert-info ("History")
|
||||
(let ((res (jupyter-wait-until-received 'history-reply
|
||||
(let ((res (jupyter-wait-until-received :history-reply
|
||||
(jupyter-history-request
|
||||
client :hist-access-type "tail" :n 2))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
(should (equal (jupyter-message-type res) "history_reply"))))
|
||||
(ert-info ("Is Complete")
|
||||
(let ((res (jupyter-wait-until-received 'is-complete-reply
|
||||
(let ((res (jupyter-wait-until-received :is-complete-reply
|
||||
(jupyter-is-complete-request
|
||||
client :code "for i in range(5):"))))
|
||||
(should-not (null res))
|
||||
|
@ -365,14 +365,14 @@ testing the callback functionality of a
|
|||
(ert-info ("Interrupt")
|
||||
(lexical-let ((time (current-time))
|
||||
(interrupt-time nil))
|
||||
(jupyter-add-callback 'status
|
||||
(jupyter-add-callback
|
||||
(jupyter-execute-request
|
||||
client :code "import time\ntime.sleep(2)")
|
||||
(lambda (msg)
|
||||
(when (jupyter-message-status-idle-p msg)
|
||||
(setq interrupt-time (current-time)))))
|
||||
:status (lambda (msg)
|
||||
(when (jupyter-message-status-idle-p msg)
|
||||
(setq interrupt-time (current-time)))))
|
||||
(sleep-for 0.2)
|
||||
(let ((res (jupyter-wait-until-received 'interrupt-reply
|
||||
(let ((res (jupyter-wait-until-received :interrupt-reply
|
||||
(jupyter-interrupt-request client))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
|
@ -381,7 +381,7 @@ testing the callback functionality of a
|
|||
(should (< (float-time (time-subtract interrupt-time time))
|
||||
2)))))
|
||||
(ert-info ("Shutdown")
|
||||
(let ((res (jupyter-wait-until-received 'shutdown-reply
|
||||
(let ((res (jupyter-wait-until-received :shutdown-reply
|
||||
(jupyter-shutdown-request client))))
|
||||
(should-not (null res))
|
||||
(should (json-plist-p res))
|
||||
|
|
Loading…
Add table
Reference in a new issue