mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-06 07:51:39 -05:00
193 lines
6.9 KiB
EmacsLisp
193 lines
6.9 KiB
EmacsLisp
;;; test-helper.el --- Helpers for jupyter-test.el -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2018 Nathaniel Nicandro
|
|
|
|
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
|
|
;; Created: 15 Nov 2018
|
|
|
|
;; This program is free software; you can redistribute it and/or
|
|
;; modify it under the terms of the GNU General Public License as
|
|
;; published by the Free Software Foundation; either version 2, or (at
|
|
;; your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful, but
|
|
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
;; General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
;; Boston, MA 02111-1307, USA.
|
|
|
|
;;; Commentary:
|
|
|
|
;;
|
|
|
|
;;; Code:
|
|
|
|
(require 'zmq)
|
|
(require 'jupyter-client)
|
|
(require 'jupyter-repl)
|
|
(require 'jupyter-org-client)
|
|
(require 'jupyter-kernel-manager)
|
|
(require 'cl-lib)
|
|
(require 'ert)
|
|
|
|
;;; `jupyter-echo-client'
|
|
|
|
(defclass jupyter-echo-client (jupyter-kernel-client)
|
|
((messages))
|
|
:documentation "A client that echo's any messages sent back to
|
|
the channel the message was sent on. No communication is actually
|
|
done with a kernel. Every sent message on a channel is just
|
|
directly sent back to the handler method. The message flow when
|
|
handling a message is always
|
|
|
|
- status: busy
|
|
- reply message
|
|
- status: idle")
|
|
|
|
(cl-defmethod initialize-instance ((client jupyter-echo-client) &rest _slots)
|
|
(cl-call-next-method)
|
|
(oset client messages (make-ring 10))
|
|
(oset client channels
|
|
(list :hb (jupyter-hb-channel)
|
|
:shell (list :alive-p nil :endpoint "foo://bar")
|
|
:stdin (list :alive-p nil :endpoint "foo://bar")
|
|
:iopub (list :alive-p nil :endpoint "foo://bar"))))
|
|
|
|
(cl-defmethod jupyter-send ((client jupyter-echo-client)
|
|
channel
|
|
type
|
|
message
|
|
&optional _flags)
|
|
(let ((req (jupyter-request :id (jupyter-new-uuid))))
|
|
(if (string-match "request" (symbol-name type))
|
|
(setq type (intern (replace-match "reply" nil nil (symbol-name type))))
|
|
(error "Not a request message type (%s)" type))
|
|
;; Message flow
|
|
;; - status: busy
|
|
;; - reply message
|
|
;; - status: idle
|
|
;;
|
|
;; Needed internally by a `jupyter-kernel-client', this is mainly handled
|
|
;; by the eventloop.
|
|
(puthash (jupyter-request-id req) req (oref client requests))
|
|
;; Simulate a delay
|
|
(run-at-time
|
|
0.001 nil
|
|
(lambda ()
|
|
(jupyter-handle-message
|
|
client :iopub (jupyter-test-message req :status (list :execution_state "busy")))
|
|
(jupyter-handle-message client channel (jupyter-test-message req type message))
|
|
(jupyter-handle-message
|
|
client :iopub (jupyter-test-message req :status (list :execution_state "idle")))))
|
|
req))
|
|
|
|
(cl-defmethod jupyter-handle-message ((client jupyter-echo-client) _channel msg)
|
|
(ring-insert+extend (oref client messages) msg 'grow)
|
|
(cl-call-next-method))
|
|
|
|
;;; Macros
|
|
|
|
(cl-defmacro jupyter-ert-info ((message-form &key ((:prefix prefix-form) "Info: "))
|
|
&body body)
|
|
"Identical to `ert-info', but clear the REPL buffer before running BODY.
|
|
In a REPL buffer, the contents are erased and an input prompt is
|
|
inserted.
|
|
|
|
If the `current-buffer' is not a REPL, this is identical to
|
|
`ert-info'."
|
|
(declare (debug ((form &rest [sexp form]) body))
|
|
(indent 1))
|
|
`(ert-info ((quote ,message-form) :prefix (quote ,prefix-form))
|
|
;; Clear the REPL buffer before each new test section, but do this only if
|
|
;; the current client is a REPL client
|
|
(when (and jupyter-current-client
|
|
(object-of-class-p jupyter-current-client
|
|
'jupyter-repl-client)
|
|
(eq (current-buffer)
|
|
(oref jupyter-current-client buffer)))
|
|
(let ((inhibit-read-only t))
|
|
(erase-buffer)
|
|
(jupyter-test-repl-ret-sync)))
|
|
,@body))
|
|
|
|
(defmacro jupyter-with-echo-client (client &rest body)
|
|
(declare (indent 1) (debug (symbolp &rest form)))
|
|
`(let ((,client (jupyter-echo-client)))
|
|
,@body))
|
|
|
|
(defmacro jupyter-with-kernel-client (kernel client &rest body)
|
|
"Start a new KERNEL client, bind it to CLIENT, evaluate BODY.
|
|
Cleanup the client and delete the kernel process after running
|
|
BODY."
|
|
(declare (indent 2) (debug (stringp symbolp &rest form)))
|
|
(let ((manager (make-symbol "--manager")))
|
|
`(cl-destructuring-bind (,manager ,client)
|
|
(jupyter-start-new-kernel
|
|
(jupyter-error-if-no-kernelspec ,kernel))
|
|
(sleep-for 0.3)
|
|
,@body)))
|
|
|
|
(defmacro jupyter-with-python-client (client &rest body)
|
|
"Start a new Python kernel, bind it to CLIENT, evaluate BODY."
|
|
(declare (indent 1) (debug (symbolp &rest form)))
|
|
`(jupyter-with-kernel-client "python" ,client
|
|
,@body))
|
|
|
|
(defmacro jupyter-with-kernel-repl (kernel client &rest body)
|
|
"Start a new KERNEL REPL, bind the client to CLIENT, evaluate BODY.
|
|
Delete the REPL buffer after running BODY."
|
|
(declare (indent 2) (debug (stringp symbolp &rest form)))
|
|
`(let ((,client (jupyter-run-repl
|
|
(jupyter-error-if-no-kernelspec ,kernel))))
|
|
(unwind-protect
|
|
(jupyter-with-repl-buffer ,client
|
|
(progn ,@body))
|
|
(cl-letf (((symbol-function 'yes-or-no-p)
|
|
(lambda (_prompt) t))
|
|
((symbol-function 'y-or-n-p)
|
|
(lambda (_prompt) t)))
|
|
(kill-buffer (oref client buffer))))))
|
|
|
|
(defmacro jupyter-with-python-repl (client &rest body)
|
|
"Start a new Python REPL and run BODY.
|
|
CLIENT is bound to the Python REPL. Delete the REPL buffer after
|
|
running BODY."
|
|
(declare (indent 1) (debug (symbolp &rest form)))
|
|
`(jupyter-with-kernel-repl "python" ,client
|
|
,@body))
|
|
|
|
;;; Functions
|
|
|
|
(defun jupyter-error-if-no-kernelspec (kernel)
|
|
(prog1 kernel
|
|
(unless (car (jupyter-find-kernelspecs
|
|
(regexp-quote kernel)))
|
|
(error "Kernel not found (%s)" kernel))))
|
|
|
|
(defun jupyter-test-message (req type content)
|
|
"Return a bare bones message plist for REQ.
|
|
TYPE is the message type of the returned message. CONTENT is the
|
|
message contents."
|
|
(list :msg_id (jupyter-new-uuid)
|
|
:msg_type type
|
|
:parent_header (list :msg_id (jupyter-request-id req))
|
|
:content content))
|
|
|
|
(defun jupyter-test-wait-until-idle-repl (client)
|
|
"Wait until the execution state of a REPL CLIENT is idle."
|
|
(while (not (equal (jupyter-execution-state client) "idle"))
|
|
(sleep-for 0.01)))
|
|
|
|
(defun jupyter-test-repl-ret-sync ()
|
|
"A synchronous version of `jupyter-repl-ret'."
|
|
;; TODO: There are issues here since the kernel goes through multiple idle ->
|
|
;; busy cycles
|
|
(jupyter-repl-ret)
|
|
(jupyter-test-wait-until-idle-repl
|
|
jupyter-current-client))
|
|
|
|
;;; test-helper.el ends here
|