2018-11-14 13:15:29 -06:00
|
|
|
|
;;; jupyter-test.el --- Jupyter tests -*- lexical-binding: t -*-
|
2018-01-08 21:38:32 -06:00
|
|
|
|
|
2020-04-07 15:13:51 -05:00
|
|
|
|
;; Copyright (C) 2018-2020 Nathaniel Nicandro
|
2018-01-08 21:38:32 -06:00
|
|
|
|
|
|
|
|
|
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
|
|
|
|
|
;; Created: 08 Jan 2018
|
|
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or
|
|
|
|
|
;; modify it under the terms of the GNU General Public License as
|
2019-05-31 09:44:39 -05:00
|
|
|
|
;; published by the Free Software Foundation; either version 3, or (at
|
2018-01-08 21:38:32 -06:00
|
|
|
|
;; 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:
|
2018-01-07 14:25:54 -06:00
|
|
|
|
|
2019-06-28 20:02:00 -05:00
|
|
|
|
(require 'jupyter-env)
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(require 'jupyter-client)
|
|
|
|
|
(require 'jupyter-repl)
|
|
|
|
|
(require 'jupyter-org-client)
|
|
|
|
|
(require 'cl-lib)
|
|
|
|
|
(require 'ert)
|
2019-03-10 19:53:54 -05:00
|
|
|
|
(require 'subr-x) ; string-trim
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(declare-function org-babel-python-table-or-string "ob-python" (results))
|
2018-01-07 14:25:54 -06:00
|
|
|
|
|
|
|
|
|
;; TODO: Required tests
|
2018-08-27 20:37:27 -05:00
|
|
|
|
;; - Jupyter REPL
|
2018-01-07 14:25:54 -06:00
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;;; Mock
|
|
|
|
|
|
2018-01-06 13:58:32 -06:00
|
|
|
|
(ert-deftest jupyter-echo-client ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(mock)
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(jupyter-with-echo-client client
|
2018-01-06 13:58:32 -06:00
|
|
|
|
(ert-info ("Mock echo client echo's messages back to channel.")
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(let ((req (jupyter-send client "execute_request" :code "foo"))
|
2020-05-08 16:25:14 -05:00
|
|
|
|
(msg (jupyter-test-message
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(make-jupyter-request) "execute_request" '(:code "foo"))))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(sleep-for 0.3)
|
2018-01-06 13:58:32 -06:00
|
|
|
|
(setq msgs (nreverse (ring-elements (oref client messages))))
|
|
|
|
|
(should (= (length msgs) 3))
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(should (equal (jupyter-message-type (car msgs)) "status"))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(should (equal (jupyter-message-parent-id (car msgs))
|
2018-01-06 13:58:32 -06:00
|
|
|
|
(jupyter-request-id req)))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(should (equal (jupyter-message-get (car msgs) :execution_state) "busy"))
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(should (equal (jupyter-message-type (cadr msgs)) "execute_reply"))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(should (equal (jupyter-message-parent-id (cadr msgs))
|
2018-01-06 13:58:32 -06:00
|
|
|
|
(jupyter-request-id req)))
|
2019-03-29 12:31:00 -05:00
|
|
|
|
(should (equal (jupyter-message-content (cadr msgs))
|
2020-05-08 16:25:14 -05:00
|
|
|
|
(plist-get msg :content)))
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(should (equal (jupyter-message-type (caddr msgs)) "status"))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(should (equal (jupyter-message-parent-id (caddr msgs))
|
2018-01-06 13:58:32 -06:00
|
|
|
|
(jupyter-request-id req)))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(should (equal (jupyter-message-get (caddr msgs) :execution_state) "idle"))))))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;;; Callbacks
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-wait-until-idle ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(callbacks)
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(jupyter-with-echo-client client
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(let ((req (jupyter-send client "execute_request" :code "foo")))
|
2020-11-21 12:52:27 -06:00
|
|
|
|
(should-not (jupyter-request-idle-p req))
|
|
|
|
|
(jupyter-wait-until-idle req)
|
|
|
|
|
(should (jupyter-request-idle-p req)))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
|
|
|
|
;;; `jupyter-insert'
|
|
|
|
|
|
2020-04-10 15:33:04 -05:00
|
|
|
|
(ert-deftest jupyter-map-mime-bundle ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(mime)
|
2020-04-10 15:33:04 -05:00
|
|
|
|
(let ((res (current-time))
|
|
|
|
|
(content
|
|
|
|
|
'(:data
|
|
|
|
|
(:text/plain
|
|
|
|
|
1 :text/html 2
|
|
|
|
|
:text/latex 3)
|
|
|
|
|
:metadata
|
|
|
|
|
(:text/plain
|
|
|
|
|
4 :text/html 5
|
|
|
|
|
:text/latex 6))))
|
|
|
|
|
(should
|
|
|
|
|
(eq
|
|
|
|
|
res
|
|
|
|
|
(jupyter-map-mime-bundle
|
|
|
|
|
'(:text/html :text/latex :text/plain)
|
|
|
|
|
content
|
|
|
|
|
(lambda (mime content)
|
|
|
|
|
(when (eq mime :text/plain)
|
|
|
|
|
(should (= (plist-get content :data) 1))
|
|
|
|
|
(should (= (plist-get content :metadata) 4))
|
|
|
|
|
res)))))
|
|
|
|
|
(let (called)
|
|
|
|
|
(should-not
|
|
|
|
|
(eq
|
|
|
|
|
res
|
|
|
|
|
(jupyter-map-mime-bundle
|
|
|
|
|
'(:text/html :text/latex :mime-without-data) content
|
|
|
|
|
(lambda (mime content)
|
|
|
|
|
(pcase mime
|
|
|
|
|
(:text/latex
|
|
|
|
|
(should (= (plist-get content :data) 3))
|
|
|
|
|
(should (= (plist-get content :metadata) 6))
|
|
|
|
|
(setq called t)
|
|
|
|
|
nil)
|
|
|
|
|
(:mime-without-data res)
|
|
|
|
|
(_ nil))))))
|
|
|
|
|
(should called))))
|
2018-11-10 13:09:34 -06:00
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(defvar jupyter-nongraphic-mime-types)
|
|
|
|
|
|
2018-11-09 12:20:38 -06:00
|
|
|
|
(ert-deftest jupyter-insert ()
|
|
|
|
|
"Test the `jupyter-insert' method."
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(mime)
|
2018-11-09 12:20:38 -06:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(let ((msg (list :data (list :text/plain "foo")
|
|
|
|
|
:metadata nil)))
|
|
|
|
|
(ert-info ("Return value is the mimetype inserted")
|
|
|
|
|
(should (eq (jupyter-insert msg) :text/plain))
|
|
|
|
|
(should (equal (buffer-string) "foo\n"))
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(ert-info ("Return nil on invalid mimetype")
|
|
|
|
|
(should-not (jupyter-insert :text/foo "bar"))
|
|
|
|
|
(should-not (jupyter-insert (list :data (list :text/foo "bar")))))
|
|
|
|
|
(ert-info ("Calling with data plist directly")
|
|
|
|
|
(should (eq (jupyter-insert (plist-get msg :data)) :text/plain))
|
|
|
|
|
(should (equal (buffer-string) "foo\n"))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(erase-buffer))
|
|
|
|
|
(ert-info ("Calling with message plist directly")
|
|
|
|
|
(should (eq (jupyter-insert msg) :text/plain))
|
|
|
|
|
(should (equal (buffer-string) "foo\n"))
|
2018-11-09 12:20:38 -06:00
|
|
|
|
(erase-buffer)))
|
|
|
|
|
(let ((msg (list :data (list :text/plain "foo"
|
|
|
|
|
:text/html "<b>bar</b>")
|
|
|
|
|
:metadata nil)))
|
|
|
|
|
(ert-info ("Mimetype priority")
|
|
|
|
|
(should (eq (jupyter-insert msg) :text/html))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(should (equal (string-trim (buffer-string)) "bar"))
|
2018-11-09 12:20:38 -06:00
|
|
|
|
(erase-buffer)))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(let ((data (list :image/jpeg (base64-encode-string "kjdaljk")))
|
|
|
|
|
;; So that this test runs under ert-runner
|
|
|
|
|
(jupyter-nongraphic-mime-types jupyter-mime-types))
|
|
|
|
|
(ert-info ("The right method specializers are called")
|
2018-11-09 12:20:38 -06:00
|
|
|
|
(cl-letf (((symbol-function #'jupyter-insert-image)
|
|
|
|
|
(lambda (data &rest _) (insert data))))
|
|
|
|
|
(cl-letf (((symbol-function #'image-type-available-p)
|
|
|
|
|
(lambda (_typ) nil)))
|
|
|
|
|
(should-not (jupyter-insert data)))
|
2019-09-15 13:35:31 -05:00
|
|
|
|
(cl-letf (((symbol-function #'image-type-available-p)
|
|
|
|
|
(lambda (typ) (eq typ 'jpeg))))
|
|
|
|
|
(should (eq (jupyter-insert data) :image/jpeg)))
|
2018-11-09 12:20:38 -06:00
|
|
|
|
(should (equal (buffer-string) "kjdaljk\n"))
|
|
|
|
|
(erase-buffer))))))
|
|
|
|
|
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(defun jupyter-test-display-id-all (id beg end)
|
|
|
|
|
(not (text-property-not-all beg end 'jupyter-display id)))
|
|
|
|
|
|
2018-12-01 00:27:39 -06:00
|
|
|
|
(ert-deftest jupyter-insert-with-ids ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(mime display-id)
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(let ((id "1")
|
|
|
|
|
(msg (list :data (list :text/plain "foo"))))
|
|
|
|
|
(ert-info ("`jupyter-display-ids' initialization")
|
|
|
|
|
(should-not jupyter-display-ids)
|
|
|
|
|
(should (eq (jupyter-insert id msg) :text/plain))
|
|
|
|
|
(should (hash-table-p jupyter-display-ids))
|
|
|
|
|
(should (equal (buffer-string) "foo\n")))
|
|
|
|
|
(ert-info ("`jupyter-display-ids' is updated with ID")
|
|
|
|
|
(should (not (null (gethash id jupyter-display-ids)))))
|
|
|
|
|
(ert-info ("IDs are `eq'")
|
|
|
|
|
;; This is done so that they are comparable as text properties.
|
|
|
|
|
(should (eq (gethash id jupyter-display-ids) id)))
|
|
|
|
|
(ert-info ("Text property added to inserted text")
|
|
|
|
|
(should (jupyter-test-display-id-all id (point-min) (point-max)))))))
|
|
|
|
|
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(ert-deftest jupyter-delete-current-display ()
|
|
|
|
|
:tags '(mime display-id)
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(let ((id1 "1")
|
|
|
|
|
(id2 "2")
|
|
|
|
|
(msg (list :data (list :text/plain "foo"))))
|
|
|
|
|
(ert-info ("Actually deletes text with display ID")
|
|
|
|
|
(jupyter-insert id1 msg)
|
|
|
|
|
(should (equal (buffer-string) "foo\n"))
|
|
|
|
|
(goto-char (point-min))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(jupyter-delete-current-display)
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(should (= (point-min) (point-max))))
|
|
|
|
|
(ert-info ("Does not do anything if no display ID at point")
|
|
|
|
|
(insert "bar")
|
|
|
|
|
(goto-char (point-min))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(jupyter-delete-current-display)
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(should (equal (buffer-string) "bar"))
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(ert-info ("Deletes only text with the same display ID")
|
|
|
|
|
(jupyter-insert id1 msg)
|
|
|
|
|
(jupyter-insert id2 msg)
|
|
|
|
|
(goto-char (point-min))
|
2018-11-14 13:15:29 -06:00
|
|
|
|
(jupyter-delete-current-display)
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(should (equal (buffer-string) "foo\n"))
|
|
|
|
|
(should (jupyter-test-display-id-all id2 (point-min) (point-max)))
|
|
|
|
|
(erase-buffer)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-update-display ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(mime display-id)
|
2018-11-10 02:35:03 -06:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(let ((id1 "1")
|
|
|
|
|
(id2 "2")
|
|
|
|
|
(msg1 (list :data (list :text/plain "foo")))
|
|
|
|
|
(msg2 (list :data (list :text/plain "bar"))))
|
|
|
|
|
(ert-info ("Text with matching display ID is actually updated")
|
|
|
|
|
(jupyter-insert id1 msg1)
|
|
|
|
|
(jupyter-insert id2 msg2)
|
|
|
|
|
(should (equal (buffer-string) "foo\nbar\n"))
|
|
|
|
|
(should (jupyter-test-display-id-all
|
|
|
|
|
id1 (point-min) (+ 4 (point-min))))
|
|
|
|
|
(should (jupyter-test-display-id-all
|
|
|
|
|
id2 (- (point-max) 4) (point-max)))
|
|
|
|
|
(jupyter-update-display "1" (list :data (list :text/plain "baz")))
|
|
|
|
|
(should (equal (buffer-string) "baz\nbar\n"))
|
|
|
|
|
(should (jupyter-test-display-id-all
|
|
|
|
|
id1 (point-min) (+ 4 (point-min))))
|
|
|
|
|
(should (jupyter-test-display-id-all
|
|
|
|
|
id2 (- (point-max) 4) (point-max)))
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(ert-info ("All displays are updated")
|
|
|
|
|
(jupyter-insert id1 msg1)
|
|
|
|
|
(jupyter-insert id1 msg1)
|
|
|
|
|
(pop-to-buffer (current-buffer))
|
|
|
|
|
(should (equal (buffer-string) "foo\nfoo\n"))
|
|
|
|
|
(should (jupyter-test-display-id-all
|
|
|
|
|
id1 (point-min) (point-max)))
|
|
|
|
|
(jupyter-update-display "1" (list :data (list :text/plain "baz")))
|
|
|
|
|
(should (equal (buffer-string) "baz\nbaz\n"))
|
|
|
|
|
(should (jupyter-test-display-id-all
|
|
|
|
|
id1 (point-min) (point-max)))))))
|
|
|
|
|
|
2018-11-17 15:24:17 -06:00
|
|
|
|
(ert-deftest jupyter-insert-html ()
|
|
|
|
|
:tags '(mime)
|
|
|
|
|
(ert-info ("Correct libxml parser is called depending on prolog")
|
|
|
|
|
(ert-info ("XML prolog means to parse as XML")
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(cl-letf* ((xml-parser-called nil)
|
|
|
|
|
((symbol-function #'libxml-parse-xml-region)
|
|
|
|
|
(lambda (&rest _)
|
|
|
|
|
(prog1 nil
|
|
|
|
|
(setq xml-parser-called t)))))
|
|
|
|
|
(jupyter-insert :text/html "<?xml version=\"1.0\" encoding=\"UTF-8\"?><p>hello</p>")
|
|
|
|
|
(should xml-parser-called))))
|
|
|
|
|
(ert-info ("Parse as html")
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(cl-letf* ((html-parser-called nil)
|
|
|
|
|
((symbol-function #'libxml-parse-html-region)
|
|
|
|
|
(lambda (&rest _)
|
|
|
|
|
(prog1 nil
|
|
|
|
|
(setq html-parser-called t)))))
|
|
|
|
|
(jupyter-insert :text/html "<p>hello</p>")
|
|
|
|
|
(should html-parser-called))))))
|
|
|
|
|
|
2019-02-15 21:48:00 -06:00
|
|
|
|
(ert-deftest jupyter-with-display-buffer ()
|
|
|
|
|
:tags '(buffers)
|
|
|
|
|
(jupyter-with-display-buffer "foo" t)
|
|
|
|
|
(jupyter-with-display-buffer "foo" nil
|
|
|
|
|
(should (= jupyter-display-buffer-marker (point-min)))
|
|
|
|
|
(insert "12345")
|
|
|
|
|
(should (= jupyter-display-buffer-marker (point-max)))
|
|
|
|
|
(goto-char (point-min)))
|
|
|
|
|
(jupyter-with-display-buffer "foo" nil
|
|
|
|
|
(should (= (point) (point-max)))
|
|
|
|
|
(should (= jupyter-display-buffer-marker (point-max)))
|
|
|
|
|
(insert "foobar")
|
|
|
|
|
(should (= jupyter-display-buffer-marker (point-max)))
|
|
|
|
|
(should (equal (buffer-string) "12345foobar")))
|
|
|
|
|
(jupyter-with-display-buffer "foo" t
|
|
|
|
|
(should (equal (buffer-string) ""))
|
|
|
|
|
(should (= (point-min) (point-max)))
|
|
|
|
|
(should (= jupyter-display-buffer-marker (point-min)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-with-contol-code-handling ()
|
|
|
|
|
:tags '(buffers)
|
|
|
|
|
(jupyter-with-display-buffer "foo" t)
|
|
|
|
|
(jupyter-with-display-buffer "foo" nil
|
|
|
|
|
(insert "foo\r"))
|
|
|
|
|
(jupyter-with-display-buffer "foo" nil
|
|
|
|
|
(should (equal (buffer-string) "foo\r"))
|
|
|
|
|
(jupyter-test-text-has-property 'invisible t '(4))
|
|
|
|
|
(insert "foo\r"))
|
|
|
|
|
(jupyter-with-display-buffer "foo" nil
|
|
|
|
|
(should (equal (buffer-string) "foo\r"))
|
|
|
|
|
(insert "bar\r\nbaz\rfoo"))
|
|
|
|
|
(jupyter-with-display-buffer "foo" nil
|
|
|
|
|
(should (equal (buffer-string) "bar\nfoo"))))
|
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;;; Messages
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-message-identities ()
|
|
|
|
|
:tags '(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-deftest jupyter-message-headers ()
|
|
|
|
|
:tags '(messages)
|
|
|
|
|
(let* ((session (jupyter-session :key (jupyter-new-uuid)))
|
|
|
|
|
(id (jupyter-new-uuid))
|
|
|
|
|
(header (jupyter--message-header session :input-reply id)))
|
|
|
|
|
(should (plist-get header :msg_id))
|
|
|
|
|
(should (plist-get header :date))
|
|
|
|
|
(should (eq (plist-get header :msg_type) :input-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) (jupyter-session-id session)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-message-time ()
|
|
|
|
|
:tags '(messages)
|
2018-11-17 16:26:03 -06:00
|
|
|
|
(let ((tz (getenv "TZ")))
|
|
|
|
|
(setenv "TZ" "UTC0")
|
2019-05-31 14:57:00 -05:00
|
|
|
|
(should (equal (jupyter-encode-time '(23385 27704 100000))
|
2018-11-17 16:26:03 -06:00
|
|
|
|
"2018-07-26T06:37:44.100000"))
|
2019-05-31 14:57:00 -05:00
|
|
|
|
(should (equal (jupyter-decode-time "2018-07-26T01:37:44.100")
|
2018-11-17 16:26:03 -06:00
|
|
|
|
'(23385 9704 100000 0)))
|
2019-05-31 14:57:00 -05:00
|
|
|
|
(should (equal (jupyter-decode-time "2018-07-26T01:37:44.10011122")
|
2018-11-17 16:26:03 -06:00
|
|
|
|
'(23385 9704 100111 0)))
|
2019-05-31 14:57:00 -05:00
|
|
|
|
(should (equal (jupyter-decode-time "2018-07-26T01:37:44")
|
2018-11-17 16:26:03 -06:00
|
|
|
|
'(23385 9704 0 0)))
|
2019-05-31 14:57:00 -05:00
|
|
|
|
(should (equal (jupyter-decode-time "2018-07-26")
|
2018-11-17 16:26:03 -06:00
|
|
|
|
'(23385 3840 0 0)))
|
|
|
|
|
(setenv "TZ" tz)))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-message-signing ()
|
|
|
|
|
:tags '(messages)
|
|
|
|
|
(let ((session (jupyter-session :key "foo"))
|
|
|
|
|
(msg (list "" "{\"msg_id\":\"1\",\"msg_type\":\"execute_reply\"}" "{}" "{}" "{}"))
|
|
|
|
|
(signature "f9080fb30e80a1b424895b557b8249157d5f83d6fc897cb96a4d2fa54a1280e6"))
|
|
|
|
|
(should (equal signature
|
|
|
|
|
(jupyter-sign-message session msg #'jupyter-hmac-sha256)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-message-decoding ()
|
|
|
|
|
:tags '(messages)
|
|
|
|
|
(let ((session (jupyter-session)))
|
|
|
|
|
(ert-info ("Minimum message length")
|
|
|
|
|
(should-error (jupyter-decode-message session (list "1" "2" "3"))))
|
|
|
|
|
(ert-info ("Form of decoded message")
|
|
|
|
|
(let* ((session (jupyter-session :key "foo"))
|
|
|
|
|
(msg (list "f9080fb30e80a1b424895b557b8249157d5f83d6fc897cb96a4d2fa54a1280e6"
|
|
|
|
|
"{\"msg_id\":\"1\",\"msg_type\":\"execute_reply\"}" "{}" "{}" "{}"))
|
|
|
|
|
(plist (jupyter-decode-message session msg)))
|
|
|
|
|
(cl-loop
|
|
|
|
|
with true-msg = (list
|
|
|
|
|
:header '(message-part
|
|
|
|
|
"{\"msg_id\":\"1\",\"msg_type\":\"execute_reply\"}"
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(:msg_id "1" :msg_type "execute_reply"))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
:msg_id "1"
|
2020-09-12 13:26:12 -05:00
|
|
|
|
:msg_type "execute_reply"
|
2018-11-16 22:14:31 -06:00
|
|
|
|
:parent_header '(message-part "{}" nil)
|
|
|
|
|
:content '(message-part "{}" nil)
|
|
|
|
|
:metadata '(message-part "{}" nil)
|
|
|
|
|
:buffers nil)
|
|
|
|
|
for key in '(:header :msg_id :msg_type :content
|
|
|
|
|
:parent_header :metadata :buffers)
|
|
|
|
|
do (should (equal (plist-get true-msg key) (plist-get plist key))))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-message-encoding ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(messages)
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;; TODO
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-message-types ()
|
|
|
|
|
:tags '(client messages)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-client client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("Kernel info")
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-kernel-info-request))))
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(should res)
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "kernel_info_reply"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("Comm info")
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
|
|
|
|
(jupyter-comm-info-request))))
|
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "comm_info_reply"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("Execute")
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
2020-09-21 05:42:59 -05:00
|
|
|
|
(jupyter-execute-request :code "y = 1 + 2"))))
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "execute_reply"))))
|
2020-11-21 19:35:59 -06:00
|
|
|
|
(ert-info ("Input")
|
|
|
|
|
(cl-letf (((symbol-function 'read-from-minibuffer)
|
|
|
|
|
(lambda (_prompt &rest _args) "foo")))
|
|
|
|
|
(jupyter-mlet* ((msgs (jupyter-messages
|
|
|
|
|
(jupyter-execute-request :code "input('')")
|
|
|
|
|
jupyter-long-timeout)))
|
|
|
|
|
(let ((res (jupyter-find-message "execute_result" msgs)))
|
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "execute_result"))
|
|
|
|
|
(should (equal (jupyter-message-data res :text/plain) "'foo'"))))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("Inspect")
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-inspect-request
|
|
|
|
|
:code "list((1, 2, 3))"
|
|
|
|
|
:pos 2
|
|
|
|
|
:detail 0))))
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "inspect_reply"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("Complete")
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-complete-request
|
|
|
|
|
:code "foo = lis"
|
|
|
|
|
:pos 8))))
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "complete_reply"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("History")
|
2020-12-12 18:59:58 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-history-request
|
|
|
|
|
:hist-access-type "tail" :n 2))))
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "history_reply"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-info ("Is Complete")
|
2020-12-12 18:59:58 -06:00
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-is-complete-request
|
|
|
|
|
:code "for i in range(5):"))))
|
2020-11-22 17:19:23 -06:00
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "is_complete_reply"))))
|
2020-12-12 19:00:52 -06:00
|
|
|
|
(ert-info ("Shutdown")
|
|
|
|
|
(jupyter-mlet* ((res (jupyter-reply-message
|
|
|
|
|
(jupyter-shutdown-request))))
|
|
|
|
|
(should-not (null res))
|
|
|
|
|
(should (json-plist-p res))
|
|
|
|
|
(should (string= (jupyter-message-type res) "shutdown_reply"))
|
|
|
|
|
;; TODO: Ensure we give the kernel process time to die off
|
|
|
|
|
))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
2019-01-21 23:18:50 -06:00
|
|
|
|
(ert-deftest jupyter-message-lambda ()
|
|
|
|
|
:tags '(messages)
|
|
|
|
|
(let ((msg (jupyter-test-message
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(make-jupyter-request) "execute_reply"
|
2019-01-21 23:18:50 -06:00
|
|
|
|
(list :status "idle" :data (list :text/plain "foo")))))
|
|
|
|
|
(should (equal (funcall (jupyter-message-lambda (status)
|
|
|
|
|
status)
|
|
|
|
|
msg)
|
|
|
|
|
"idle"))
|
|
|
|
|
(should (equal (funcall (jupyter-message-lambda ((res text/plain))
|
|
|
|
|
res)
|
|
|
|
|
msg)
|
|
|
|
|
"foo"))
|
|
|
|
|
(should (equal (funcall (jupyter-message-lambda (status (res text/plain))
|
|
|
|
|
(cons status res))
|
|
|
|
|
msg)
|
|
|
|
|
(cons "idle" "foo")))))
|
|
|
|
|
|
2021-02-06 10:21:28 -06:00
|
|
|
|
;;; Channels
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-zmq-channel ()
|
|
|
|
|
:tags '(channels zmq)
|
|
|
|
|
(let* ((port (car (jupyter-available-local-ports 1)))
|
|
|
|
|
(channel (jupyter-zmq-channel
|
|
|
|
|
:type :shell
|
|
|
|
|
:endpoint (format "tcp://127.0.0.1:%s" port))))
|
|
|
|
|
(ert-info ("Starting the channel")
|
|
|
|
|
(should-not (jupyter-alive-p channel))
|
|
|
|
|
(jupyter-start channel :identity "foo")
|
|
|
|
|
(should (jupyter-alive-p channel))
|
|
|
|
|
(should (equal (zmq-socket-get (oref channel socket)
|
|
|
|
|
zmq-ROUTING-ID)
|
|
|
|
|
"foo")))
|
|
|
|
|
(ert-info ("Stopping the channel")
|
|
|
|
|
(let ((sock (oref channel socket)))
|
|
|
|
|
(jupyter-stop channel)
|
|
|
|
|
(should-not (jupyter-alive-p channel))
|
|
|
|
|
;; Ensure the socket was disconnected
|
|
|
|
|
(should-error (zmq-send sock "foo" zmq-NOBLOCK) :type 'zmq-EAGAIN)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-hb-channel ()
|
|
|
|
|
:tags '(channels)
|
|
|
|
|
(should (eq (oref (jupyter-hb-channel) type) :hb))
|
|
|
|
|
(let* ((port (car (jupyter-available-local-ports 1)))
|
|
|
|
|
(channel (jupyter-hb-channel
|
|
|
|
|
:endpoint (format "tcp://127.0.0.1:%s" port)
|
|
|
|
|
:session (jupyter-session)))
|
|
|
|
|
(died-cb-called nil)
|
|
|
|
|
(jupyter-hb-max-failures 1))
|
|
|
|
|
(oset channel time-to-dead 0.1)
|
|
|
|
|
(should-not (jupyter-alive-p channel))
|
|
|
|
|
(should-not (jupyter-hb-beating-p channel))
|
|
|
|
|
(should (oref channel paused))
|
|
|
|
|
(oset channel beating t)
|
|
|
|
|
(jupyter-start channel)
|
|
|
|
|
(jupyter-hb-on-kernel-dead channel (lambda () (setq died-cb-called t)))
|
|
|
|
|
(should (jupyter-alive-p channel))
|
|
|
|
|
;; `jupyter-hb-unpause' needs to explicitly called
|
|
|
|
|
(should (oref channel paused))
|
|
|
|
|
(jupyter-hb-unpause channel)
|
|
|
|
|
(sleep-for 0.2)
|
|
|
|
|
;; It seems the timers are run after returning from the first `sleep-for'
|
|
|
|
|
;; call.
|
|
|
|
|
(sleep-for 0.1)
|
|
|
|
|
(should (oref channel paused))
|
|
|
|
|
(should-not (oref channel beating))
|
|
|
|
|
(should died-cb-called)
|
|
|
|
|
(should (jupyter-alive-p channel))
|
|
|
|
|
(should-not (jupyter-hb-beating-p channel))))
|
|
|
|
|
|
2019-05-07 00:41:44 -05:00
|
|
|
|
;;; GC
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-weak-ref ()
|
|
|
|
|
:tags '(gc)
|
|
|
|
|
(let (ref)
|
|
|
|
|
(let ((obj (list 1)))
|
|
|
|
|
(setq ref (jupyter-weak-ref obj)))
|
2019-05-09 12:51:00 -05:00
|
|
|
|
(let ((table (make-hash-table)))
|
|
|
|
|
(dotimes (_ gc-cons-threshold)
|
|
|
|
|
(puthash (random) t table)))
|
2019-05-07 20:20:52 -05:00
|
|
|
|
(garbage-collect)
|
|
|
|
|
(garbage-collect)
|
2019-05-07 00:41:44 -05:00
|
|
|
|
(garbage-collect)
|
|
|
|
|
(should-not (jupyter-weak-ref-resolve ref))))
|
|
|
|
|
|
Refactor of `jupyter-kernel-manager.el`
This refactor implements a new class hierarchy to manage the lifetime of a
Jupyter kernel. The first node in this hierarchy is the
`jupyter-kernel-lifetime` class which defines a set of methods to manage the
lifetime of a kernel. An object that inherits from `jupyter-kernel-lifetime` is
stating that it has an association with a kernel and can be used to manage the
lifetime of the associated kernel.
The `jupyter-meta-kernel` class inherits from `jupyter-kernel-lifetime` and
mainly defines a `spec` slot used to hold the `kernelspec` from which a command
can be constructed to start a kernel and a `session` slot used to hold the
`jupyter-session` object that clients can use to establish communication with a
kernel once its live. Concrete classes that actually launch kernels are
intended to inherit from this class and use its slots.
`jupyter-kernel-process` manages the lifetime of a kernel started as a process
using the function `start-file-process`, `jupyter-command-kernel` calls the
`jupyter kernel` shell command to start a kernel, finally `jupyter-spec-kernel`
uses the `spec` slot to construct a shell command to start a kernel.
A `jupyter-kernel-manager` now consists of a `kernel` slot that holds a
`jupyter-meta-kernel` and a `control-channel` slot and inherits from
`jupyter-kernel-lifetime`. The `jupyter-kernel-lifetime` methods of the manager
just defer to those of `kernel` while also taking into account the
`control-channel`.
* jupyter-base.el (jupyter-write-connection-file): New function.
* jupyter-channel-ioloop.el
(jupyter-channel-ioloop-add-start-channel-event): Remove `sleep-for` call.
The startup message is not so important anymore.
* jupyter-client.el (jupyter-wait-until-startup: New function.
* jupyter-kernel-manager.el (jupyter-kernel-lifetime)
(jupyter-kernel, jupyter-kernel-process, jupyter-command-kernel)
(jupyter-spec-kernel): New classes.
(jupyter-kernel-manager): Inherit from jupyter-kernel-lifetime only and
implement its methods.
(jupyter-kernel-manager--cleanup, jupyter-kernel-managers)
(jupyter-delete-all-kernels, jupyter--kernel-sentinel)
(jupyter--start-kernel): Remove and remove related, their functionality has
been generalized in the new classes.
(jupyter-interrupt-kernel, jupyter-shutdown-kernel)
(jupyter-start-channels, jupyter-start-kernel, jupyter-kernel-alive-p)
(jupyter-kill-kernel): Refactor and implement to use the new class hierarchy.
* test/jupyter-test.el: Refactor tests to account for changes.
(jupyter-write-connect-file, jupyter-command-kernel): New tests.
* jupyter-kernelspec.el (jupyter-guess-kernelspec): New function.
2019-05-09 08:31:00 -05:00
|
|
|
|
;;; Kernel
|
|
|
|
|
|
2019-05-16 20:23:53 -05:00
|
|
|
|
(ert-deftest jupyter-locate-python ()
|
|
|
|
|
:tags '(kernel)
|
|
|
|
|
;; TODO: Generalize for Windows
|
|
|
|
|
(skip-unless (not (memq system-type '(ms-dos windows-nt cygwin))))
|
|
|
|
|
;; Load file name handlers
|
|
|
|
|
(ignore (file-remote-p "/ssh:foo:"))
|
|
|
|
|
(cl-letf (((symbol-function #'jupyter-command)
|
|
|
|
|
(lambda (&rest _)
|
|
|
|
|
"{\"data\": [\"/home/USER/.local/share/jupyter\", \
|
|
|
|
|
\"/home/USER/.julia/conda/3/share/jupyter\", \
|
|
|
|
|
\"/usr/local/share/jupyter\", \
|
|
|
|
|
\"/usr/share/jupyter\"]}"))
|
|
|
|
|
((symbol-function #'file-exists-p)
|
|
|
|
|
(lambda (file)
|
|
|
|
|
(member file '("/home/USER/.julia/conda/3/bin/python3"
|
|
|
|
|
"/ssh:foo:/usr/local/bin/python3")))))
|
|
|
|
|
(should (equal (jupyter-locate-python) "/home/USER/.julia/conda/3/bin/python3"))
|
|
|
|
|
(let ((default-directory "/ssh:foo:"))
|
|
|
|
|
(should (equal (jupyter-locate-python) "/usr/local/bin/python3"))))
|
|
|
|
|
(cl-letf (((symbol-function #'jupyter-command)
|
|
|
|
|
(lambda (&rest _)
|
|
|
|
|
"{\"foo\": [\"/home/USER/.local/share/jupyter\", \
|
|
|
|
|
\"/usr/share/jupyter\"]}")))
|
|
|
|
|
(should-error (jupyter-locate-python)))
|
|
|
|
|
(cl-letf (((symbol-function #'jupyter-command)
|
|
|
|
|
(lambda (&rest _)
|
|
|
|
|
"{\"data\": [\"/home/USER/.local/share/jupyter\", \
|
|
|
|
|
\"/usr/share/jupyter\"]}"))
|
|
|
|
|
((symbol-function #'file-exists-p)
|
|
|
|
|
(lambda (_) nil)))
|
|
|
|
|
(should-error (jupyter-locate-python))))
|
|
|
|
|
|
2021-02-06 10:21:28 -06:00
|
|
|
|
;; FIXME: Revisit after transition
|
|
|
|
|
(ert-deftest jupyter-kernel-process ()
|
|
|
|
|
:tags '(kernel)
|
|
|
|
|
;; TODO: `jupyter-do-interrupt'
|
|
|
|
|
(ert-info ("`jupyter-do-launch', `jupyter-do-shutdown'")
|
|
|
|
|
(cl-macrolet
|
|
|
|
|
((confirm-shutdown-state
|
|
|
|
|
()
|
|
|
|
|
`(progn
|
|
|
|
|
(should-not (jupyter-alive-p kernel))
|
|
|
|
|
(should (jupyter-kernel-spec kernel))
|
|
|
|
|
(should-not (jupyter-kernel-session kernel))
|
|
|
|
|
(should-not (process-live-p (jupyter-process kernel)))))
|
|
|
|
|
(confirm-launch-state
|
|
|
|
|
()
|
|
|
|
|
`(progn
|
|
|
|
|
(should (jupyter-alive-p kernel))
|
|
|
|
|
(should (jupyter-kernel-spec kernel))
|
|
|
|
|
(should (jupyter-kernel-session kernel))
|
|
|
|
|
(should (process-live-p (jupyter-process kernel))))))
|
|
|
|
|
(let ((kernel (jupyter-kernel-process
|
|
|
|
|
:spec (jupyter-guess-kernelspec "python"))))
|
|
|
|
|
(confirm-shutdown-state)
|
|
|
|
|
(jupyter-launch kernel)
|
|
|
|
|
(confirm-launch-state)
|
|
|
|
|
(jupyter-do-shutdown kernel)
|
|
|
|
|
(confirm-shutdown-state))))
|
|
|
|
|
;; (let (called)
|
|
|
|
|
;; (let* ((plist '(:argv ["sleep" "60"] :env nil :interrupt_mode "signal"))
|
|
|
|
|
;; (kernel (jupyter-kernel
|
|
|
|
|
;; :spec (make-jupyter-kernelspec
|
|
|
|
|
;; :name "sleep"
|
|
|
|
|
;; :plist plist))))
|
|
|
|
|
;; (let ((jupyter-long-timeout 0.01))
|
|
|
|
|
;; (jupyter-launch kernel))
|
|
|
|
|
;; (cl-letf (((symbol-function #'interrupt-process)
|
|
|
|
|
;; (lambda (&rest args)
|
|
|
|
|
;; (setq called t))))
|
|
|
|
|
;; (jupyter-do-interrupt kernel))
|
|
|
|
|
;; (should called)
|
|
|
|
|
;; (setq called nil)
|
|
|
|
|
;; (jupyter-do-shutdown kernel)))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; (let ((kernel (jupyter--kernel-process
|
|
|
|
|
;; :spec (jupyter-guess-kernelspec "python"))))
|
|
|
|
|
;; (ert-info ("Session set after kernel starts")
|
|
|
|
|
;; (should-not (jupyter-kernel-alive-p kernel))
|
|
|
|
|
;; (jupyter-start-kernel kernel)
|
|
|
|
|
;; (should (jupyter-kernel-alive-p kernel))
|
|
|
|
|
;; (should (oref kernel session))
|
|
|
|
|
;; (jupyter-kill-kernel kernel)
|
|
|
|
|
;; (should-not (jupyter-kernel-alive-p kernel)))
|
|
|
|
|
;; (ert-info ("Can we communicate?")
|
|
|
|
|
;; (let ((manager (jupyter-kernel-manager :kernel kernel)))
|
|
|
|
|
;; (jupyter-start-kernel manager)
|
|
|
|
|
;; (unwind-protect
|
|
|
|
|
;; (let ((jupyter-current-client
|
|
|
|
|
;; (jupyter-make-client manager 'jupyter-kernel-client)))
|
|
|
|
|
;; (jupyter-start-channels jupyter-current-client)
|
|
|
|
|
;; (unwind-protect
|
|
|
|
|
;; (progn
|
|
|
|
|
;; (jupyter-wait-until-startup jupyter-current-client)
|
|
|
|
|
;; (should (equal (jupyter-eval "1 + 1") "2")))
|
|
|
|
|
;; (jupyter-stop-channels jupyter-current-client)))
|
|
|
|
|
;; (jupyter-shutdown-kernel manager)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-delete-connection-files ()
|
|
|
|
|
:tags '(kernel process)
|
|
|
|
|
(let ((jupyter--kernel-processes
|
|
|
|
|
(cl-loop repeat 2
|
|
|
|
|
collect (list nil (make-temp-file "jupyter-test")))))
|
|
|
|
|
(jupyter-delete-connection-files)
|
|
|
|
|
(should-not
|
|
|
|
|
(cl-loop for (_ conn-file) in jupyter--kernel-processes
|
|
|
|
|
thereis (file-exists-p conn-file)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-kernel-process/connection-file-management ()
|
|
|
|
|
:tags '(kernel process)
|
|
|
|
|
(let (jupyter--kernel-processes)
|
|
|
|
|
(pcase-let ((`(,kernelA ,kernelB)
|
|
|
|
|
(cl-loop
|
|
|
|
|
repeat 2
|
|
|
|
|
collect (jupyter-kernel :spec "python"))))
|
|
|
|
|
(jupyter-launch kernelA)
|
|
|
|
|
(should (= (length jupyter--kernel-processes) 1))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(pcase-let* ((`(,processA ,conn-fileA) (car jupyter--kernel-processes))
|
|
|
|
|
(process-bufferA (process-buffer processA)))
|
|
|
|
|
(should (eq processA (jupyter-kernel-process-process kernelA)))
|
|
|
|
|
(jupyter-do-shutdown kernelA)
|
|
|
|
|
(should-not (process-live-p processA))
|
|
|
|
|
(should (file-exists-p conn-fileA))
|
|
|
|
|
(should (buffer-live-p process-bufferA))
|
|
|
|
|
(jupyter-launch kernelB)
|
|
|
|
|
(should-not (buffer-live-p process-bufferA))
|
|
|
|
|
(should-not (file-exists-p conn-fileA))
|
|
|
|
|
(should (= (length jupyter--kernel-processes) 1))
|
|
|
|
|
(pcase-let ((`(,processB ,conn-fileB)
|
|
|
|
|
(car jupyter--kernel-processes)))
|
|
|
|
|
(should-not (eq processA processB))
|
|
|
|
|
(should-not (string= conn-fileA conn-fileB))))
|
|
|
|
|
(jupyter-do-shutdown kernelA)
|
|
|
|
|
(jupyter-do-shutdown kernelB)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-kernel-process/on-unexpected-exit ()
|
|
|
|
|
:tags '(kernel process)
|
|
|
|
|
(skip-unless nil)
|
|
|
|
|
(let ((kernel (jupyter-kernel :spec "python"))
|
|
|
|
|
called)
|
|
|
|
|
(jupyter-launch
|
|
|
|
|
kernel (lambda (kernel)
|
|
|
|
|
(setq called t)))
|
|
|
|
|
(let ((process (jupyter--kernel-process kernel)))
|
|
|
|
|
(kill-process process)
|
|
|
|
|
(sleep-for 0.01)
|
|
|
|
|
(should called))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-session-with-random-ports ()
|
|
|
|
|
:tags '(kernel)
|
|
|
|
|
(let ((session (jupyter-session-with-random-ports)))
|
|
|
|
|
(should (jupyter-session-p session))
|
|
|
|
|
(let ((process-exists
|
|
|
|
|
(cl-loop
|
|
|
|
|
for p in (process-list)
|
|
|
|
|
thereis (string= (process-name p)
|
|
|
|
|
"jupyter-session-with-random-ports"))))
|
|
|
|
|
(should-not process-exists))
|
|
|
|
|
(cl-destructuring-bind (&key hb_port stdin_port
|
|
|
|
|
control_port shell_port iopub_port
|
|
|
|
|
&allow-other-keys)
|
|
|
|
|
(jupyter-session-conn-info session)
|
|
|
|
|
;; Verify the ports are open for us
|
|
|
|
|
(cl-loop
|
|
|
|
|
for port in (list hb_port stdin_port
|
|
|
|
|
control_port shell_port iopub_port)
|
|
|
|
|
for proc = (make-network-process
|
|
|
|
|
:name "jupyter-test"
|
|
|
|
|
:server t
|
|
|
|
|
:host "127.0.0.1"
|
|
|
|
|
:service port)
|
|
|
|
|
do (delete-process proc)))))
|
|
|
|
|
|
2020-04-18 21:28:00 -05:00
|
|
|
|
(ert-deftest jupyter-expand-environment-variables ()
|
|
|
|
|
:tags '(kernel)
|
|
|
|
|
(let ((process-environment
|
|
|
|
|
(append (list "FOO=bar")
|
|
|
|
|
process-environment)))
|
|
|
|
|
(should (string= (jupyter-expand-environment-variables "xy") "xy"))
|
|
|
|
|
(should (string= (jupyter-expand-environment-variables "${FOO}xy") "barxy"))
|
|
|
|
|
(should (string= (jupyter-expand-environment-variables "x${FOO}y") "xbary"))
|
|
|
|
|
(should (string= (jupyter-expand-environment-variables "xy${FOO}") "xybar"))
|
|
|
|
|
(should (string= (jupyter-expand-environment-variables "${FOO}x${FOO}y") "barxbary"))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-process-environment ()
|
|
|
|
|
:tags '(kernel)
|
|
|
|
|
(let ((process-environment
|
|
|
|
|
(append (list "FOO=bar")
|
|
|
|
|
process-environment)))
|
|
|
|
|
(should (equal (jupyter-process-environment
|
|
|
|
|
(make-jupyter-kernelspec
|
|
|
|
|
:plist '(:env (:ONE "x" :TWO "x${FOO}"))))
|
|
|
|
|
'("ONE=x" "TWO=xbar")))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-kernel-argv ()
|
|
|
|
|
:tags '(kernel)
|
|
|
|
|
(should (equal (jupyter-kernel-argv
|
|
|
|
|
(make-jupyter-kernelspec
|
|
|
|
|
:resource-directory "/ssh::~"
|
|
|
|
|
:plist '(:argv
|
|
|
|
|
["python" "-c" "kernel" "{connection_file}"
|
|
|
|
|
"{resource_dir}"]
|
|
|
|
|
:env (:ONE "x" :TWO "x${FOO}")))
|
|
|
|
|
|
|
|
|
|
"/ssh::conn-file.json")
|
|
|
|
|
'("python" "-c" "kernel" "conn-file.json" "~"))))
|
|
|
|
|
|
2019-07-08 18:47:30 -05:00
|
|
|
|
;;; Environment
|
|
|
|
|
|
2020-03-12 21:53:00 -05:00
|
|
|
|
(ert-deftest jupyter-canonicalize-language-string ()
|
|
|
|
|
:tags '(base env)
|
|
|
|
|
(should (equal (jupyter-canonicalize-language-string "Wolfram Language")
|
2020-04-09 11:13:10 -05:00
|
|
|
|
"Wolfram-Language"))
|
|
|
|
|
(should (equal (jupyter-canonicalize-language-string "R") "R"))
|
2020-03-12 21:53:00 -05:00
|
|
|
|
(should (equal (jupyter-canonicalize-language-string "/gnu/store/python")
|
|
|
|
|
"python")))
|
|
|
|
|
|
2019-07-08 18:47:30 -05:00
|
|
|
|
(ert-deftest jupyter-runtime-directory ()
|
|
|
|
|
:tags '(env)
|
|
|
|
|
(let (dir-created jupyter-runtime-directory)
|
|
|
|
|
(cl-letf (((symbol-function #'jupyter-command)
|
|
|
|
|
(lambda (&rest _) "foo"))
|
|
|
|
|
((symbol-function #'make-directory)
|
|
|
|
|
(lambda (&rest _)
|
|
|
|
|
(setq dir-created t))))
|
|
|
|
|
(jupyter-runtime-directory)
|
|
|
|
|
(should dir-created)
|
|
|
|
|
(setq dir-created nil)
|
2020-04-23 23:39:34 -05:00
|
|
|
|
(should (equal jupyter-runtime-directory "foo/"))
|
2019-07-08 23:07:14 -05:00
|
|
|
|
(let ((default-directory "/ssh:foo:/"))
|
2020-04-23 23:39:34 -05:00
|
|
|
|
(should (equal (jupyter-runtime-directory) "/ssh:foo:foo/"))
|
2019-07-08 18:47:30 -05:00
|
|
|
|
(ert-info ("Variable definition is always local")
|
|
|
|
|
(setq jupyter-runtime-directory nil)
|
|
|
|
|
(jupyter-runtime-directory)
|
2020-04-23 23:39:34 -05:00
|
|
|
|
(should (equal jupyter-runtime-directory "foo/")))))))
|
2019-07-08 18:47:30 -05:00
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;;; Client
|
2018-11-08 21:54:20 -06:00
|
|
|
|
|
2021-02-06 10:21:28 -06:00
|
|
|
|
;; TODO: Different values of the session argument
|
|
|
|
|
;;
|
|
|
|
|
;; FIXME: Re-work after refactoring the kernelspec -> connectable kernel code paths.
|
|
|
|
|
(ert-deftest jupyter-comm-initialize ()
|
|
|
|
|
:tags '(client init)
|
|
|
|
|
(skip-unless nil)
|
|
|
|
|
(jupyter-test-with-python-client client
|
|
|
|
|
(with-slots (session kcomm) client
|
|
|
|
|
(ert-info ("Client session")
|
|
|
|
|
(should (string= (jupyter-session-key session)
|
|
|
|
|
(plist-get conn-info :key)))
|
|
|
|
|
(should (equal (jupyter-session-conn-info session)
|
|
|
|
|
conn-info)))
|
|
|
|
|
(ert-info ("Heartbeat channel initialized")
|
|
|
|
|
(should (eq session (oref (oref kcomm hb) session)))
|
|
|
|
|
(should (string= (oref (oref kcomm hb) endpoint)
|
|
|
|
|
(format "tcp://127.0.0.1:%d"
|
|
|
|
|
(plist-get conn-info :hb_port)))))
|
|
|
|
|
(ert-info ("Shell, iopub, stdin initialized")
|
|
|
|
|
(cl-loop
|
|
|
|
|
for channel in '(:shell :iopub :stdin)
|
|
|
|
|
for port_sym = (intern (concat (symbol-name channel) "_port"))
|
|
|
|
|
do
|
|
|
|
|
(should (plist-member (plist-get channels channel) :alive-p))
|
|
|
|
|
(should (plist-member (plist-get channels channel) :endpoint))
|
|
|
|
|
(should
|
|
|
|
|
(string= (plist-get (plist-get channels channel) :endpoint)
|
|
|
|
|
(format "tcp://127.0.0.1:%d"
|
|
|
|
|
(plist-get conn-info port_sym))))))
|
|
|
|
|
(ert-info ("Initialization stops any running channels")
|
|
|
|
|
(should-not (jupyter-channels-running-p client))
|
|
|
|
|
(jupyter-start-channels client)
|
|
|
|
|
(should (jupyter-channels-running-p client))
|
|
|
|
|
(jupyter-comm-initialize client conn-info)
|
|
|
|
|
(should-not (jupyter-channels-running-p client)))
|
|
|
|
|
(ert-info ("Invalid signature scheme")
|
|
|
|
|
(plist-put conn-info :signature_scheme "hmac-foo")
|
|
|
|
|
(should-error (jupyter-comm-initialize client conn-info))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-write-connection-file ()
|
|
|
|
|
:tags '(client)
|
|
|
|
|
(let* ((conn-info '(:kernel_name "python"
|
|
|
|
|
:transport "tcp" :ip "127.0.0.1"
|
|
|
|
|
:signature_scheme "hmac-sha256"
|
|
|
|
|
:key "00a2cadb-3da7-45d2-b394-dbd01b5f80eb"
|
|
|
|
|
:hb_port 45473 :stdin_port 40175
|
|
|
|
|
:control_port 36301
|
|
|
|
|
:shell_port 39263 :iopub_port 36731))
|
|
|
|
|
(conn-file (jupyter-write-connection-file
|
|
|
|
|
(jupyter-session
|
|
|
|
|
:conn-info conn-info))))
|
|
|
|
|
(should (file-exists-p conn-file))
|
|
|
|
|
(should (string= (file-name-directory conn-file) (jupyter-runtime-directory)))
|
|
|
|
|
(should (equal (jupyter-read-plist conn-file) conn-info))))
|
|
|
|
|
|
|
|
|
|
;; FIXME: Revisit after transition
|
|
|
|
|
(ert-deftest jupyter-client-channels ()
|
|
|
|
|
:tags '(client channels)
|
|
|
|
|
(skip-unless nil)
|
|
|
|
|
(ert-info ("Starting/stopping channels")
|
|
|
|
|
;; FIXME: Without a new client, I'm getting
|
|
|
|
|
;;
|
|
|
|
|
;; (zmq-EFSM "Operation cannot be accomplished in current state")
|
|
|
|
|
;;
|
|
|
|
|
;; on the `jupyter-connect-repl' test pretty consistently.
|
|
|
|
|
(let ((jupyter-test-with-new-client t))
|
|
|
|
|
(jupyter-test-with-python-client client
|
|
|
|
|
(jupyter-stop-channels client)
|
|
|
|
|
(cl-loop
|
|
|
|
|
for channel in '(:hb :shell :iopub :stdin)
|
|
|
|
|
for alive-p = (jupyter-alive-p client channel)
|
|
|
|
|
do (should-not alive-p))
|
|
|
|
|
(jupyter-start-channels client)
|
|
|
|
|
(cl-loop
|
|
|
|
|
for channel in '(:hb :shell :iopub :stdin)
|
|
|
|
|
for alive-p = (jupyter-alive-p client channel)
|
|
|
|
|
do (should alive-p))
|
|
|
|
|
(jupyter-stop-channels client)
|
|
|
|
|
(cl-loop
|
|
|
|
|
for channel in '(:hb :shell :iopub :stdin)
|
|
|
|
|
for alive-p = (jupyter-alive-p client channel)
|
|
|
|
|
do (should-not alive-p))))))
|
|
|
|
|
|
2018-11-08 21:54:20 -06:00
|
|
|
|
(ert-deftest jupyter-inhibited-handlers ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(client handlers)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-client client
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-mlet* ((req (jupyter-kernel-info-request
|
|
|
|
|
:handlers '(not "stream"))))
|
2018-11-08 21:54:20 -06:00
|
|
|
|
(should (equal (jupyter-request-inhibited-handlers req)
|
2020-09-12 13:26:12 -05:00
|
|
|
|
'("stream")))
|
2020-04-12 02:47:30 -05:00
|
|
|
|
(should-not (jupyter--request-allows-handler-p
|
2018-11-14 13:15:29 -06:00
|
|
|
|
req (jupyter-test-message
|
2020-09-12 13:26:12 -05:00
|
|
|
|
req "stream" (list :name "stdout" :text "foo"))))
|
2020-11-15 16:51:21 -06:00
|
|
|
|
(should-error (jupyter-kernel-info-request
|
|
|
|
|
:handlers '(not "foo"))))))
|
2018-11-08 21:54:20 -06:00
|
|
|
|
|
2019-05-07 00:41:44 -05:00
|
|
|
|
(ert-deftest jupyter-eval ()
|
|
|
|
|
:tags '(client)
|
|
|
|
|
(jupyter-test-with-python-client client
|
|
|
|
|
(let ((jupyter-current-client client))
|
|
|
|
|
(should (equal (jupyter-eval "1 + 1") "2")))))
|
|
|
|
|
|
2019-06-27 16:53:00 -05:00
|
|
|
|
(ert-deftest jupyter-line-count-greater-p ()
|
|
|
|
|
:tags '(client)
|
|
|
|
|
(should (jupyter-line-count-greater-p "\n\n" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "a\n\n" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "\na\n" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "a\na\n" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "\n\na" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "a\n\na" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "\na\na" 1))
|
|
|
|
|
(should (jupyter-line-count-greater-p "a\na\na" 1))
|
|
|
|
|
(should-not (jupyter-line-count-greater-p "\n\n" 2))
|
|
|
|
|
(should-not (jupyter-line-count-greater-p "\n\n" 3)))
|
|
|
|
|
|
2019-06-28 20:03:00 -05:00
|
|
|
|
(ert-deftest jupyter-available-local-ports ()
|
|
|
|
|
:tags '(client)
|
|
|
|
|
(let ((ports (jupyter-available-local-ports 5)))
|
|
|
|
|
(should (= (length ports) 5))
|
|
|
|
|
(dolist (p ports) (should (integerp p)))
|
|
|
|
|
(dolist (proc (process-list))
|
|
|
|
|
(should-not (string-match-p "jupyter-available-local-ports"
|
|
|
|
|
(process-name proc))))))
|
|
|
|
|
|
2019-01-15 16:08:58 -06:00
|
|
|
|
(defvar server-mode)
|
|
|
|
|
(defvar server-buffer)
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-server-mode-set-client ()
|
|
|
|
|
:tags '(client)
|
|
|
|
|
(let (server-buffer)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(setq server-buffer (buffer-name))
|
|
|
|
|
(let ((server-mode t)
|
|
|
|
|
(client (jupyter-kernel-client)))
|
|
|
|
|
(should-not jupyter-current-client)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(jupyter-server-mode-set-client client 0.01))
|
|
|
|
|
(should jupyter-current-client)
|
2019-07-25 10:23:23 -05:00
|
|
|
|
(sleep-for 0.1)
|
2019-01-15 16:08:58 -06:00
|
|
|
|
(should-not jupyter-current-client)))))
|
|
|
|
|
|
2020-04-10 16:44:46 -05:00
|
|
|
|
(defvar jupyter-test-idle-sync-hook nil)
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-idle-sync ()
|
|
|
|
|
:tags '(client hook)
|
|
|
|
|
(jupyter-test-with-python-client client
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-mlet* ((req (jupyter-execute-request :code "1 + 1")))
|
2020-04-14 23:18:46 -05:00
|
|
|
|
(should-not (jupyter-request-idle-p req))
|
2020-04-10 16:44:46 -05:00
|
|
|
|
(jupyter-idle-sync req)
|
2020-04-14 23:18:46 -05:00
|
|
|
|
(should (jupyter-request-idle-p req)))
|
2020-11-21 12:29:02 -06:00
|
|
|
|
(jupyter-mlet* ((req (jupyter-execute-request :code "1 + 1")))
|
2020-04-10 16:44:46 -05:00
|
|
|
|
(should (null jupyter-test-idle-sync-hook))
|
|
|
|
|
(jupyter-add-idle-sync-hook 'jupyter-test-idle-sync-hook req)
|
|
|
|
|
(should-not (null jupyter-test-idle-sync-hook))
|
2020-04-14 23:18:46 -05:00
|
|
|
|
(should-not (jupyter-request-idle-p req))
|
2020-04-10 16:44:46 -05:00
|
|
|
|
(run-hooks 'jupyter-test-idle-sync-hook)
|
2020-04-14 23:18:46 -05:00
|
|
|
|
(should (jupyter-request-idle-p req))
|
2020-04-10 16:44:46 -05:00
|
|
|
|
(should (null jupyter-test-idle-sync-hook)))))
|
|
|
|
|
|
2021-02-06 10:21:28 -06:00
|
|
|
|
;;; IOloop
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-ioloop-lifetime ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(let ((ioloop (jupyter-ioloop))
|
|
|
|
|
(jupyter-default-timeout 2))
|
|
|
|
|
(should-not (process-live-p (oref ioloop process)))
|
|
|
|
|
(jupyter-ioloop-start ioloop #'ignore)
|
|
|
|
|
(should (equal (jupyter-ioloop-last-event ioloop) '(start)))
|
|
|
|
|
(with-slots (process) ioloop
|
|
|
|
|
(should (process-live-p process))
|
|
|
|
|
(jupyter-ioloop-stop ioloop)
|
|
|
|
|
(should (equal (jupyter-ioloop-last-event ioloop) '(quit)))
|
|
|
|
|
(sleep-for 0.1)
|
|
|
|
|
(should-not (process-live-p process)))))
|
|
|
|
|
|
|
|
|
|
(defvar jupyter-ioloop-test-handler-called nil
|
|
|
|
|
"Flag variable used for testing the `juyter-ioloop'.")
|
|
|
|
|
|
|
|
|
|
(defun jupyter-test-ioloop-start (ioloop)
|
|
|
|
|
(jupyter-ioloop-start
|
|
|
|
|
ioloop (lambda (event)
|
|
|
|
|
(should (equal (cadr event) "message"))
|
|
|
|
|
(setq jupyter-ioloop-test-handler-called t))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-ioloop-wait-until ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(let ((ioloop (jupyter-ioloop)))
|
|
|
|
|
(should-not (jupyter-ioloop-last-event ioloop))
|
|
|
|
|
(jupyter-test-ioloop-start ioloop)
|
|
|
|
|
(should (equal (jupyter-ioloop-last-event ioloop) '(start)))
|
|
|
|
|
(jupyter-ioloop-stop ioloop)))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-ioloop-callbacks ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(ert-info ("Callback added before starting the ioloop")
|
|
|
|
|
(let ((ioloop (jupyter-ioloop)))
|
|
|
|
|
(setq jupyter-ioloop-test-handler-called nil)
|
|
|
|
|
(jupyter-ioloop-add-callback ioloop
|
|
|
|
|
`(lambda () (zmq-prin1 (list 'test "message"))))
|
|
|
|
|
(jupyter-test-ioloop-start ioloop)
|
|
|
|
|
(jupyter-ioloop-stop ioloop)
|
|
|
|
|
(should jupyter-ioloop-test-handler-called)))
|
|
|
|
|
(ert-info ("Callback added after starting the ioloop")
|
|
|
|
|
(let ((ioloop (jupyter-ioloop)))
|
|
|
|
|
(setq jupyter-ioloop-test-handler-called nil)
|
|
|
|
|
(jupyter-test-ioloop-start ioloop)
|
|
|
|
|
(should (process-live-p (oref ioloop process)))
|
|
|
|
|
(jupyter-ioloop-add-callback ioloop
|
|
|
|
|
`(lambda () (zmq-prin1 (list 'test "message"))))
|
|
|
|
|
(jupyter-ioloop-wait-until ioloop 'test #'identity)
|
|
|
|
|
(jupyter-ioloop-stop ioloop)
|
|
|
|
|
(should jupyter-ioloop-test-handler-called))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-ioloop-setup ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(let ((ioloop (jupyter-ioloop)))
|
|
|
|
|
(setq jupyter-ioloop-test-handler-called nil)
|
|
|
|
|
(jupyter-ioloop-add-setup ioloop
|
|
|
|
|
(zmq-prin1 (list 'test "message")))
|
|
|
|
|
(jupyter-test-ioloop-start ioloop)
|
|
|
|
|
(jupyter-ioloop-stop ioloop)
|
|
|
|
|
(should jupyter-ioloop-test-handler-called)))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-ioloop-teardown ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(let ((ioloop (jupyter-ioloop)))
|
|
|
|
|
(setq jupyter-ioloop-test-handler-called nil)
|
|
|
|
|
(jupyter-ioloop-add-teardown ioloop
|
|
|
|
|
(zmq-prin1 (list 'test "message")))
|
|
|
|
|
(jupyter-test-ioloop-start ioloop)
|
|
|
|
|
(jupyter-ioloop-stop ioloop)
|
|
|
|
|
(should jupyter-ioloop-test-handler-called)))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-ioloop-add-event ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(let ((ioloop (jupyter-ioloop)))
|
|
|
|
|
(setq jupyter-ioloop-test-handler-called nil)
|
|
|
|
|
(jupyter-ioloop-add-event ioloop test (data)
|
|
|
|
|
"Echo DATA back to the parent process."
|
|
|
|
|
(list 'test data))
|
|
|
|
|
(jupyter-test-ioloop-start ioloop)
|
|
|
|
|
(jupyter-send ioloop 'test "message")
|
|
|
|
|
(jupyter-ioloop-stop ioloop)
|
|
|
|
|
(should jupyter-ioloop-test-handler-called)))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-channel-ioloop-send-event ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(jupyter-test-channel-ioloop
|
|
|
|
|
(ioloop (jupyter-zmq-channel-ioloop))
|
|
|
|
|
(cl-letf (((symbol-function #'jupyter-send)
|
|
|
|
|
(lambda (_channel _msg-type _msg msg-id) msg-id)))
|
|
|
|
|
(setq jupyter-channel-ioloop-session (jupyter-session :key "foo"))
|
|
|
|
|
(push (jupyter-zmq-channel :type :shell) jupyter-channel-ioloop-channels)
|
|
|
|
|
(let* ((msg-id (jupyter-new-uuid))
|
|
|
|
|
(event `(list 'send :shell :execute-request '(msg) ,msg-id)))
|
|
|
|
|
(jupyter-test-ioloop-eval-event ioloop event)
|
|
|
|
|
(ert-info ("Return value to parent process")
|
|
|
|
|
(let ((result (read (buffer-string))))
|
|
|
|
|
(should (equal result `(sent :shell ,msg-id)))))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-channel-ioloop-start-channel-event ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(jupyter-test-channel-ioloop
|
|
|
|
|
(ioloop (jupyter-zmq-channel-ioloop))
|
|
|
|
|
(setq jupyter-channel-ioloop-session (jupyter-session :key "foo"))
|
|
|
|
|
(let ((channel-endpoint "tcp://127.0.0.1:5555"))
|
|
|
|
|
(ert-info ("start-channel event creates channel")
|
|
|
|
|
(should (null jupyter-channel-ioloop-channels))
|
|
|
|
|
(let ((event `(list 'start-channel :shell ,channel-endpoint)))
|
|
|
|
|
(jupyter-test-ioloop-eval-event ioloop event))
|
|
|
|
|
(should-not (null jupyter-channel-ioloop-channels))
|
|
|
|
|
(let ((channel (object-assoc :shell :type jupyter-channel-ioloop-channels)))
|
|
|
|
|
(should (jupyter-zmq-channel-p channel))))
|
|
|
|
|
(let ((channel (object-assoc :shell :type jupyter-channel-ioloop-channels)))
|
|
|
|
|
(with-slots (type socket endpoint) channel
|
|
|
|
|
(ert-info ("Verify the requested channel was started")
|
|
|
|
|
(should (eq type :shell))
|
|
|
|
|
(should (zmq-socket-p socket))
|
|
|
|
|
(should (equal endpoint channel-endpoint))
|
|
|
|
|
(should (equal (zmq-socket-get socket zmq-LAST-ENDPOINT) channel-endpoint))
|
|
|
|
|
(ert-info ("Identity of socket matches session")
|
|
|
|
|
(should (equal (zmq-socket-get socket zmq-IDENTITY)
|
|
|
|
|
(jupyter-session-id jupyter-channel-ioloop-session)))))
|
|
|
|
|
(ert-info ("Ensure the channel was added to the poller")
|
|
|
|
|
;; FIXME: Does it make sense to have this side effect as part of starting
|
|
|
|
|
;; a channel? It makes it so that we don't reference any `zmq' functions
|
|
|
|
|
;; in `jupyter-channel-ioloop'.
|
|
|
|
|
(should-error
|
|
|
|
|
(zmq-poller-add jupyter-ioloop-poller socket (list zmq-POLLIN))
|
|
|
|
|
:type 'zmq-EINVAL)))
|
|
|
|
|
(ert-info ("Return value to parent process")
|
|
|
|
|
(let ((result (read (buffer-string))))
|
|
|
|
|
(should (equal result `(start-channel :shell)))))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-channel-ioloop-stop-channel-event ()
|
|
|
|
|
:tags '(ioloop)
|
|
|
|
|
(jupyter-test-channel-ioloop
|
|
|
|
|
(ioloop (jupyter-zmq-channel-ioloop))
|
|
|
|
|
(setq jupyter-channel-ioloop-session (jupyter-session :key "foo"))
|
|
|
|
|
(let ((event `(list 'start-channel :shell "tcp://127.0.0.1:5556")))
|
|
|
|
|
(jupyter-test-ioloop-eval-event ioloop event)
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(let* ((channel (object-assoc :shell :type jupyter-channel-ioloop-channels))
|
|
|
|
|
(socket (oref channel socket)))
|
|
|
|
|
(ert-info ("Verify the requested channel stops")
|
|
|
|
|
(should (jupyter-alive-p channel))
|
|
|
|
|
(should (progn (zmq-poller-modify
|
|
|
|
|
jupyter-ioloop-poller
|
|
|
|
|
(oref channel socket) (list zmq-POLLIN zmq-POLLOUT))
|
|
|
|
|
t))
|
|
|
|
|
(jupyter-test-ioloop-eval-event ioloop `(list 'stop-channel :shell))
|
|
|
|
|
(should-not (jupyter-alive-p channel)))
|
|
|
|
|
(ert-info ("Ensure the channel was removed from the poller")
|
|
|
|
|
(should-error
|
|
|
|
|
(zmq-poller-modify jupyter-ioloop-poller socket (list zmq-POLLIN))
|
|
|
|
|
:type 'zmq-EINVAL))
|
|
|
|
|
(ert-info ("Return value to parent process")
|
|
|
|
|
(let ((result (read (buffer-string))))
|
|
|
|
|
(should (equal result `(stop-channel :shell))))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-zmq-channel-ioloop-send-fast ()
|
|
|
|
|
:tags '(ioloop queue)
|
|
|
|
|
(jupyter-test-with-python-client client
|
|
|
|
|
(let ((jupyter-current-client client))
|
|
|
|
|
(jupyter-send client :execute-request :code "1 + 1")
|
|
|
|
|
(jupyter-send client :execute-request :code "1 + 1")
|
|
|
|
|
(jupyter-send client :execute-request :code "1 + 1")
|
|
|
|
|
(let ((req (jupyter-send client :execute-request :code "1 + 1")))
|
|
|
|
|
(should
|
|
|
|
|
(equal
|
|
|
|
|
(jupyter-message-data
|
|
|
|
|
(jupyter-wait-until-received :execute-result req jupyter-long-timeout)
|
|
|
|
|
:text/plain)
|
|
|
|
|
"2"))))))
|
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;;; Completion
|
2018-08-27 20:37:27 -05:00
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-deftest jupyter-completion-number-p ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(completion)
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert "0.311")
|
|
|
|
|
(should (jupyter-completion-number-p))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "0311")
|
|
|
|
|
(should (jupyter-completion-number-p))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "0311.")
|
|
|
|
|
(should (jupyter-completion-number-p))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "100foo")
|
|
|
|
|
(should-not (jupyter-completion-number-p))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "foo100")
|
|
|
|
|
(should-not (jupyter-completion-number-p))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-completion-prefetch-p ()
|
|
|
|
|
:tags '(completion)
|
|
|
|
|
(let ((jupyter-completion-cache '("foo")))
|
|
|
|
|
(ert-info ("Prefetch when the cached prefix is more specialized")
|
|
|
|
|
(should (jupyter-completion-prefetch-p "f"))
|
|
|
|
|
(should (jupyter-completion-prefetch-p "")))
|
|
|
|
|
(ert-info ("Don't prefetch when the cached prefix is less specialized")
|
|
|
|
|
(should-not (jupyter-completion-prefetch-p "foo"))
|
|
|
|
|
(should-not (jupyter-completion-prefetch-p "foobar")))
|
|
|
|
|
(ert-info ("Prefetch when starting argument lists")
|
|
|
|
|
(should (jupyter-completion-prefetch-p "foobar("))))
|
|
|
|
|
(let ((jupyter-completion-cache '("")))
|
|
|
|
|
(ert-info ("Prefetch when given some context")
|
|
|
|
|
(should-not (jupyter-completion-prefetch-p ""))
|
|
|
|
|
(should (jupyter-completion-prefetch-p "a"))))
|
|
|
|
|
(let ((jupyter-completion-cache '(fetched "")))
|
|
|
|
|
(ert-info ("Prefetch when not processed")
|
|
|
|
|
(should (jupyter-completion-prefetch-p "a"))
|
|
|
|
|
;; But only if the fetched candidates do not match
|
|
|
|
|
;; the prefix
|
|
|
|
|
(should-not (jupyter-completion-prefetch-p ""))
|
|
|
|
|
(setq jupyter-completion-cache nil)
|
|
|
|
|
(should (jupyter-completion-prefetch-p ""))
|
|
|
|
|
(should (jupyter-completion-prefetch-p "a")))))
|
|
|
|
|
|
|
|
|
|
;;; REPL
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-repl-client-predicates ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(repl)
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(should-not (jupyter-repl-connected-p))
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(should (jupyter-repl-connected-p))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-repl-cell-predicates ()
|
|
|
|
|
:tags '(repl cell)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-cell-line-p'")
|
|
|
|
|
(should (jupyter-repl-cell-line-p))
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 1")
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(should (jupyter-repl-cell-line-p))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should (jupyter-repl-cell-line-p))
|
|
|
|
|
(forward-line -1)
|
|
|
|
|
(should-not (jupyter-repl-cell-line-p)))
|
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-cell-finalized-p'")
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 1")
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(should (jupyter-repl-cell-finalized-p)))
|
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-cell-beginning-p'")
|
|
|
|
|
(should-not (jupyter-repl-cell-beginning-p))
|
|
|
|
|
(goto-char (- (point) 2))
|
|
|
|
|
(should (jupyter-repl-cell-beginning-p))
|
|
|
|
|
(should (= (point) (jupyter-repl-cell-beginning-position))))
|
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-cell-end-p'")
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(should (jupyter-repl-cell-end-p))
|
|
|
|
|
(should (= (point) (jupyter-repl-cell-end-position)))
|
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 1")
|
|
|
|
|
(let ((end (point-max)))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(goto-char end)
|
|
|
|
|
(should (jupyter-repl-cell-end-p))
|
|
|
|
|
(should (= end (jupyter-repl-cell-end-position)))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-repl-cell-positions ()
|
|
|
|
|
:tags '(repl)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("Cell code position info")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 2")
|
|
|
|
|
(should (= (point) (point-max)))
|
|
|
|
|
(goto-char (1- (point)))
|
|
|
|
|
(should (= (char-after) ?2))
|
|
|
|
|
(should (= (jupyter-repl-cell-code-position) 5))
|
|
|
|
|
(goto-char (line-beginning-position))
|
|
|
|
|
(should (= (char-after) ?1))
|
|
|
|
|
(should (= (jupyter-repl-cell-code-position) 1)))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("Cell code beginning")
|
|
|
|
|
(should (= (point) (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should (= (point) (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(should (= (point) (jupyter-repl-cell-code-beginning-position))))
|
|
|
|
|
(jupyter-ert-info ("Cell code end")
|
|
|
|
|
(should (= (point-max) (jupyter-repl-cell-code-end-position)))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(should (= (1+ (line-end-position)) (jupyter-repl-cell-code-end-position))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-repl-ret ()
|
|
|
|
|
:tags '(repl)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("`point' before last cell in buffer")
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(let ((tick (buffer-modified-tick)))
|
|
|
|
|
(goto-char (point-min))
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(jupyter-test-repl-ret-sync)
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(should (= (point) (point-max)))
|
|
|
|
|
(should (equal tick (buffer-modified-tick)))))
|
|
|
|
|
(jupyter-ert-info ("No cells in buffer")
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(should-not (next-single-property-change (point-min) 'jupyter-cell))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should (next-single-property-change (point-min) 'jupyter-cell)))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-repl-cell-code-replacement ()
|
|
|
|
|
:tags '(repl)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("Replacing cell code")
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))
|
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 1")
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "1 + 1"))
|
|
|
|
|
(jupyter-repl-replace-cell-code "foo\n bar")
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo\n bar"))
|
|
|
|
|
(jupyter-repl-replace-cell-code ""))))
|
2018-08-27 20:37:27 -05:00
|
|
|
|
|
2018-06-14 20:57:02 -05:00
|
|
|
|
(defun jupyter-test-set-dummy-repl-history ()
|
|
|
|
|
"Reset `jupyter-repl-history' to a value used for testing.
|
|
|
|
|
The history contains the elements \"1\", \"2\", and \"3\", the
|
|
|
|
|
last element being the newest element added to the history."
|
|
|
|
|
(setq-local jupyter-repl-history (make-ring 5))
|
|
|
|
|
(ring-insert jupyter-repl-history 'jupyter-repl-history)
|
2019-02-14 16:15:00 -06:00
|
|
|
|
(jupyter-repl-history-add "1")
|
|
|
|
|
(jupyter-repl-history-add "2")
|
|
|
|
|
(jupyter-repl-history-add "3"))
|
2018-06-14 20:57:02 -05:00
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-repl-history ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(repl)
|
2019-05-18 20:03:14 -05:00
|
|
|
|
(ert-info ("Adding to REPL history")
|
|
|
|
|
(let ((jupyter-repl-history (make-ring 5)))
|
|
|
|
|
(ring-insert jupyter-repl-history 'jupyter-repl-history)
|
|
|
|
|
(jupyter-repl-history-add "1")
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("1" jupyter-repl-history)))
|
|
|
|
|
(ert-info ("Reset history before addition")
|
|
|
|
|
(ring-insert-at-beginning
|
|
|
|
|
jupyter-repl-history (ring-remove jupyter-repl-history 0))
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'(jupyter-repl-history "1")))
|
|
|
|
|
(jupyter-repl-history-add "2")
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("2" "1" jupyter-repl-history))))
|
|
|
|
|
(ert-info ("Drop oldest element when max reached")
|
|
|
|
|
(jupyter-repl-history-add "3")
|
|
|
|
|
(jupyter-repl-history-add "4")
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("4" "3" "2" "1" jupyter-repl-history)))
|
|
|
|
|
(jupyter-repl-history-add "5")
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("5" "4" "3" "2" jupyter-repl-history))))))
|
2019-05-08 10:08:34 -05:00
|
|
|
|
(let (jupyter-repl-history)
|
|
|
|
|
(ert-info ("Rotating REPL history ring")
|
2019-05-19 19:34:07 -05:00
|
|
|
|
(ert-info ("Rotating empty ring")
|
|
|
|
|
(setq jupyter-repl-history (make-ring 5))
|
|
|
|
|
(ring-insert jupyter-repl-history 'jupyter-repl-history)
|
|
|
|
|
(should (null (jupyter-repl-history--rotate -1)))
|
|
|
|
|
(should (null (jupyter-repl-history--rotate 0)))
|
|
|
|
|
(should (null (jupyter-repl-history--rotate 1))))
|
2018-06-14 20:57:02 -05:00
|
|
|
|
(jupyter-test-set-dummy-repl-history)
|
2019-05-08 10:08:34 -05:00
|
|
|
|
(should (null (jupyter-repl-history--rotate 1)))
|
|
|
|
|
(ert-info ("No rotation")
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("3" "2" "1" jupyter-repl-history)))
|
|
|
|
|
(should (equal (jupyter-repl-history--rotate 0) "3"))
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("3" "2" "1" jupyter-repl-history))))
|
|
|
|
|
(ert-info ("Rotate to older elements")
|
|
|
|
|
(should (equal (jupyter-repl-history--rotate -1) "2"))
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("2" "1" jupyter-repl-history "3"))))
|
|
|
|
|
(ert-info ("Rotate to newer elements")
|
|
|
|
|
(should (equal (jupyter-repl-history--rotate 1) "3"))
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("3" "2" "1" jupyter-repl-history))))
|
|
|
|
|
(ert-info ("Rotations stop at sentinel")
|
|
|
|
|
(should (null (jupyter-repl-history--rotate -4)))
|
|
|
|
|
(should (equal (ring-elements jupyter-repl-history)
|
|
|
|
|
'("1" jupyter-repl-history "3" "2"))))))
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("Replacing cell contents with history")
|
2018-06-14 20:57:02 -05:00
|
|
|
|
(jupyter-test-set-dummy-repl-history)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))
|
|
|
|
|
(jupyter-repl-history-previous)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "3"))
|
|
|
|
|
(jupyter-repl-history-previous)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "2"))
|
|
|
|
|
(jupyter-repl-history-previous)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "1"))
|
|
|
|
|
(should-error (jupyter-repl-history-previous))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "1"))
|
|
|
|
|
(jupyter-repl-history-next)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "2"))
|
|
|
|
|
(jupyter-repl-history-next)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "3"))
|
|
|
|
|
(jupyter-repl-history-next)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))
|
|
|
|
|
(should-error (jupyter-repl-history-next))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "")))))
|
|
|
|
|
|
2019-05-19 17:13:14 -07:00
|
|
|
|
(ert-deftest jupyter-repl-history-matching ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("REPL-input-history completion/matching")
|
|
|
|
|
(cl-macrolet ((Rx (l) `(should (equal (reverse (ring-elements
|
|
|
|
|
jupyter-repl-history))
|
|
|
|
|
,l))) ; oldest -> newest
|
|
|
|
|
(Hp (p n m) ; "helper" (pat, reps, member)
|
|
|
|
|
`(let ((i (jupyter-repl-history--match-input ,p ,n)))
|
|
|
|
|
(should (equal ,m (ring-ref jupyter-repl-history i)))
|
|
|
|
|
(should (eq (< i 0) (< ,n 0)))))) ; obvious?
|
|
|
|
|
(ert-info ("Create dummy history")
|
|
|
|
|
(jupyter-test-set-dummy-repl-history)
|
|
|
|
|
(ring-extend jupyter-repl-history 2)
|
|
|
|
|
(jupyter-repl-history-add "foo.a")
|
|
|
|
|
(jupyter-repl-history-add "1")
|
|
|
|
|
(jupyter-repl-history-add "foo.b")
|
|
|
|
|
(jupyter-repl-history-add "2")
|
|
|
|
|
(jupyter-repl-history-add "foo.c")
|
|
|
|
|
(jupyter-repl-history-add "3"))
|
|
|
|
|
(ert-info ("Baseline")
|
|
|
|
|
(Rx '(jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c" "3"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))
|
|
|
|
|
(insert "foo")
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo"))
|
|
|
|
|
(should (= (point) (point-max)))
|
|
|
|
|
;; Prev
|
|
|
|
|
(Hp "^foo" 1 "foo.c")
|
|
|
|
|
(should (integer-or-marker-p
|
|
|
|
|
(jupyter-repl-history-previous-matching 1)))
|
|
|
|
|
(Rx '("3" jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.c"))
|
|
|
|
|
(should (and (looking-back "foo" (point-at-bol))
|
|
|
|
|
(looking-at-p "\\.c")))
|
|
|
|
|
;; Next (ding)
|
|
|
|
|
(should-error (jupyter-repl-history-next-matching 1))
|
|
|
|
|
(Rx '("3" jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.c")))
|
|
|
|
|
(ert-info ("Initial input matches oldest item")
|
|
|
|
|
;; Coverage contrivance (but reachable in normal use)
|
|
|
|
|
(jupyter-repl-history-previous)
|
|
|
|
|
(Rx '("foo.c" "3" jupyter-repl-history "foo.a" "1" "foo.b" "2"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "2"))
|
|
|
|
|
(jupyter-repl-replace-cell-code "foo.c")
|
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
|
|
|
|
(should (and (search-forward "foo") (looking-at-p "\\.c")))
|
|
|
|
|
;; Next (ding)
|
|
|
|
|
(should-not (jupyter-repl-history--match-input "^foo" -2))
|
|
|
|
|
(should-error (jupyter-repl-history-next-matching 1))
|
|
|
|
|
(Rx '("foo.c" "3" jupyter-repl-history "foo.a" "1" "foo.b" "2"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.c")))
|
|
|
|
|
(ert-info ("Step once if point at bol")
|
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
|
|
|
|
(should (looking-at-p "foo\\.c"))
|
|
|
|
|
;; Next
|
|
|
|
|
(Hp "^" -2 "3")
|
|
|
|
|
(should (jupyter-repl-history-next-matching 1))
|
|
|
|
|
(Rx '(jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c" "3"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "3"))
|
|
|
|
|
;; Prev
|
|
|
|
|
(Hp "^" 2 "foo.c")
|
|
|
|
|
(should (jupyter-repl-history-previous-matching 1))
|
|
|
|
|
(Rx '("3" jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.c"))
|
|
|
|
|
(should (and (search-forward "foo") (looking-at-p "\\.c"))))
|
|
|
|
|
(ert-info ("Backward to oldest matching element")
|
|
|
|
|
;; Prev
|
|
|
|
|
(Hp "^foo" 2 "foo.b")
|
|
|
|
|
(should (jupyter-repl-history-previous-matching)) ; n = nil
|
|
|
|
|
(Rx '("2" "foo.c" "3" jupyter-repl-history "foo.a" "1" "foo.b"))
|
|
|
|
|
(should (looking-at-p "\\.b"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.b"))
|
|
|
|
|
;; Prev
|
|
|
|
|
(Hp "^foo" 2 "foo.a")
|
|
|
|
|
(should (jupyter-repl-history-previous-matching 1))
|
|
|
|
|
(Rx '("1" "foo.b" "2" "foo.c" "3" jupyter-repl-history "foo.a"))
|
|
|
|
|
(should (looking-at-p "\\.a"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.a"))
|
|
|
|
|
;; Prev (ding) and repeat
|
|
|
|
|
(should-not (jupyter-repl-history--match-input "^foo" 2))
|
|
|
|
|
(should-error (jupyter-repl-history-previous-matching 1))
|
|
|
|
|
(Rx '("1" "foo.b" "2" "foo.c" "3" jupyter-repl-history "foo.a"))
|
|
|
|
|
(should-error (jupyter-repl-history-previous-matching 1))
|
|
|
|
|
(Rx '("1" "foo.b" "2" "foo.c" "3" jupyter-repl-history "foo.a"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.a")))
|
|
|
|
|
(ert-info ("Forward to most recent matching element")
|
|
|
|
|
;; Next
|
|
|
|
|
(Hp "^foo" -1 "foo.b")
|
|
|
|
|
(should (jupyter-repl-history-next-matching)) ; n = nil
|
|
|
|
|
(Rx '("2" "foo.c" "3" jupyter-repl-history "foo.a" "1" "foo.b"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.b"))
|
|
|
|
|
;; Next
|
|
|
|
|
(Hp "^foo" -1 "foo.c")
|
|
|
|
|
(should (jupyter-repl-history-next-matching 1))
|
|
|
|
|
(Rx '("3" jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.c"))
|
|
|
|
|
;; Next (ding)
|
|
|
|
|
(should-error (jupyter-repl-history-next-matching 1))
|
|
|
|
|
(Rx '("3" jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c"))
|
|
|
|
|
(should (looking-at-p "\\.c")))
|
|
|
|
|
(ert-info ("Scaled")
|
|
|
|
|
;; Prev 2x
|
|
|
|
|
(Hp "^foo" 3 "foo.a")
|
|
|
|
|
(should (jupyter-repl-history-previous-matching 2))
|
|
|
|
|
(Rx '("1" "foo.b" "2" "foo.c" "3" jupyter-repl-history "foo.a"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.a"))
|
|
|
|
|
;; Next 2x
|
|
|
|
|
(Hp "^foo" -2 "foo.c")
|
|
|
|
|
(should (jupyter-repl-history-next-matching 2))
|
|
|
|
|
(Rx '("3" jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "foo.c")))
|
|
|
|
|
(ert-info ("Basic history commands still work")
|
|
|
|
|
(jupyter-repl-history-next)
|
|
|
|
|
(Rx '(jupyter-repl-history "foo.a" "1" "foo.b" "2" "foo.c" "3"))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "3"))
|
|
|
|
|
(jupyter-repl-history-next)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "")))))))
|
|
|
|
|
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(ert-deftest jupyter-repl-cell-motions ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(repl motion)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-10-31 18:51:40 -05:00
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-goto-cell'")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(let (cell-pos req)
|
|
|
|
|
(setq cell-pos (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char cell-pos)
|
|
|
|
|
(setq req (jupyter-repl-cell-request)))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should (/= (point) cell-pos))
|
|
|
|
|
(jupyter-repl-goto-cell req)
|
|
|
|
|
(should (= (point) cell-pos))))
|
2018-10-31 18:51:40 -05:00
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-previous-cell'")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(let (cell-pos1)
|
|
|
|
|
(setq cell-pos1 (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(goto-char cell-pos1)
|
|
|
|
|
(ert-info ("First motion to beginning of current cell")
|
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 1")
|
|
|
|
|
(should (/= (point) cell-pos1))
|
|
|
|
|
(should (= (jupyter-repl-previous-cell) 0))
|
|
|
|
|
(should (= (point) cell-pos1))
|
|
|
|
|
(jupyter-repl-replace-cell-code ""))
|
|
|
|
|
(ert-info ("Motion with count")
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(goto-char (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(should (= (jupyter-repl-previous-cell 2) 0))
|
|
|
|
|
(should (= (point) cell-pos1)))
|
|
|
|
|
(ert-info ("First cell of buffer")
|
|
|
|
|
(goto-char cell-pos1)
|
|
|
|
|
(should (= (jupyter-repl-previous-cell) 1))
|
|
|
|
|
(should (= (point) (point-min))))))
|
2018-10-31 18:51:40 -05:00
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-backward-cell'")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(let (cell-pos1)
|
|
|
|
|
(setq cell-pos1 (jupyter-repl-cell-code-beginning-position))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should-not (= (point) cell-pos1))
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(should (= (point) cell-pos1))))
|
2018-10-31 18:51:40 -05:00
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-next-cell'")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(let (cell-pos1 cell-pos2)
|
|
|
|
|
(setq cell-pos1 (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(ert-info ("Motion with count")
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(setq cell-pos2 (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(goto-char cell-pos1)
|
|
|
|
|
(should (= (jupyter-repl-next-cell 2) 0))
|
|
|
|
|
(should (= (point) cell-pos2)))
|
|
|
|
|
(ert-info ("Last cell of buffer")
|
|
|
|
|
(goto-char cell-pos2)
|
|
|
|
|
(should (= (jupyter-repl-next-cell) 1))
|
|
|
|
|
(should (= (point) (point-max))))))
|
2018-10-31 18:51:40 -05:00
|
|
|
|
(jupyter-ert-info ("`jupyter-repl-forward-cell'")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(let (cell-pos1 cell-pos2)
|
|
|
|
|
(setq cell-pos1 (jupyter-repl-cell-code-beginning-position))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(setq cell-pos2 (jupyter-repl-cell-code-beginning-position))
|
|
|
|
|
(goto-char cell-pos1)
|
|
|
|
|
(jupyter-repl-forward-cell)
|
|
|
|
|
(should (= (point) cell-pos2))))))
|
|
|
|
|
|
2019-02-14 16:13:00 -06:00
|
|
|
|
(ert-deftest jupyter-repl-finalize-cell ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("Finalize the last cell only")
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(should (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(should-not (= (point) (point-max)))
|
|
|
|
|
(jupyter-repl-finalize-cell nil)
|
|
|
|
|
(should (= (point) (point-max)))
|
|
|
|
|
(should (jupyter-repl-cell-finalized-p)))
|
|
|
|
|
(jupyter-ert-info
|
|
|
|
|
("Don't modify the jupyter-request property of a finalized cell")
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(jupyter-repl-backward-cell)
|
|
|
|
|
(let* ((finalized-cell-beg (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(finalized-cell-req
|
|
|
|
|
(get-text-property finalized-cell-beg 'jupyter-request)))
|
|
|
|
|
(should finalized-cell-req)
|
|
|
|
|
(jupyter-repl-finalize-cell nil)
|
|
|
|
|
(should (eq (get-text-property finalized-cell-beg 'jupyter-request)
|
|
|
|
|
finalized-cell-req))))))
|
|
|
|
|
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(ert-deftest jupyter-repl-cell-positions ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(repl motion)
|
2019-01-23 12:57:11 -06:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("Beginning of a cell")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(should (= (point) (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
|
(should (get-text-property (- (point) 2) 'jupyter-cell))
|
|
|
|
|
(should (jupyter-repl-cell-beginning-p (- (point) 2)))
|
|
|
|
|
(should (= (jupyter-repl-cell-beginning-position) (- (point) 2))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("End of unfinalized cell")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(should-not (get-text-property (point-max) 'jupyter-cell))
|
|
|
|
|
(should (= (jupyter-repl-cell-end-p (point-max))))
|
|
|
|
|
(should (= (jupyter-repl-cell-end-position) (point-max)))
|
|
|
|
|
(should (= (jupyter-repl-cell-code-end-position) (point-max))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("End of finalized cell")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(should (= (point) (jupyter-repl-cell-code-beginning-position)))
|
|
|
|
|
(goto-char (1- (jupyter-repl-cell-beginning-position)))
|
|
|
|
|
(should (jupyter-repl-cell-end-p))
|
|
|
|
|
(should (= (jupyter-repl-cell-end-position) (point)))
|
2018-12-16 18:48:41 -06:00
|
|
|
|
(should (= (jupyter-repl-cell-code-end-position) (point)))
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(should (jupyter-repl-cell-finalized-p)))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(jupyter-ert-info ("Cell boundary errors")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(jupyter-repl-replace-cell-code "1 + 1")
|
2020-04-29 22:20:28 -05:00
|
|
|
|
(jupyter-wait-until-idle (jupyter-repl-execute-cell client))
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(forward-line -2)
|
|
|
|
|
(should (eq (car (get-text-property (1- (point)) 'jupyter-cell))
|
|
|
|
|
'out))
|
|
|
|
|
(should-error (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(should-error (jupyter-repl-cell-end-position)))))
|
|
|
|
|
|
2020-04-08 03:19:07 -05:00
|
|
|
|
(ert-deftest jupyter-repl-map-cells ()
|
2019-03-02 14:28:37 -06:00
|
|
|
|
:tags '(repl)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert "foo")
|
|
|
|
|
(insert (propertize "bar" 'field 'cell-code))
|
|
|
|
|
(insert "baz")
|
2020-04-08 03:19:07 -05:00
|
|
|
|
(let (input output)
|
|
|
|
|
(jupyter-repl-map-cells (point-min) (point-max)
|
|
|
|
|
(lambda () (push (buffer-string) input))
|
|
|
|
|
(lambda () (push (buffer-string) output)))
|
|
|
|
|
(should (equal input '("bar")))
|
|
|
|
|
(should (equal output '("baz" "foo"))))))
|
2019-03-02 14:28:37 -06:00
|
|
|
|
|
2019-03-02 00:30:09 -06:00
|
|
|
|
(ert-deftest jupyter-repl-restart-kernel ()
|
|
|
|
|
:tags '(repl restart)
|
2020-04-25 13:18:12 -05:00
|
|
|
|
(skip-unless nil)
|
2019-03-02 00:30:09 -06:00
|
|
|
|
(let ((jupyter-test-with-new-client t))
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("Restart without errors")
|
|
|
|
|
(should (equal (oref client execution-state) "idle"))
|
|
|
|
|
;; Increment the cell count just to make sure it gets reset to 1 after
|
|
|
|
|
;; a restart
|
|
|
|
|
(jupyter-repl-update-cell-count 2)
|
|
|
|
|
(let* ((pos (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(restart-p nil)
|
|
|
|
|
(jupyter-include-other-output t)
|
|
|
|
|
(jupyter-iopub-message-hook
|
|
|
|
|
(lambda (_ msg)
|
|
|
|
|
(when (jupyter-message-status-starting-p msg)
|
|
|
|
|
(setq restart-p t)))))
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(jupyter-repl-restart-kernel)
|
2019-03-02 19:26:32 -06:00
|
|
|
|
;; Attempt to catch the status: starting message
|
|
|
|
|
(jupyter-with-timeout (nil jupyter-long-timeout)
|
2019-03-02 00:30:09 -06:00
|
|
|
|
restart-p)
|
2019-03-02 19:26:32 -06:00
|
|
|
|
(should (jupyter-kernel-info client))
|
2019-03-02 00:30:09 -06:00
|
|
|
|
(should (equal (jupyter-repl-cell-code-beginning-position) (point)))
|
|
|
|
|
(should-not (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(goto-char pos)
|
|
|
|
|
(should (jupyter-repl-cell-finalized-p))
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(should (= (jupyter-repl-cell-count) 1))
|
|
|
|
|
(jupyter-repl-sync-execution-state)
|
|
|
|
|
(should (equal (jupyter-execution-state client) "idle")))))))
|
|
|
|
|
|
2018-08-27 20:37:27 -05:00
|
|
|
|
(ert-deftest jupyter-repl-prompts ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(repl prompt)
|
2019-05-10 17:37:15 -05:00
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(let (cell-prop)
|
|
|
|
|
(jupyter-ert-info ("Prompt properties")
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(let (prompt-overlay)
|
|
|
|
|
(goto-char (jupyter-repl-cell-beginning-position))
|
|
|
|
|
(setq prompt-overlay (car (overlays-at (point))))
|
|
|
|
|
(should-not (null prompt-overlay))
|
2019-05-10 17:37:15 -05:00
|
|
|
|
(setq cell-prop (get-text-property (point) 'jupyter-cell))
|
|
|
|
|
(should (eq (car cell-prop) 'beginning))
|
|
|
|
|
(should (and (numberp (cadr cell-prop))
|
|
|
|
|
(>= (cadr cell-prop) 1)))
|
|
|
|
|
(should (= (jupyter-repl-cell-count) (cadr cell-prop)))))
|
|
|
|
|
(jupyter-ert-info ("Input prompts")
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(goto-char (jupyter-repl-cell-code-beginning-position))
|
|
|
|
|
;; To prevent prompts from inheriting text properties of cell code there is
|
|
|
|
|
;; an invisible character at the end of every prompt. This is because
|
|
|
|
|
;; prompts are implemented as overlays and therefore will inherit the text
|
|
|
|
|
;; properties of adjacent text, we want to prevent that.
|
|
|
|
|
(should (invisible-p (1- (point))))
|
|
|
|
|
(should (jupyter-repl-cell-beginning-p (- (point) 2)))
|
|
|
|
|
(should (eq (char-after (- (point) 2)) ?\n))
|
|
|
|
|
(let* ((props (text-properties-at (- (point) 2)))
|
|
|
|
|
(cell-property (memq 'jupyter-cell props)))
|
|
|
|
|
(should (not (null cell-property)))
|
|
|
|
|
(should (listp (cdr cell-property)))
|
2019-05-10 17:37:15 -05:00
|
|
|
|
(should (equal (cadr cell-property) cell-prop)))))
|
|
|
|
|
(jupyter-ert-info ("Continuation prompts")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
|
2019-05-10 17:37:15 -05:00
|
|
|
|
)
|
|
|
|
|
(jupyter-ert-info ("Output prompts")
|
2018-08-27 20:37:27 -05:00
|
|
|
|
|
2019-05-10 17:37:15 -05:00
|
|
|
|
)))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
2019-02-22 20:51:00 -06:00
|
|
|
|
(ert-deftest jupyter-repl-prompt-margin ()
|
|
|
|
|
:tags '(repl prompt)
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
(erase-buffer))
|
|
|
|
|
(let ((jupyter-repl-prompt-margin-width 2))
|
|
|
|
|
(jupyter-repl--reset-prompts)
|
|
|
|
|
(should (= jupyter-repl-prompt-margin-width 2))
|
|
|
|
|
(should (= left-margin-width 2))
|
|
|
|
|
(jupyter-repl-insert-prompt)
|
|
|
|
|
(should (> left-margin-width 2))
|
|
|
|
|
(should (= left-margin-width jupyter-repl-prompt-margin-width))
|
|
|
|
|
(should (= jupyter-repl-prompt-margin-width
|
|
|
|
|
(length (jupyter-repl-prompt-string)))))))
|
|
|
|
|
|
2019-01-23 12:30:17 -06:00
|
|
|
|
(ert-deftest jupyter-repl-yank ()
|
|
|
|
|
:tags '(repl yank)
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("Ensure field property exists after yanking")
|
|
|
|
|
(kill-new "import foo")
|
|
|
|
|
(yank)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "import foo"))
|
|
|
|
|
(should-not (text-property-not-all
|
|
|
|
|
(jupyter-repl-cell-code-beginning-position)
|
|
|
|
|
(jupyter-repl-cell-code-end-position)
|
2019-01-24 12:30:35 -06:00
|
|
|
|
'field 'cell-code)))
|
|
|
|
|
(jupyter-ert-info ("Undo rear-nonsticky property inserted by `insert-for-yank'")
|
|
|
|
|
(kill-new "import foo")
|
|
|
|
|
(yank)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "import foo"))
|
|
|
|
|
(should-not (get-text-property (1- (jupyter-repl-cell-code-end-position))
|
|
|
|
|
'rear-nonsticky)))))
|
2019-01-23 12:30:17 -06:00
|
|
|
|
|
2019-02-14 23:06:00 -06:00
|
|
|
|
(ert-deftest jupyter-repl-syntax-propertize-function ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
;; TODO: Test field = `cell-code` path
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(let ((jupyter-current-client client))
|
|
|
|
|
(insert "(foo) bar")
|
|
|
|
|
(jupyter-repl-syntax-propertize-function #'ignore (point-min) (point-max))
|
2019-02-17 23:41:01 -06:00
|
|
|
|
(jupyter-test-text-has-property 'syntax-table '(1 . ?.) '(1 5))
|
2019-02-14 23:06:00 -06:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "(foo)")
|
|
|
|
|
(jupyter-repl-syntax-propertize-function #'ignore (point-min) (point-max))
|
2019-02-17 23:41:01 -06:00
|
|
|
|
(jupyter-test-text-has-property 'syntax-table '(1 . ?.) '(1 5))
|
2019-02-14 23:06:00 -06:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "foo (bar)")
|
|
|
|
|
(jupyter-repl-syntax-propertize-function #'ignore (point-min) (point-max))
|
2019-02-17 23:41:01 -06:00
|
|
|
|
(jupyter-test-text-has-property 'syntax-table '(1 . ?.) '(5 9))))))
|
2019-02-14 23:06:00 -06:00
|
|
|
|
|
2019-02-21 22:54:30 -06:00
|
|
|
|
(ert-deftest jupyter-repl-undo ()
|
|
|
|
|
:tags '(repl yank undo)
|
2019-06-27 16:54:00 -05:00
|
|
|
|
(let ((ensure-field-property
|
|
|
|
|
(lambda ()
|
|
|
|
|
(should-not
|
|
|
|
|
(text-property-not-all
|
|
|
|
|
(jupyter-repl-cell-code-beginning-position)
|
|
|
|
|
(jupyter-repl-cell-code-end-position)
|
|
|
|
|
'field 'cell-code)))))
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("Undo after yank undoes all the yanked text")
|
|
|
|
|
(kill-new "import IPython\ndef foo(x)\n\treturn x")
|
|
|
|
|
(undo-boundary)
|
|
|
|
|
(yank)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "import IPython\ndef foo(x)\n\treturn x"))
|
|
|
|
|
(funcall ensure-field-property)
|
|
|
|
|
(let ((beg (jupyter-repl-cell-beginning-position)))
|
|
|
|
|
(undo)
|
|
|
|
|
(should (get-text-property beg 'jupyter-cell))
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))))
|
|
|
|
|
(jupyter-ert-info ("Correct undo after inserting continuation prompt")
|
|
|
|
|
;; See #139
|
|
|
|
|
(insert "\
|
|
|
|
|
for item in range(10):
|
|
|
|
|
print(item)")
|
|
|
|
|
(backward-char)
|
|
|
|
|
(undo-boundary)
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(undo-boundary)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "\
|
|
|
|
|
for item in range(10):
|
|
|
|
|
print(item
|
|
|
|
|
)"))
|
|
|
|
|
(funcall ensure-field-property)
|
|
|
|
|
(undo)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "\
|
|
|
|
|
for item in range(10):
|
|
|
|
|
print(item)")))
|
|
|
|
|
(jupyter-ert-info ("Passing through `jupyter-repl-indent-line'")
|
|
|
|
|
(insert "\
|
|
|
|
|
next(x")
|
|
|
|
|
(undo-boundary)
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(undo-boundary)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code)
|
|
|
|
|
"\
|
|
|
|
|
next(x
|
|
|
|
|
"))
|
|
|
|
|
(funcall ensure-field-property)
|
2019-02-21 22:54:30 -06:00
|
|
|
|
(undo)
|
2019-06-27 16:54:00 -05:00
|
|
|
|
(should (equal (jupyter-repl-cell-code)
|
|
|
|
|
"\
|
|
|
|
|
next(x"))))))
|
2019-02-21 22:54:30 -06:00
|
|
|
|
|
2019-02-27 14:43:50 -06:00
|
|
|
|
(ert-deftest jupyter-repl-after-change ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
;; See #38
|
|
|
|
|
(jupyter-ert-info ("Maintain field membership after deleting text at beginning of cell")
|
|
|
|
|
(insert "foo(")
|
|
|
|
|
(should (eql (field-at-pos (jupyter-repl-cell-code-beginning-position)) 'cell-code))
|
|
|
|
|
(backward-char)
|
|
|
|
|
(backward-kill-word 1)
|
|
|
|
|
(should (eql (field-at-pos (jupyter-repl-cell-code-beginning-position)) 'cell-code)))))
|
|
|
|
|
|
2019-04-01 21:45:00 -05:00
|
|
|
|
(ert-deftest jupyter-repl-propagate-client ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(setq jupyter-current-client (jupyter-repl-client))
|
|
|
|
|
(oset jupyter-current-client kernel-info
|
|
|
|
|
(list :language_info
|
|
|
|
|
;; :name is a symbol, see `jupyter-kernel-info'
|
|
|
|
|
(list :name 'python :file_extension "py")))
|
|
|
|
|
(let ((buffer (generate-new-buffer " *temp*")))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
(ert-info ("Verify that `jupyter-current-client' is actually set")
|
|
|
|
|
(should-not (buffer-local-value 'jupyter-current-client buffer))
|
|
|
|
|
(with-current-buffer buffer
|
|
|
|
|
(python-mode))
|
|
|
|
|
(jupyter-repl-propagate-client buffer)
|
|
|
|
|
(should (eq (buffer-local-value 'jupyter-current-client buffer)
|
|
|
|
|
jupyter-current-client)))
|
|
|
|
|
(ert-info ("Robust to bad arguments")
|
|
|
|
|
(jupyter-repl-propagate-client 1)
|
|
|
|
|
(jupyter-repl-propagate-client
|
|
|
|
|
(generate-new-buffer-name "foo"))))
|
|
|
|
|
(when (buffer-live-p buffer)
|
|
|
|
|
(kill-buffer buffer))))))
|
|
|
|
|
|
2019-04-15 20:47:20 -05:00
|
|
|
|
(ert-deftest jupyter-connect-repl ()
|
|
|
|
|
:tags '(repl)
|
2020-09-11 16:09:09 -05:00
|
|
|
|
(skip-unless nil)
|
2019-04-15 20:47:20 -05:00
|
|
|
|
(jupyter-test-with-python-repl client
|
2019-05-09 21:16:10 -05:00
|
|
|
|
(let ((cclient (jupyter-connect-repl
|
|
|
|
|
(jupyter-session-conn-info
|
|
|
|
|
(oref client session)))))
|
2019-04-15 20:47:20 -05:00
|
|
|
|
(unwind-protect
|
2020-09-12 13:26:12 -05:00
|
|
|
|
(let ((msg (jupyter-wait-until-received "execute_result"
|
2020-11-15 16:51:21 -06:00
|
|
|
|
(jupyter-with-client cclient
|
|
|
|
|
(jupyter-execute-request
|
|
|
|
|
:handlers nil
|
|
|
|
|
:code "1 + 1")))))
|
2019-04-15 20:47:20 -05:00
|
|
|
|
(should msg)
|
|
|
|
|
(should (equal (jupyter-message-data msg :text/plain) "2")))
|
2019-05-09 21:16:10 -05:00
|
|
|
|
(with-current-buffer (oref cclient buffer)
|
|
|
|
|
(jupyter-stop-channels cclient)
|
|
|
|
|
(let ((kill-buffer-query-functions nil))
|
|
|
|
|
(kill-buffer)))))))
|
2019-04-15 20:47:20 -05:00
|
|
|
|
|
2019-05-21 16:45:44 -05:00
|
|
|
|
(ert-deftest jupyter-repl-echo-eval-p ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-ert-info ("Copying input")
|
|
|
|
|
(let ((jupyter-repl-echo-eval-p t))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))
|
|
|
|
|
(let ((req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should-not (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)
|
|
|
|
|
(jupyter-repl-goto-cell req)
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "1 + 1")))))
|
2019-10-17 11:37:14 -04:00
|
|
|
|
(jupyter-ert-info ("Preserving code")
|
|
|
|
|
(let ((jupyter-repl-echo-eval-p t))
|
|
|
|
|
(jupyter-repl-replace-cell-code "2 + 2")
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "2 + 2"))
|
|
|
|
|
(let ((req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should-not (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) "2 + 2")))))
|
2019-05-21 16:45:44 -05:00
|
|
|
|
(jupyter-ert-info ("Not copying input")
|
|
|
|
|
(let ((jupyter-repl-echo-eval-p nil))
|
|
|
|
|
(should (equal (jupyter-repl-cell-code) ""))
|
|
|
|
|
(let ((req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)
|
|
|
|
|
(should-error (jupyter-repl-goto-cell req)))))
|
|
|
|
|
(ert-info ("Add callbacks when REPL buffer is invisible")
|
|
|
|
|
(cl-letf (((symbol-function #'get-buffer-window)
|
|
|
|
|
(lambda (&rest _) nil)))
|
|
|
|
|
(ert-info ("`jupyter-repl-echo-eval-p' = t")
|
|
|
|
|
(let* ((jupyter-repl-echo-eval-p t)
|
|
|
|
|
(req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should-not (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)))
|
|
|
|
|
(ert-info ("`jupyter-repl-echo-eval-p' = nil")
|
|
|
|
|
(let* ((jupyter-repl-echo-eval-p nil)
|
|
|
|
|
(req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)))))
|
|
|
|
|
(ert-info ("No callbacks when REPL buffer visible")
|
|
|
|
|
(cl-letf (((symbol-function #'get-buffer-window)
|
|
|
|
|
(lambda (&rest _) (selected-window))))
|
|
|
|
|
(ert-info ("`jupyter-repl-echo-eval-p' = t")
|
|
|
|
|
(let* ((jupyter-repl-echo-eval-p t)
|
|
|
|
|
(req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should-not (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)))
|
|
|
|
|
(ert-info ("`jupyter-repl-echo-eval-p' = nil")
|
|
|
|
|
(let* ((jupyter-repl-echo-eval-p nil)
|
|
|
|
|
(req (jupyter-eval-string "1 + 1")))
|
|
|
|
|
(should (jupyter-request-inhibited-handlers req))
|
|
|
|
|
(jupyter-wait-until-idle req)))))))
|
|
|
|
|
|
2020-04-03 23:01:39 -05:00
|
|
|
|
(ert-deftest jupyter-repl-issue-219 ()
|
|
|
|
|
:tags '(repl)
|
2020-04-04 12:27:44 -05:00
|
|
|
|
(skip-unless (version<= "27" emacs-version))
|
2020-04-03 23:01:39 -05:00
|
|
|
|
;; A symptom of the REPL not setting the right text properties is an
|
|
|
|
|
;; un-terminating loop in `font-lock-default-fontify-region' this test
|
|
|
|
|
;; catches that.
|
|
|
|
|
(jupyter-test-with-python-repl client
|
|
|
|
|
(jupyter-with-repl-buffer client
|
|
|
|
|
(jupyter-repl-replace-cell-code "l = [1,2,3]")
|
|
|
|
|
(jupyter-test-repl-ret-sync)
|
|
|
|
|
(let* ((count 0)
|
|
|
|
|
(font-lock-extend-region-functions
|
|
|
|
|
(cons (lambda ()
|
|
|
|
|
(when (> (setq count (1+ count)) 100)
|
|
|
|
|
(error "Un-terminating `font-lock-extend-region-functions' handling."))
|
|
|
|
|
nil)
|
|
|
|
|
font-lock-extend-region-functions)))
|
|
|
|
|
(font-lock-ensure)))))
|
|
|
|
|
|
2022-02-27 13:48:11 +01:00
|
|
|
|
(ert-deftest jupyter-available-kernelspecs-sorting ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(jupyter-test-with-some-kernelspecs '("foo_qux" "qux" "bar_qux")
|
|
|
|
|
(let ((result (mapcar #'car (jupyter-available-kernelspecs t))))
|
|
|
|
|
(should (equal result (sort (copy-sequence result) #'string<))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-run-repl-issue-371 ()
|
|
|
|
|
:tags '(repl)
|
|
|
|
|
(jupyter-test-with-some-kernelspecs '("foo_qux" "qux" "bar_qux")
|
|
|
|
|
(let ((client))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
(setq client (jupyter-run-repl "qux"))
|
|
|
|
|
(should (equal (plist-get (jupyter-session-conn-info (oref client session))
|
|
|
|
|
:kernel_name)
|
|
|
|
|
"qux")))
|
|
|
|
|
(jupyter-test-kill-buffer (oref client buffer))))))
|
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;;; `org-mode'
|
2017-12-13 11:27:13 -06:00
|
|
|
|
|
2018-05-16 20:45:26 -05:00
|
|
|
|
(defvar org-babel-jupyter-resource-directory nil)
|
|
|
|
|
|
2020-04-04 08:35:23 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-parse-session ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(require 'ob-jupyter)
|
|
|
|
|
(ert-info ("Sessions with a kernel connection file")
|
|
|
|
|
(let ((session (org-babel-jupyter-parse-session "/foo/bar.json")))
|
|
|
|
|
(should (org-babel-jupyter-remote-session-p session))
|
|
|
|
|
(should (org-babel-jupyter-remote-session-connect-repl-p session))
|
|
|
|
|
(should (equal (org-babel-jupyter-session-name session) "/foo/bar.json")))
|
|
|
|
|
(let ((session (org-babel-jupyter-parse-session "/ssh:ec2:foo/bar.json")))
|
|
|
|
|
(should (org-babel-jupyter-remote-session-p session))
|
|
|
|
|
(should (org-babel-jupyter-remote-session-connect-repl-p session))
|
|
|
|
|
(should (equal (org-babel-jupyter-session-name session) "/ssh:ec2:foo/bar.json"))))
|
2021-04-03 13:40:11 -05:00
|
|
|
|
(ert-info ("Remote sessions")
|
2020-04-04 08:35:23 -05:00
|
|
|
|
(let ((session (org-babel-jupyter-parse-session "/ssh:ec2:foo/bar")))
|
|
|
|
|
(should (org-babel-jupyter-remote-session-p session))
|
|
|
|
|
(should-not (org-babel-jupyter-remote-session-connect-repl-p session))
|
|
|
|
|
(should (equal (org-babel-jupyter-session-name session) "/ssh:ec2:foo/bar"))))
|
|
|
|
|
(let ((session (org-babel-jupyter-parse-session "foo/bar")))
|
|
|
|
|
(should (org-babel-jupyter-session-p session))
|
|
|
|
|
(should (equal (org-babel-jupyter-session-name session) "foo/bar"))))
|
|
|
|
|
|
2020-04-14 10:58:48 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-initiate-session-by-key ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(let ((session (make-temp-name "jupyter")))
|
|
|
|
|
(save-excursion
|
|
|
|
|
(insert
|
|
|
|
|
(format "#+begin_src jupyter-python :session %s\n1+1\n#+end_src\n"
|
|
|
|
|
session)))
|
|
|
|
|
(let* ((params (nth 2 (org-babel-get-src-block-info)))
|
|
|
|
|
(key (org-babel-jupyter-session-key params)))
|
|
|
|
|
(should-not (gethash key org-babel-jupyter-session-clients))
|
|
|
|
|
(let ((buffer (org-babel-jupyter-initiate-session-by-key
|
|
|
|
|
session params))
|
|
|
|
|
(client (gethash key org-babel-jupyter-session-clients)))
|
|
|
|
|
(should (bufferp buffer))
|
|
|
|
|
(should client)
|
|
|
|
|
(should (object-of-class-p client 'jupyter-repl-client))
|
|
|
|
|
(should (eq buffer (oref client buffer)))
|
|
|
|
|
(jupyter-test-kill-buffer buffer)
|
|
|
|
|
(should-not (gethash key org-babel-jupyter-session-clients)))))))
|
|
|
|
|
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(ert-deftest ob-jupyter-no-results ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test-src-block "1 + 1;" ""))
|
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-deftest ob-jupyter-scalar-results ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(org)
|
2019-03-03 16:53:39 -06:00
|
|
|
|
(jupyter-org-test-src-block "1 + 1" ": 2\n")
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(ert-info ("Tables")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"[[1, 2, 3], [4, 5, 6]]"
|
|
|
|
|
"\
|
|
|
|
|
| 1 | 2 | 3 |
|
2019-03-03 16:53:39 -06:00
|
|
|
|
| 4 | 5 | 6 |
|
|
|
|
|
")))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
|
|
|
|
(ert-deftest ob-jupyter-html-results ()
|
|
|
|
|
:tags '(org)
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(jupyter-org-test-src-block
|
2018-11-16 22:14:31 -06:00
|
|
|
|
"\
|
2018-11-18 11:59:55 -06:00
|
|
|
|
from IPython.core.display import HTML
|
2018-11-16 22:14:31 -06:00
|
|
|
|
HTML('<a href=\"http://foo.com\">link</a>')"
|
|
|
|
|
"\
|
2018-02-12 10:49:41 -06:00
|
|
|
|
#+BEGIN_EXPORT html
|
|
|
|
|
<a href=\"http://foo.com\">link</a>
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXPORT
|
|
|
|
|
"))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(ert-deftest ob-jupyter-markdown-results ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.core.display import Markdown
|
|
|
|
|
Markdown('*b*')"
|
|
|
|
|
"\
|
|
|
|
|
#+BEGIN_EXPORT markdown
|
|
|
|
|
*b*
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXPORT
|
|
|
|
|
"))
|
2018-11-18 11:59:55 -06:00
|
|
|
|
|
|
|
|
|
(ert-deftest ob-jupyter-latex-results ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.core.display import Latex
|
|
|
|
|
Latex(r'$\\alpha$')"
|
|
|
|
|
"\
|
|
|
|
|
#+BEGIN_EXPORT latex
|
|
|
|
|
$\\alpha$
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXPORT
|
|
|
|
|
")
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(ert-info ("Raw results")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.core.display import Latex
|
|
|
|
|
Latex(r'$\\alpha$')"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
"$\\alpha$\n"
|
2018-11-18 11:59:55 -06:00
|
|
|
|
:results "raw")))
|
|
|
|
|
|
|
|
|
|
(ert-deftest ob-jupyter-error-results ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"from IPython."
|
2018-11-24 22:08:26 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: from IPython.
|
|
|
|
|
: ^
|
|
|
|
|
: SyntaxError: invalid syntax"
|
2018-11-18 11:59:55 -06:00
|
|
|
|
:regexp t))
|
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-deftest ob-jupyter-image-results ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(let* ((default-directory (file-name-directory
|
|
|
|
|
(locate-library "jupyter")))
|
|
|
|
|
(org-babel-jupyter-resource-directory "./")
|
|
|
|
|
(file (expand-file-name "jupyter.png"))
|
2020-09-17 14:09:14 -05:00
|
|
|
|
(py-version (jupyter-kernel-action
|
|
|
|
|
(jupyter-org-test-session-client "python")
|
|
|
|
|
(lambda (kernel)
|
|
|
|
|
(jupyter-test-ipython-kernel-version
|
|
|
|
|
(jupyter-kernel-spec kernel)))))
|
2018-11-24 22:08:26 -06:00
|
|
|
|
;; There is a change in how the IPython kernel prints base64 encoded
|
|
|
|
|
;; images somewhere between [4.6.1, 5.1]. In 5.1, base64 encoded
|
|
|
|
|
;; images are printed with line breaks whereas in 4.6.1 they are not.
|
|
|
|
|
(line-breaks (version< "4.6.1" py-version))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(data (let ((buffer-file-coding-system 'binary))
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(set-buffer-multibyte nil)
|
|
|
|
|
(insert-file-contents-literally file)
|
|
|
|
|
(base64-encode-region (point-min) (point-max) line-breaks)
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(insert "\n")
|
|
|
|
|
(buffer-substring-no-properties (point-min) (point-max)))))
|
|
|
|
|
(image-file-name (jupyter-org-image-file-name data "png")))
|
|
|
|
|
(unwind-protect
|
2019-01-10 17:48:37 -06:00
|
|
|
|
(progn
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
(format "\
|
2018-02-12 10:49:41 -06:00
|
|
|
|
from IPython.display import Image
|
2018-11-16 22:14:31 -06:00
|
|
|
|
Image(filename='%s')" file)
|
2019-03-03 16:53:39 -06:00
|
|
|
|
(format "[[file:%s]]\n" image-file-name))
|
2019-01-11 11:38:35 -06:00
|
|
|
|
(ert-info ("Create a drawer containing file links")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
(format "\
|
2019-01-10 17:48:37 -06:00
|
|
|
|
from IPython.display import Image
|
|
|
|
|
from IPython.display import display
|
|
|
|
|
display(Image(filename='%s'))
|
|
|
|
|
Image(filename='%s')" file file)
|
2019-01-11 11:38:35 -06:00
|
|
|
|
(concat
|
2019-03-03 16:53:39 -06:00
|
|
|
|
":RESULTS:\n"
|
2019-01-11 11:38:35 -06:00
|
|
|
|
(format "[[file:%s]]" image-file-name) "\n"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
(format "[[file:%s]]" image-file-name) "\n"
|
|
|
|
|
":END:\n")
|
2019-01-11 11:38:35 -06:00
|
|
|
|
:async "yes"))
|
|
|
|
|
(ert-info ("Append a file link to a drawer")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
(format "\
|
2019-01-10 17:48:37 -06:00
|
|
|
|
from IPython.display import Image
|
|
|
|
|
from IPython.display import display
|
|
|
|
|
display(Image(filename='%s'))
|
|
|
|
|
display(Image(filename='%s'))
|
|
|
|
|
Image(filename='%s')" file file file)
|
2019-01-11 11:38:35 -06:00
|
|
|
|
(concat
|
2019-03-03 16:53:39 -06:00
|
|
|
|
":RESULTS:\n"
|
|
|
|
|
(format "[[file:%s]]" image-file-name) "\n"
|
2019-01-11 11:38:35 -06:00
|
|
|
|
(format "[[file:%s]]" image-file-name) "\n"
|
|
|
|
|
(format "[[file:%s]]" image-file-name) "\n"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
":END:\n")
|
2019-01-11 11:38:35 -06:00
|
|
|
|
:async "yes"))
|
2019-01-12 20:20:19 -06:00
|
|
|
|
(ert-info ("Image with width and height metadata")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
(format "\
|
|
|
|
|
from IPython.display import Image
|
|
|
|
|
Image(filename='%s', width=300)" file)
|
|
|
|
|
(concat
|
2019-03-03 16:53:39 -06:00
|
|
|
|
":RESULTS:\n"
|
2019-01-12 20:20:19 -06:00
|
|
|
|
"#+ATTR_ORG: :width 300\n"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
(format "[[file:%s]]" image-file-name) "\n"
|
|
|
|
|
":END:\n"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(when (file-exists-p image-file-name)
|
|
|
|
|
(delete-file image-file-name)))))
|
2018-02-12 10:49:41 -06:00
|
|
|
|
|
2018-11-10 14:26:00 -06:00
|
|
|
|
(ert-deftest jupyter-org-result ()
|
2018-11-14 13:15:29 -06:00
|
|
|
|
:tags '(org)
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(let ((req (jupyter-org-request)))
|
|
|
|
|
(should (equal (jupyter-org-result req (list :text/plain "foo"))
|
2018-11-19 10:41:06 -06:00
|
|
|
|
'(fixed-width (:value "foo"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(should (equal (jupyter-org-result req (list :text/html "foo"))
|
2018-11-19 10:41:06 -06:00
|
|
|
|
'(export-block (:type "html" :value "foo\n"))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;; Calls `org-babel-script-escape' for scalar data
|
|
|
|
|
(should (equal (jupyter-org-result req (list :text/plain "[1, 2, 3]"))
|
2019-01-16 18:50:02 -06:00
|
|
|
|
"| 1 | 2 | 3 |\n"))
|
|
|
|
|
(should (equal (jupyter-org-result req (list :text/plain "[1, 2, 3] Foo"))
|
|
|
|
|
'(fixed-width (:value "[1, 2, 3] Foo"))))))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
|
2019-07-12 22:45:38 -05:00
|
|
|
|
(ert-deftest jupyter-org-request-at-point ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert (format "\
|
|
|
|
|
#+begin_src jupyter-python :session %s :async yes
|
|
|
|
|
1 + 1;
|
|
|
|
|
#+end_src" jupyter-org-test-session))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(org-babel-execute-src-block)
|
|
|
|
|
(let ((req (jupyter-org-request-at-point)))
|
|
|
|
|
(should req)
|
|
|
|
|
(should (jupyter-org-request-p req))
|
|
|
|
|
(jupyter-wait-until-idle req)
|
|
|
|
|
(should-not (jupyter-org-request-at-point)))))
|
|
|
|
|
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(ert-deftest jupyter-org-result-python ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
;; Test that the python language specialized method calls
|
|
|
|
|
;; `org-babel-python-table-or-string', this is more of a test for method
|
|
|
|
|
;; order.
|
|
|
|
|
(cl-letf* ((py-method-called nil)
|
|
|
|
|
(req (jupyter-org-request))
|
|
|
|
|
((symbol-function #'org-babel-python-table-or-string)
|
|
|
|
|
(lambda (results)
|
|
|
|
|
(setq py-method-called t)
|
|
|
|
|
(org-babel-script-escape results)))
|
|
|
|
|
(jupyter-current-client (jupyter-kernel-client)))
|
|
|
|
|
(oset jupyter-current-client kernel-info
|
2019-03-07 22:22:51 -06:00
|
|
|
|
(list :language_info (list :name 'python)))
|
|
|
|
|
(should (equal (jupyter-kernel-language jupyter-current-client) 'python))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
;; Bring in the python specific methods
|
|
|
|
|
(jupyter-load-language-support jupyter-current-client)
|
|
|
|
|
(should (equal (jupyter-org-result req (list :text/plain "[1, 2, 3]"))
|
2018-11-19 10:41:06 -06:00
|
|
|
|
"| 1 | 2 | 3 |\n"))
|
2018-11-16 22:14:31 -06:00
|
|
|
|
(should py-method-called)))
|
2018-01-08 21:38:32 -06:00
|
|
|
|
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(ert-deftest jupyter-org-src-block-cache ()
|
|
|
|
|
:tags '(org)
|
2018-11-24 22:08:26 -06:00
|
|
|
|
(let (jupyter-org--src-block-cache)
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert
|
2019-02-12 08:55:40 -06:00
|
|
|
|
"#+BEGIN_SRC jupyter-python :session " jupyter-org-test-session "\n"
|
2018-11-18 11:59:55 -06:00
|
|
|
|
"imp\n"
|
|
|
|
|
"#+END_SRC\n\n\n#+RESULTS:")
|
|
|
|
|
;; Needed for the text properties
|
|
|
|
|
(font-lock-ensure)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(end-of-line)
|
2018-11-24 22:08:26 -06:00
|
|
|
|
(should-not jupyter-org--src-block-cache)
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(should-not (jupyter-org--same-src-block-p))
|
|
|
|
|
(jupyter-org--set-current-src-block)
|
2018-11-24 22:08:26 -06:00
|
|
|
|
(should jupyter-org--src-block-cache)
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(should (jupyter-org--same-src-block-p))
|
|
|
|
|
(cl-destructuring-bind (params beg end)
|
2018-11-24 22:08:26 -06:00
|
|
|
|
jupyter-org--src-block-cache
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(should (equal (alist-get :session params) jupyter-org-test-session))
|
|
|
|
|
(should (= beg (line-beginning-position)))
|
|
|
|
|
(should (= end (line-beginning-position 2))))
|
|
|
|
|
(ert-info ("End marker updates after insertion")
|
|
|
|
|
(forward-line)
|
|
|
|
|
(insert "new source block text\n")
|
|
|
|
|
;; #+BEGIN_SRC ...
|
|
|
|
|
;; imp
|
|
|
|
|
;; new source block text
|
|
|
|
|
;; |#+END_SRC
|
|
|
|
|
(cl-destructuring-bind (params beg end)
|
2018-11-24 22:08:26 -06:00
|
|
|
|
jupyter-org--src-block-cache
|
2018-11-18 11:59:55 -06:00
|
|
|
|
(should (equal (alist-get :session params) jupyter-org-test-session))
|
|
|
|
|
(should (= beg (line-beginning-position -1)))
|
|
|
|
|
(should (= end (line-beginning-position))))))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-org-when-in-src-block ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(ert-info ("In Jupyter blocks")
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert
|
2019-02-12 08:55:40 -06:00
|
|
|
|
"#+BEGIN_SRC jupyter-python :session " jupyter-org-test-session "\n"
|
2018-11-18 11:59:55 -06:00
|
|
|
|
"1 + 1\n"
|
|
|
|
|
"#+END_SRC\nfoo")
|
|
|
|
|
;; Needed for the text properties
|
|
|
|
|
(font-lock-ensure)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should (jupyter-org-when-in-src-block t))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t))))
|
|
|
|
|
(ert-info ("Not in Jupyter block")
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert
|
|
|
|
|
"#+BEGIN_SRC python :session " jupyter-org-test-session "\n"
|
|
|
|
|
"1 + 1\n"
|
|
|
|
|
"#+END_SRC\nfoo")
|
|
|
|
|
;; Needed for the text properties
|
|
|
|
|
(font-lock-ensure)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should-not (jupyter-org-when-in-src-block t)))))
|
|
|
|
|
|
2018-12-01 00:27:39 -06:00
|
|
|
|
(ert-deftest jupyter-org--stream-context-p ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(org-mode)
|
|
|
|
|
(dolist
|
|
|
|
|
(res '(("\
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
:RESULTS:
|
|
|
|
|
: Foo
|
|
|
|
|
:END:" . 27)
|
|
|
|
|
("\
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
: Foo
|
|
|
|
|
" . 17)
|
|
|
|
|
("\
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
Foo
|
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
" . 31)
|
|
|
|
|
("\
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
:RESULTS:
|
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
Foo
|
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
:END:
|
|
|
|
|
" . 41)
|
|
|
|
|
("\
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
file:foo
|
|
|
|
|
" . nil)))
|
|
|
|
|
(insert (car res))
|
|
|
|
|
(if (cdr res)
|
|
|
|
|
(should (= (jupyter-org--stream-context-p (org-element-at-point)) (cdr res)))
|
|
|
|
|
(should-not (jupyter-org--stream-context-p (org-element-at-point))))
|
|
|
|
|
(erase-buffer))))
|
|
|
|
|
|
2019-06-11 20:13:51 -05:00
|
|
|
|
(ert-deftest jupyter-org--append-to-fixed-width ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(org-mode)
|
|
|
|
|
(pop-to-buffer (current-buffer))
|
|
|
|
|
(insert ": foo\n")
|
|
|
|
|
(skip-chars-backward "\n")
|
|
|
|
|
(jupyter-org--append-to-fixed-width "bar" nil)
|
|
|
|
|
(should (equal (buffer-string) ": foobar\n"))
|
|
|
|
|
(skip-chars-backward "\n")
|
|
|
|
|
(jupyter-org--append-to-fixed-width "bar" t)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
: foobar
|
|
|
|
|
: bar
|
|
|
|
|
"))
|
|
|
|
|
(skip-chars-backward "\n")
|
|
|
|
|
(jupyter-org--append-to-fixed-width "a\nb" nil)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
: foobar
|
|
|
|
|
: bara
|
|
|
|
|
: b
|
|
|
|
|
"))
|
|
|
|
|
(skip-chars-backward "\n")
|
|
|
|
|
(jupyter-org--append-to-fixed-width "a\nb" t)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
: foobar
|
|
|
|
|
: bara
|
|
|
|
|
: b
|
|
|
|
|
: a
|
|
|
|
|
: b
|
|
|
|
|
"))))
|
|
|
|
|
|
2019-06-11 21:00:27 -05:00
|
|
|
|
(defvar org-edit-src-preserve-indentation)
|
|
|
|
|
(defvar org-src-preserve-indentation)
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-org--append-to-example-block ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(let ((org-src-preserve-indentation 0))
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(org-mode)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|
|
|
|
|
|
#+end_example
|
|
|
|
|
")
|
|
|
|
|
(search-backward "|")
|
|
|
|
|
(forward-char)
|
|
|
|
|
(jupyter-org--append-to-example-block "a\nb" nil)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|a
|
|
|
|
|
b
|
|
|
|
|
#+end_example
|
|
|
|
|
"))
|
|
|
|
|
(backward-char)
|
|
|
|
|
(jupyter-org--append-to-example-block "a\nb" t)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|a
|
|
|
|
|
b
|
|
|
|
|
a
|
|
|
|
|
b
|
|
|
|
|
#+end_example
|
|
|
|
|
"))))
|
|
|
|
|
(when (version<= "9.2" (org-version))
|
|
|
|
|
(let ((org-src-preserve-indentation nil)
|
|
|
|
|
(org-edit-src-preserve-indentation 2))
|
|
|
|
|
(ert-info ("Example block indentation")
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(org-mode)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|
|
|
|
|
|
#+end_example
|
|
|
|
|
")
|
|
|
|
|
(search-backward "|")
|
|
|
|
|
(forward-char)
|
|
|
|
|
(jupyter-org--append-to-example-block "ab" nil)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|ab
|
|
|
|
|
#+end_example
|
|
|
|
|
"))
|
|
|
|
|
(backward-char)
|
|
|
|
|
(jupyter-org--append-to-example-block "ab" t)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|ab
|
|
|
|
|
ab
|
|
|
|
|
#+end_example
|
|
|
|
|
"))
|
|
|
|
|
(backward-char)
|
|
|
|
|
;; TODO: What to about the case of removing the common indentation
|
|
|
|
|
;; while appending to a line?
|
|
|
|
|
(jupyter-org--append-to-example-block " a\n b" nil)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|ab
|
|
|
|
|
ab a
|
|
|
|
|
b
|
|
|
|
|
#+end_example
|
|
|
|
|
"))
|
|
|
|
|
(backward-char)
|
|
|
|
|
(jupyter-org--append-to-example-block " a\n b" t)
|
|
|
|
|
(should (equal (buffer-string) "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
|ab
|
|
|
|
|
ab a
|
|
|
|
|
b
|
|
|
|
|
a
|
|
|
|
|
b
|
|
|
|
|
#+end_example
|
|
|
|
|
")))))))
|
|
|
|
|
|
2019-06-11 20:16:50 -05:00
|
|
|
|
(ert-deftest jupyter-org-indent-inserted-region ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert " a")
|
|
|
|
|
(jupyter-org-indent-inserted-region nil
|
|
|
|
|
(insert "\nb\n c\n"))
|
|
|
|
|
(should (equal (buffer-string) " a\n b\n c\n"))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(jupyter-org-indent-inserted-region 2
|
|
|
|
|
(insert "a\n b\n c\n"))
|
|
|
|
|
(should (equal (buffer-string) " a\n b\n c\n"))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(jupyter-org-indent-inserted-region nil
|
|
|
|
|
(insert " a\nb\nc"))
|
|
|
|
|
(should (equal (buffer-string) " a\nb\nc"))))
|
|
|
|
|
|
2018-12-01 00:27:39 -06:00
|
|
|
|
(ert-deftest jupyter-org-coalesce-stream-results ()
|
|
|
|
|
:tags '(org)
|
2019-02-21 11:35:47 -06:00
|
|
|
|
(let ((org-edit-src-content-indentation 0))
|
|
|
|
|
(ert-info ("Synchronous")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"foo\")
|
|
|
|
|
print(\"foo\", flush=True)
|
|
|
|
|
print(\"foo\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: foo
|
|
|
|
|
: foo
|
2019-03-03 16:53:39 -06:00
|
|
|
|
: foo
|
|
|
|
|
"))
|
2019-02-21 11:35:47 -06:00
|
|
|
|
(ert-info ("Asynchronous")
|
|
|
|
|
(ert-info ("Newline after first stream message")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"foo\")
|
|
|
|
|
print(\"foo\", flush=True)
|
|
|
|
|
print(\"foo\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: foo
|
|
|
|
|
: foo
|
2019-03-03 16:53:39 -06:00
|
|
|
|
: foo
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"foo\", flush=True)
|
|
|
|
|
print(\"foo\", end=\"\", flush=True)
|
|
|
|
|
print(\"foo\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: foo
|
2019-03-03 16:53:39 -06:00
|
|
|
|
: foofoo
|
|
|
|
|
")
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes")
|
|
|
|
|
(ert-info ("No newline after first stream message")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"foo\")
|
|
|
|
|
print(\"foo\", end=\"\", flush=True)
|
|
|
|
|
print(\"bar\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: foo
|
2019-03-03 16:53:39 -06:00
|
|
|
|
: foobar
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes"))
|
|
|
|
|
(ert-info ("Multiple newlines in appended stream message")
|
|
|
|
|
(ert-info ("Newline after first stream message")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"foo\")
|
|
|
|
|
print(\"foo\", flush=True)
|
|
|
|
|
print(\"bar\\nqux\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: foo
|
|
|
|
|
: foo
|
|
|
|
|
: bar
|
2019-03-03 16:53:39 -06:00
|
|
|
|
: qux
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes"))
|
|
|
|
|
(ert-info ("No newline after first stream message")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"foo\")
|
|
|
|
|
print(\"foo\", end=\"\", flush=True)
|
|
|
|
|
print(\"bar\\nqux\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
: foo
|
|
|
|
|
: foobar
|
2019-03-03 16:53:39 -06:00
|
|
|
|
: qux
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes")))
|
|
|
|
|
(ert-info ("fixed-width to example-block promotion")
|
|
|
|
|
(let ((org-babel-min-lines-for-block-output 2))
|
2019-03-03 16:53:39 -06:00
|
|
|
|
(jupyter-org-test-src-block "print(\"z\")" ": z\n")
|
2019-02-21 11:35:47 -06:00
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"z\", flush=True)
|
|
|
|
|
print(\"z\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
z
|
|
|
|
|
z
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes")
|
|
|
|
|
(ert-info ("Appending after block promotion")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"z\", flush=True)
|
|
|
|
|
print(\"z\", flush=True)
|
|
|
|
|
print(\"z\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
z
|
|
|
|
|
z
|
|
|
|
|
z
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes"))
|
|
|
|
|
(ert-info ("Append to block with newline after first stream message")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"z\\nz\", flush=True)
|
|
|
|
|
print(\"z\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
z
|
|
|
|
|
z
|
|
|
|
|
z
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes"))
|
|
|
|
|
(ert-info ("Append to block without newline after first stream message")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
print(\"z\\nz\", end=\"\", flush=True)
|
|
|
|
|
print(\"z\")"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
"\
|
2018-12-01 00:27:39 -06:00
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
z
|
|
|
|
|
zz
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes")))))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(ert-deftest jupyter-org-example-block-indentation ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(skip-unless (version<= "9.2" (org-version)))
|
|
|
|
|
(let ((org-babel-min-lines-for-block-output 2)
|
|
|
|
|
(org-edit-src-content-indentation 2))
|
|
|
|
|
(ert-info ("Appending obeys `org-edit-src-content-indentation'")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
print(\"z\", flush=True)
|
|
|
|
|
print(\"z\")"
|
|
|
|
|
"\
|
|
|
|
|
#+BEGIN_EXAMPLE
|
|
|
|
|
z
|
|
|
|
|
z
|
2019-03-03 16:53:39 -06:00
|
|
|
|
#+END_EXAMPLE
|
|
|
|
|
"
|
2019-02-21 11:35:47 -06:00
|
|
|
|
:async "yes"))))
|
2018-12-01 00:27:39 -06:00
|
|
|
|
|
2019-03-03 17:54:31 -06:00
|
|
|
|
(ert-deftest jupyter-org-wrapping-with-drawer ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(ert-info ("Preserve whitespace after wrapping a result")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
print(\"foo\", flush=True)
|
|
|
|
|
1 + 1"
|
|
|
|
|
"\
|
|
|
|
|
:RESULTS:
|
|
|
|
|
: foo
|
|
|
|
|
: 2
|
|
|
|
|
:END:
|
|
|
|
|
"
|
|
|
|
|
:async "yes")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
print(\"foo\", flush=True)
|
|
|
|
|
1 + 1"
|
|
|
|
|
"\
|
|
|
|
|
:RESULTS:
|
|
|
|
|
: foo
|
|
|
|
|
: 2
|
|
|
|
|
:END:
|
|
|
|
|
")))
|
|
|
|
|
|
2019-06-12 21:10:07 -05:00
|
|
|
|
(ert-deftest jupyter-org-issue-126 ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert (format "\
|
|
|
|
|
* H1
|
|
|
|
|
- list1
|
|
|
|
|
#+begin_src jupyter-python :session %s :async yes
|
|
|
|
|
print(\"Hello\")
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
* H2
|
|
|
|
|
" jupyter-org-test-session))
|
|
|
|
|
(org-babel-previous-src-block)
|
|
|
|
|
(org-babel-execute-src-block)
|
|
|
|
|
(with-current-buffer (org-babel-initiate-session)
|
2020-11-14 15:09:19 -06:00
|
|
|
|
(sleep-for 1))
|
2019-06-12 21:10:07 -05:00
|
|
|
|
(org-back-to-heading)
|
|
|
|
|
(org-down-element)
|
|
|
|
|
(should (eq (org-element-type (org-element-context)) 'plain-list))
|
|
|
|
|
(org-babel-next-src-block)
|
|
|
|
|
(should (eq (org-element-type (org-element-context)) 'src-block))
|
|
|
|
|
(should (org-babel-where-is-src-block-result))
|
|
|
|
|
(goto-char (org-babel-where-is-src-block-result))
|
|
|
|
|
(let ((result (org-element-context)))
|
|
|
|
|
(should (eq (org-element-type result) 'fixed-width))
|
|
|
|
|
(should (equal (org-element-property :value result) "Hello")))))
|
|
|
|
|
|
2019-03-16 16:50:00 -05:00
|
|
|
|
(ert-deftest jupyter-org-font-lock-ansi-escapes ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"print('AB\x1b[43mCD\x1b[0mEF')"
|
|
|
|
|
": AB[43mCD[0mEF\n")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.display import publish_display_data
|
|
|
|
|
publish_display_data({'text/plain': 'AB\x1b[43mCD\x1b[0mEF'});"
|
|
|
|
|
": AB[43mCD[0mEF\n")
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(org-mode)
|
|
|
|
|
(jupyter-org-interaction-mode 1)
|
|
|
|
|
(let ((test-fun
|
|
|
|
|
(lambda (face-pos invisible-pos)
|
|
|
|
|
(font-lock-ensure)
|
|
|
|
|
(jupyter-test-text-has-property 'invisible t invisible-pos)
|
|
|
|
|
(should (listp (get-text-property face-pos 'face)))
|
|
|
|
|
(should (get-text-property face-pos 'font-lock-face))
|
|
|
|
|
(should (eq (caar (get-text-property face-pos 'face)) 'background-color)))))
|
|
|
|
|
(insert ": AB[43mCD[0mEF")
|
|
|
|
|
(funcall test-fun 10 '(5 6 7 8 9 12 13 14 15))
|
|
|
|
|
;; Test the cached faces path
|
|
|
|
|
(remove-text-properties (point-min) (point-max) '(face))
|
|
|
|
|
(funcall test-fun 10 '(5 6 7 8 9 12 13 14 15))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+begin_example
|
|
|
|
|
AB[43mCD[0mEF
|
|
|
|
|
#+end_example")
|
|
|
|
|
;; Test the cached faces path
|
|
|
|
|
(remove-text-properties (point-min) (point-max) '(face))
|
2019-06-11 21:48:22 -05:00
|
|
|
|
(funcall test-fun 24 '(19 20 21 22 23 26 27 28 29))))
|
|
|
|
|
(ert-info ("Leading indentation")
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(org-mode)
|
|
|
|
|
(jupyter-org-interaction-mode 1)
|
|
|
|
|
(pop-to-buffer (current-buffer))
|
|
|
|
|
(let ((beg (+ (point-min) 2)) end)
|
|
|
|
|
(insert " : AB[43mCD[0mEF\n")
|
|
|
|
|
(insert " : AB[43mCD[0mEF\n")
|
|
|
|
|
(setq end (1- (point)))
|
|
|
|
|
(insert "hey\n")
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(jupyter-org-font-lock-ansi-escapes (point-max))
|
|
|
|
|
(should-not (text-property-not-all beg end 'jupyter-ansi t))))))
|
2019-03-16 16:50:00 -05:00
|
|
|
|
|
2019-03-24 00:11:56 -05:00
|
|
|
|
(ert-deftest jupyter-org-closest-jupyter-language ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
x
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC jupyter-bar
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
#+BEGIN_SRC baz
|
|
|
|
|
#+END_SRC
|
|
|
|
|
")
|
|
|
|
|
(re-search-backward "x")
|
|
|
|
|
(should (equal (jupyter-org-closest-jupyter-language)
|
|
|
|
|
"jupyter-bar"))
|
|
|
|
|
(forward-line -1)
|
|
|
|
|
(should (equal (jupyter-org-closest-jupyter-language)
|
|
|
|
|
"jupyter-foo"))
|
|
|
|
|
(forward-line 2)
|
|
|
|
|
(should (equal (jupyter-org-closest-jupyter-language)
|
|
|
|
|
"jupyter-bar"))
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(should (equal (jupyter-org-closest-jupyter-language)
|
|
|
|
|
"jupyter-bar"))
|
|
|
|
|
(forward-line -3)
|
|
|
|
|
(should (equal (jupyter-org-closest-jupyter-language)
|
|
|
|
|
"jupyter-bar"))))
|
|
|
|
|
|
2019-06-13 10:42:53 -05:00
|
|
|
|
(ert-deftest jupyter-org-define-key ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(save-excursion
|
|
|
|
|
(insert (format "\
|
|
|
|
|
#+begin_src jupyter-python :session %s
|
|
|
|
|
1 + 1
|
|
|
|
|
#+end_src" jupyter-org-test-session)))
|
|
|
|
|
;; Needed for the text properties
|
|
|
|
|
(font-lock-ensure)
|
|
|
|
|
(forward-line)
|
|
|
|
|
(let ((test-key
|
|
|
|
|
(lambda (key &optional lang no-def)
|
|
|
|
|
(or lang (setq lang 'jupyter))
|
|
|
|
|
(let* ((jupyter-org-interaction-mode-map (make-sparse-keymap))
|
|
|
|
|
(test-def-called nil)
|
|
|
|
|
(test-def (lambda ()
|
|
|
|
|
(interactive)
|
|
|
|
|
(setq test-def-called t))))
|
|
|
|
|
(jupyter-org-define-key key test-def lang)
|
|
|
|
|
(let ((def (lookup-key jupyter-org-interaction-mode-map key)))
|
|
|
|
|
(if no-def (should-not def)
|
|
|
|
|
(should (functionp def))
|
|
|
|
|
(call-interactively test-def)
|
|
|
|
|
(should test-def-called)))))))
|
|
|
|
|
(ert-info ("Simple definition")
|
|
|
|
|
(funcall test-key "i"))
|
|
|
|
|
(ert-info ("Prefix keys")
|
|
|
|
|
(funcall test-key (kbd "C-x C-e")))
|
|
|
|
|
(ert-info ("Language based keys")
|
|
|
|
|
(funcall test-key (kbd "g") 'python)
|
|
|
|
|
(funcall test-key (kbd "g") 'julia 'no-def))
|
|
|
|
|
(forward-line -1)
|
|
|
|
|
(ert-info ("No definition outside source block")
|
|
|
|
|
(funcall test-key (kbd "g") 'python 'no-def)))))
|
|
|
|
|
|
2019-03-23 00:12:06 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-src-block-session ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo :session bar
|
|
|
|
|
#+END_SRC")
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(should-error (org-babel-jupyter-src-block-session))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo :kernel bar
|
|
|
|
|
#+END_SRC")
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(should-error (org-babel-jupyter-src-block-session))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo :session bar :kernel bar
|
|
|
|
|
#+END_SRC")
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(should (equal (org-babel-jupyter-src-block-session)
|
|
|
|
|
(org-babel-jupyter-session-key
|
|
|
|
|
(nth 2 (org-babel-get-src-block-info 'light)))))
|
2019-07-25 03:31:44 -05:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
|
|
|
|
|
(insert "\
|
|
|
|
|
#+NAME: foo
|
|
|
|
|
#+BEGIN_SRC jupyter-foo :session bar :kernel bar
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
#+CALL: foo()")
|
|
|
|
|
(should (equal (org-babel-jupyter-src-block-session)
|
|
|
|
|
(org-babel-jupyter-session-key
|
|
|
|
|
(nth 2 (org-babel-lob-get-info)))))))
|
2019-03-23 00:12:06 -05:00
|
|
|
|
|
2019-05-16 14:26:19 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-override-src-block ()
|
|
|
|
|
:tags '(org)
|
2020-04-10 11:06:56 -05:00
|
|
|
|
(let* ((lang (cl-gensym))
|
2019-05-16 14:26:19 -05:00
|
|
|
|
(overriding-funs (cl-set-difference
|
|
|
|
|
org-babel-jupyter--babel-ops
|
2020-04-10 11:06:56 -05:00
|
|
|
|
'(variable-assignments expand-body))))
|
|
|
|
|
(cl-macrolet
|
|
|
|
|
((var-symbol
|
|
|
|
|
(var lang)
|
|
|
|
|
`(org-babel-jupyter--babel-var-symbol ,var ,lang))
|
|
|
|
|
(op-symbol
|
|
|
|
|
(op lang)
|
|
|
|
|
`(org-babel-jupyter--babel-op-symbol ,op ,lang))
|
|
|
|
|
(advice-p
|
|
|
|
|
(not name)
|
|
|
|
|
`(,(if not 'should-not 'should)
|
|
|
|
|
(advice-member-p 'ob-jupyter ,name))))
|
|
|
|
|
(set (var-symbol 'header-args (format "jupyter-%s" lang))
|
|
|
|
|
'((:kernel . "foo")))
|
|
|
|
|
(unwind-protect
|
|
|
|
|
(progn
|
|
|
|
|
(ert-info ("Overriding")
|
|
|
|
|
(org-babel-jupyter-override-src-block lang)
|
|
|
|
|
(dolist (fn overriding-funs)
|
|
|
|
|
(advice-p nil (op-symbol fn lang)))
|
|
|
|
|
(should (equal (symbol-value (var-symbol 'header-args lang))
|
|
|
|
|
'((:kernel . "foo")))))
|
|
|
|
|
(ert-info ("Restoring")
|
|
|
|
|
(org-babel-jupyter-restore-src-block lang)
|
|
|
|
|
(dolist (fn overriding-funs)
|
|
|
|
|
(advice-p t (op-symbol fn lang)))
|
|
|
|
|
(should-not (symbol-value (var-symbol 'header-args lang)))))
|
|
|
|
|
(dolist (op org-babel-jupyter--babel-ops)
|
|
|
|
|
(obarray-remove obarray (op-symbol op lang)))
|
|
|
|
|
(obarray-remove obarray (var-symbol 'header-args (format "jupyter-%s" lang)))
|
|
|
|
|
(dolist (op org-babel-jupyter--babel-vars)
|
|
|
|
|
(obarray-remove obarray (var-symbol op lang)))))))
|
2019-05-16 14:26:19 -05:00
|
|
|
|
|
2019-05-08 10:25:23 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-strip-ansi-escapes ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
: AB[43mCD[0mEF\n")
|
|
|
|
|
(org-babel-jupyter-strip-ansi-escapes 'latex)
|
|
|
|
|
(should (equal (buffer-string)
|
|
|
|
|
"\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
: ABCDEF\n"))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
")
|
|
|
|
|
(org-babel-jupyter-strip-ansi-escapes 'latex)
|
|
|
|
|
(should (equal (buffer-string)
|
|
|
|
|
"\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
|
|
|
|
#+RESULTS:
|
|
|
|
|
"))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert "\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
")
|
|
|
|
|
(org-babel-jupyter-strip-ansi-escapes 'latex)
|
|
|
|
|
(should (equal (buffer-string)
|
|
|
|
|
"\
|
|
|
|
|
#+BEGIN_SRC jupyter-foo
|
|
|
|
|
#+END_SRC
|
|
|
|
|
"))))
|
|
|
|
|
|
2019-02-22 08:29:00 -06:00
|
|
|
|
(ert-deftest org-babel-jupyter-:results-header-arg ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(ert-info ("scalar suppresses table output")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"[1, 2, 3]"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
"| 1 | 2 | 3 |\n"
|
2019-02-22 08:29:00 -06:00
|
|
|
|
;; Ensure that no interference happens from removing the file header
|
|
|
|
|
;; argument.
|
|
|
|
|
:file "foo"
|
|
|
|
|
;; FIXME: How to handle header arguments consistently in the async vs sync
|
|
|
|
|
;; case.
|
|
|
|
|
:async "yes")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"[1, 2, 3]"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
": [1, 2, 3]\n"
|
2019-02-22 08:29:00 -06:00
|
|
|
|
:results "scalar")))
|
|
|
|
|
|
2019-01-16 17:50:17 -06:00
|
|
|
|
(ert-deftest org-babel-jupyter-:dir-header-arg ()
|
|
|
|
|
:tags '(org)
|
2019-05-02 18:50:00 -05:00
|
|
|
|
(let ((convert-path
|
|
|
|
|
(lambda (s)
|
|
|
|
|
;; Convert forward slashes to backslashes on Windows
|
|
|
|
|
(if (memq system-type '(windows-nt cygwin ms-dos))
|
|
|
|
|
(replace-regexp-in-string "/" "\\\\" s)
|
2020-09-11 09:34:46 -05:00
|
|
|
|
s)))
|
|
|
|
|
(temporary-file-directory jupyter-test-temporary-directory))
|
|
|
|
|
(let ((dir (make-temp-file "dir" t))
|
|
|
|
|
(pwd
|
|
|
|
|
(substring
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(jupyter-eval "import os; os.getcwd()"))
|
|
|
|
|
1 -1)))
|
|
|
|
|
(ert-info ("Python")
|
|
|
|
|
(eval `(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2019-01-16 17:50:17 -06:00
|
|
|
|
os.path.abspath(os.getcwd())"
|
2020-09-11 09:34:46 -05:00
|
|
|
|
,(concat ": " (funcall convert-path dir) "\n")
|
|
|
|
|
:dir ,dir))
|
|
|
|
|
(ert-info ("Directory restored")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
2019-05-02 18:50:00 -05:00
|
|
|
|
os.path.abspath(os.getcwd())"
|
2020-09-11 09:34:46 -05:00
|
|
|
|
(concat ": "
|
|
|
|
|
(funcall convert-path
|
|
|
|
|
(expand-file-name (directory-file-name pwd)))
|
|
|
|
|
"\n")))
|
|
|
|
|
(ert-info ("Transformed code and backslashes")
|
|
|
|
|
;; See #302
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"print(r\"\\r\")"
|
|
|
|
|
": \\r\n"))
|
|
|
|
|
(ert-info ("Relative directory")
|
|
|
|
|
;; See #302
|
|
|
|
|
(let* ((temporary-file-directory jupyter-test-temporary-directory)
|
|
|
|
|
(dir (make-temp-file "dir-header-arg" t)))
|
|
|
|
|
;; FIXME: Don't use an internal function here.
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(let ((default-directory (file-name-directory dir)))
|
|
|
|
|
(jupyter-org-test-src-block-1
|
|
|
|
|
"print(\"hi\")"
|
|
|
|
|
": hi\n" nil
|
|
|
|
|
`((:dir . ,(file-name-base dir))))))))))))
|
2019-01-16 17:50:17 -06:00
|
|
|
|
|
2019-02-22 14:56:22 +01:00
|
|
|
|
(ert-deftest jupyter-org--find-mime-types ()
|
|
|
|
|
:tags '(org mime)
|
|
|
|
|
(ert-info ("Mimetype priority overwrite")
|
2019-02-22 08:32:00 -06:00
|
|
|
|
(should (equal (jupyter-org--find-mime-types "text")
|
|
|
|
|
'(:text/plain)))
|
|
|
|
|
(should (equal (jupyter-org--find-mime-types "image")
|
|
|
|
|
'(:image/png)))
|
|
|
|
|
(should (equal (jupyter-org--find-mime-types "plain html")
|
|
|
|
|
'(:text/plain :text/html)))
|
|
|
|
|
(should (equal (jupyter-org--find-mime-types "org jpeg")
|
|
|
|
|
'(:text/org :image/jpeg)))
|
|
|
|
|
(should (equal (jupyter-org--find-mime-types "plain foo html bar")
|
|
|
|
|
'(:text/plain :text/html)))
|
|
|
|
|
(should (equal (jupyter-org--find-mime-types "foo bar")
|
|
|
|
|
'()))))
|
|
|
|
|
|
|
|
|
|
(ert-deftest org-babel-jupyter-:display-header-arg ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.display import publish_display_data
|
|
|
|
|
publish_display_data({'text/plain': \"foo\", 'text/latex': \"$\\alpha$\"});"
|
2019-03-03 16:53:39 -06:00
|
|
|
|
": foo\n"
|
2019-02-22 08:32:00 -06:00
|
|
|
|
:display "plain"))
|
2019-02-22 14:56:22 +01:00
|
|
|
|
|
2019-07-25 03:33:58 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-babel-call ()
|
|
|
|
|
:tags '(org babel)
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert (format "\
|
|
|
|
|
#+NAME: foo
|
|
|
|
|
#+begin_src jupyter-python :async yes :session %s
|
|
|
|
|
1 + 1
|
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
|
|
" jupyter-org-test-session))
|
|
|
|
|
(insert "
|
|
|
|
|
#+CALL: foo()")
|
|
|
|
|
(org-ctrl-c-ctrl-c)
|
|
|
|
|
(beginning-of-line)
|
|
|
|
|
(jupyter-wait-until-idle (jupyter-org-request-at-point))
|
|
|
|
|
(goto-char (org-babel-where-is-src-block-result))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should (looking-at-p ": 2\n"))))
|
|
|
|
|
|
2020-03-12 21:47:00 -05:00
|
|
|
|
(ert-deftest org-babel-jupyter-inline-blocks ()
|
|
|
|
|
:tags '(org)
|
|
|
|
|
(ert-info ("Treat inline and non-inline results similarly")
|
|
|
|
|
;; #204
|
2020-03-16 01:00:23 -05:00
|
|
|
|
(let ((src (format "\
|
2020-03-12 21:47:00 -05:00
|
|
|
|
#+NAME: hello_jupyter
|
|
|
|
|
#+BEGIN_SRC jupyter-python :results value :display plain :session %s
|
|
|
|
|
\"hello\"
|
|
|
|
|
#+END_SRC
|
|
|
|
|
|
2020-03-16 01:00:23 -05:00
|
|
|
|
" jupyter-org-test-session)))
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(insert src)
|
|
|
|
|
(insert "call_hello_jupyter()")
|
|
|
|
|
(let ((pos (point)))
|
|
|
|
|
(beginning-of-line)
|
|
|
|
|
(org-ctrl-c-ctrl-c)
|
|
|
|
|
(goto-char pos)
|
|
|
|
|
(should (looking-at-p " {{{results(=hello=)}}}"))))
|
|
|
|
|
(jupyter-org-test
|
|
|
|
|
(save-excursion (insert src))
|
2020-03-12 21:47:00 -05:00
|
|
|
|
(org-ctrl-c-ctrl-c)
|
2020-03-16 01:00:23 -05:00
|
|
|
|
(goto-char (org-babel-where-is-src-block-result))
|
|
|
|
|
(forward-line)
|
|
|
|
|
(should (looking-at-p ": hello"))))))
|
2020-03-12 21:47:00 -05:00
|
|
|
|
|
2021-11-12 13:50:52 -06:00
|
|
|
|
(ert-deftest org-babel-jupyter-pandoc-output-order ()
|
|
|
|
|
:tags '(org pandoc)
|
|
|
|
|
;; See #351
|
|
|
|
|
(ert-info ("Ensure output order doesn't depend on Pandoc processing time")
|
|
|
|
|
(ert-info ("Async")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.display import HTML, Markdown, Latex
|
|
|
|
|
|
|
|
|
|
print(1)
|
|
|
|
|
display(HTML('<b>bold</b>'),
|
|
|
|
|
Latex('\\\\bf{lbold}'),
|
|
|
|
|
Markdown('**mbold**'))
|
|
|
|
|
print(2)"
|
|
|
|
|
"\
|
|
|
|
|
:RESULTS:
|
|
|
|
|
: 1
|
|
|
|
|
*bold*
|
|
|
|
|
*lbold*
|
|
|
|
|
*mbold*
|
|
|
|
|
: 2
|
|
|
|
|
:END:
|
|
|
|
|
"
|
|
|
|
|
:async "yes"
|
|
|
|
|
:pandoc "t"))
|
|
|
|
|
(ert-info ("Sync")
|
|
|
|
|
(jupyter-org-test-src-block
|
|
|
|
|
"\
|
|
|
|
|
from IPython.display import HTML, Markdown, Latex
|
|
|
|
|
|
|
|
|
|
print(1)
|
|
|
|
|
display(HTML('<b>bold</b>'),
|
|
|
|
|
Latex('\\\\bf{lbold}'),
|
|
|
|
|
Markdown('**mbold**'))
|
|
|
|
|
print(2)"
|
|
|
|
|
"\
|
|
|
|
|
:RESULTS:
|
|
|
|
|
: 1
|
|
|
|
|
*bold*
|
|
|
|
|
*lbold*
|
|
|
|
|
*mbold*
|
|
|
|
|
: 2
|
|
|
|
|
:END:
|
|
|
|
|
"
|
|
|
|
|
:pandoc "t"))))
|
2020-03-12 21:47:00 -05:00
|
|
|
|
|
2017-12-13 11:27:13 -06:00
|
|
|
|
;; Local Variables:
|
2019-06-27 16:54:00 -05:00
|
|
|
|
;; byte-compile-warnings: (unresolved obsolete lexical)
|
Refactor of `jupyter-kernel-manager.el`
This refactor implements a new class hierarchy to manage the lifetime of a
Jupyter kernel. The first node in this hierarchy is the
`jupyter-kernel-lifetime` class which defines a set of methods to manage the
lifetime of a kernel. An object that inherits from `jupyter-kernel-lifetime` is
stating that it has an association with a kernel and can be used to manage the
lifetime of the associated kernel.
The `jupyter-meta-kernel` class inherits from `jupyter-kernel-lifetime` and
mainly defines a `spec` slot used to hold the `kernelspec` from which a command
can be constructed to start a kernel and a `session` slot used to hold the
`jupyter-session` object that clients can use to establish communication with a
kernel once its live. Concrete classes that actually launch kernels are
intended to inherit from this class and use its slots.
`jupyter-kernel-process` manages the lifetime of a kernel started as a process
using the function `start-file-process`, `jupyter-command-kernel` calls the
`jupyter kernel` shell command to start a kernel, finally `jupyter-spec-kernel`
uses the `spec` slot to construct a shell command to start a kernel.
A `jupyter-kernel-manager` now consists of a `kernel` slot that holds a
`jupyter-meta-kernel` and a `control-channel` slot and inherits from
`jupyter-kernel-lifetime`. The `jupyter-kernel-lifetime` methods of the manager
just defer to those of `kernel` while also taking into account the
`control-channel`.
* jupyter-base.el (jupyter-write-connection-file): New function.
* jupyter-channel-ioloop.el
(jupyter-channel-ioloop-add-start-channel-event): Remove `sleep-for` call.
The startup message is not so important anymore.
* jupyter-client.el (jupyter-wait-until-startup: New function.
* jupyter-kernel-manager.el (jupyter-kernel-lifetime)
(jupyter-kernel, jupyter-kernel-process, jupyter-command-kernel)
(jupyter-spec-kernel): New classes.
(jupyter-kernel-manager): Inherit from jupyter-kernel-lifetime only and
implement its methods.
(jupyter-kernel-manager--cleanup, jupyter-kernel-managers)
(jupyter-delete-all-kernels, jupyter--kernel-sentinel)
(jupyter--start-kernel): Remove and remove related, their functionality has
been generalized in the new classes.
(jupyter-interrupt-kernel, jupyter-shutdown-kernel)
(jupyter-start-channels, jupyter-start-kernel, jupyter-kernel-alive-p)
(jupyter-kill-kernel): Refactor and implement to use the new class hierarchy.
* test/jupyter-test.el: Refactor tests to account for changes.
(jupyter-write-connect-file, jupyter-command-kernel): New tests.
* jupyter-kernelspec.el (jupyter-guess-kernelspec): New function.
2019-05-09 08:31:00 -05:00
|
|
|
|
;; eval: (and (functionp 'aggressive-indent-mode) (aggressive-indent-mode -1))
|
2017-12-13 11:27:13 -06:00
|
|
|
|
;; End:
|
2018-11-14 13:15:29 -06:00
|
|
|
|
;;; jupyter-test.el ends here
|