emacs-jupyter/jupyter-tests.el

226 lines
10 KiB
EmacsLisp
Raw Normal View History

2017-12-14 13:21:55 -06:00
;; -*- lexical-binding: t -*-
2017-12-13 11:27:13 -06:00
(require 'jupyter-client)
(require 'jupyter-messages)
(require 'cl-lib)
(require 'ert)
(ert-deftest jupyter-messages ()
(ert-info ("Splitting identities from messages")
(let ((msg (list "123" "323" jupyter-message-delimiter
"msg1" "msg2" "\0\0")))
(should (equal (jupyter-split-identities msg)
(cons (list "123" "323")
(list "msg1" "msg2" "\0\0"))))
(setq msg (list "123" "No" "delim" "in" "message"))
(should-error (jupyter-split-identities msg))))
(ert-info ("Creating message headers")
(let ((header (jupyter-message-header "stdin_reply" "session-id")))
;; TODO: Check fields
(should (plist-get header :msg_id))
(should (plist-get header :date))
(should (string= (plist-get header :msg_type) "stdin_reply"))
(should (string= (plist-get header :version) jupyter-protocol-version))
(should (string= (plist-get header :username) user-login-name))
(should (string= (plist-get header :session) "session-id")))
;; TODO: Handle other kinds of encoding
(ert-info ("Encoding/decoding objects")
(let ((json-object-type 'plist)
(obj nil))
(should-not (multibyte-string-p (jupyter-encode-object "foîji")))
;; TODO: Only decodes json plists, what to do instead?
(should-error (jupyter-decode-string (jupyter-encode-object "foîji")))
(setq obj '(:msg_id 12342 :msg_type "stdin_reply" :session "foîji"))
(should (json-plist-p obj))
(should-not (multibyte-string-p (jupyter-encode-object obj)))
(should (equal (jupyter-decode-string (jupyter-encode-object obj))
obj))))))
(ert-deftest jupyter-channels ()
(ert-info ("Channel types should match their class")
(should (eq (oref (jupyter-shell-channel) type) :shell))
(should (eq (oref (jupyter-stdin-channel) type) :stdin))
(should (eq (oref (jupyter-iopub-channel) type) :iopub))
(should (eq (oref (jupyter-control-channel) type) :control))
(should (eq (oref (jupyter-hb-channel) type) :hb)))
(let ((channel (jupyter-shell-channel :endpoint "tcp://127.0.0.1:5555")))
(ert-info ("Starting a channel")
(oset channel socket nil)
(should-not (oref channel socket))
(should-not (jupyter-channel-alive-p channel))
(jupyter-start-channel channel :identity "foo")
(should (oref channel socket))
(cl-check-type (oref channel socket) zmq-socket)
(should (equal (zmq-socket-get (oref channel socket) zmq-ROUTING_ID)
"foo"))
(should (jupyter-channel-alive-p channel)))
(ert-info ("Stopping a channel")
(ring-insert (oref channel recv-queue) "bar")
(let ((sock (oref channel socket)))
(jupyter-stop-channel channel)
(should-not (oref channel socket))
(should-error (zmq-close sock) :type 'zmq-ENOTSOCK)
;; All pending messages are droped when a channel is stopped
(should (= (ring-length (oref channel recv-queue)) 0))
(should-not (jupyter-channel-alive-p channel)))))
(ert-info ("Heartbeat channel")
(let ((proc (zmq-start-process
(lambda (ctx)
(with-zmq-socket sock zmq-REP
(zmq-bind sock "tcp://127.0.0.1:5555")
(while t
(zmq-recv sock)
(zmq-send sock "pong"))))))
(channel (jupyter-hb-channel :endpoint "tcp://127.0.0.1:5555")))
(unwind-protect
(progn
(ert-info ("Starting the channel")
(oset channel process nil)
(should-not (jupyter-channel-alive-p channel))
(should-not (jupyter-hb-beating-p channel))
(jupyter-start-channel channel)
(should (jupyter-channel-alive-p channel))
(should (jupyter-hb-beating-p channel))
(should-not (oref channel paused)))
;; TODO: Fix pausing the channel
(ert-info ("Pausing the channel")
(jupyter-hb-pause channel)
;; TODO: Improve these wait times
(sleep-for 2)
(should (oref channel paused)))
(ert-info ("Unpausing the channel")
(jupyter-hb-unpause channel)
(sleep-for 2)
(should-not (oref channel paused)))
(ert-info ("Checking the status")
(jupyter-hb-pause channel)
(sleep-for 2)
(should (jupyter-hb-beating-p channel))
;; Asking if the hb is beating unpauses the channel
(should-not (oref channel paused))))
(delete-process proc)
;; TODO: Refactor to remove this
(jupyter-channel-stop channel)
(kill-buffer (process-buffer proc))))))
(ert-deftest jupyter-client ()
(let* ((socks (cl-loop repeat 4
collect (zmq-socket (current-zmq-context) zmq-REQ)))
(sock-endpoint
(cl-loop
with addr = "tcp://127.0.0.1"
for sock in socks
collect (cons sock (format "%s:%d" addr (zmq-bind-to-random-port
sock addr))))))
(unwind-protect
(progn
(setq client (jupyter-kernel-client
:session (jupyter-session
:key "58e05d24-7600e037194e78bde23108de")
:shell-channel (jupyter-shell-channel
:endpoint (cdr (nth 0 sock-endpoint)))
:iopub-channel (jupyter-iopub-channel
:endpoint (cdr (nth 1 sock-endpoint)))
:stdin-channel (jupyter-stdin-channel
:endpoint (cdr (nth 2 sock-endpoint)))
:hb-channel (jupyter-hb-channel
:endpoint (cdr (nth 3 sock-endpoint)))))
(cl-loop
for cname in (list 'shell-channel 'iopub-channel
'hb-channel 'stdin-channel)
do (should (slot-boundp client cname))
(if (eq cname 'hb-channel) (cl-check-type (eieio-oref client cname)
jupyter-hb-channel)
(cl-check-type (eieio-oref client cname) jupyter-channel))
(should-not (jupyter-channel-alive-p (eieio-oref client cname))))
(jupyter-start-channels client)
(cl-loop
for cname in (list 'shell-channel 'iopub-channel
'hb-channel 'stdin-channel)
for channel = (eieio-oref client cname)
unless (eq cname 'hb-channel) do
(should (slot-boundp channel 'socket))
(cl-check-type (oref channel socket) zmq-socket)
and do (should (jupyter-channel-alive-p (eieio-oref client cname)))))
(mapc (lambda (se) (zmq-close (car se))) sock-endpoint))))
2017-12-14 13:21:55 -06:00
(ert-deftest jupyter-message-callbacks ()
(let ((client (jupyter-kernel-client-using-kernel "python")))
(jupyter-start-channels client)
;; Let channels start
(sleep-for 1)
(unwind-protect
(progn
(ert-info ("Invalid message type")
(should-error
(jupyter-add-receive-callback
client 'exe-res (jupyter-send-execute client :code "y = 1+2\ny")
(lambda (msg) (+ 1 2)))))
(ert-info ("Execute on correct message ID")
(let ((id (jupyter-send-kernel-info client))
(recv-id nil))
(jupyter-send-kernel-info client)
(jupyter-add-receive-callback client 'kernel-info-reply id
(lambda (msg)
(setq recv-id (plist-get (plist-get msg :parent_header)
:msg_id))))
;; Receive messages from kernel
(sleep-for 1)
(should (equal id recv-id))))
(ert-info ("Multiple callbacks on the same message")
(let ((id (jupyter-send-execute client :code "y = 1+2\ny"))
(msg-res nil)
(msg-rep nil))
(jupyter-add-receive-callback client 'execute-result id
(lambda (msg) (setq msg-res msg)))
(jupyter-add-receive-callback client 'execute-reply id
(lambda (msg) (setq msg-rep msg)))
;; Receive messages from kernel
(sleep-for 1)
(should-not (null msg-res))
(should-not (null msg-rep))
;; execute_result
(should (equal id (plist-get (plist-get msg-res :parent_header)
:msg_id)))
(should (equal (plist-get msg-res :msg_type) "execute_result"))
(cl-destructuring-bind (&key data &allow-other-keys)
(plist-get msg-res :content)
(should (equal (plist-get data :text/plain) "3")))
;; execute_reply
(should (equal id (plist-get (plist-get msg-rep :parent_header)
:msg_id)))
(should (equal (plist-get msg-rep :msg_type) "execute_reply")))))
(jupyter-stop-channels client)
(delete-process (oref client kernel))
(kill-buffer (process-buffer (oref client kernel))))))
2017-12-13 11:27:13 -06:00
(ert-deftest jupyter-processing-messages ()
(setq client (jupyter-kernel-client-using-kernel "python"))
(jupyter-start-channels client)
;; TODO: Better interface for this. It looks like I will have to handle
;; certain messages specially. For a shutdown message, I will also have to
;; delete the process as is done here. Also, I don't like these receive
;; callbacks without some type of error handling on the msg-type of the
;; reply. If something is mispelled, then it won't fire. And if a certain
;; message is not expecting a reply, it will also never fire. This will cause
;; hangs when using `jupyter-wait-until-received'.
;;
;; TODO: `jupyter-shutdown' seems ambiguous. Prefix the sending messages like
;; `jupyter-send-shutdown', `jupyter-send-execute', ...
(lexical-let ((proc (oref client kernel))
(id (jupyter-shutdown client)))
(jupyter-add-receive-callback
client "shutdown_reply" id
(lambda (msg)
(when (equal (plist-get (plist-get msg :content) :status)
"ok")
(delete-process proc)
(kill-buffer (process-buffer proc)))))))
;; Local Variables:
;; byte-compile-warnings: (not free-vars)
;; End: