emacs-ipython-notebook/tests/func-test.el
John Miller 39f5d329f1 Functional tests start jupyter using ein:jupyter-server-start
This reduces some of the complexity in testein.py, but unfortunately running
tests is still unreliable. Running batchwise tests don't work at all in Windows,
and running from inside emacs tests often need to be run multiple times before
they pass.

The worst offender is the delete notebook test, which will pass on usually only
1 out of 3 tries.

Testing seems to have revealed a couple bugs, so win??
2017-04-07 08:18:41 -05:00

270 lines
13 KiB
EmacsLisp

(eval-when-compile (require 'cl))
(require 'ert)
(require 'ein-loaddefs)
(require 'ein-notebooklist)
(require 'ein-jupyter)
(require 'wid-edit)
(require 'ein-testing)
(require 'ein-testing-cell)
(let ((backend (getenv "EL_REQUEST_BACKEND")))
(when (and backend (not (equal backend "")))
(setq request-backend (intern backend))
(message "Using request-backend = %S" request-backend)))
(ein:setq-if-not ein:testing-dump-file-log "func-test-batch-log.log")
(ein:setq-if-not ein:testing-dump-file-messages "func-test-batch-messages.log")
(ein:setq-if-not ein:testing-dump-server-log "func-test_server_batch_emacs.log")
(ein:setq-if-not ein:testing-jupyter-server-command "c:/Users/mille/Miniconda3/envs/anaconda/Scripts/jupyter.exe")
(ein:setq-if-not ein:testing-jupyter-server-directory "c:/Users/mille/Dropbox/Projects/emacs-ipython-notebook/tests/notebook/nbformat4")
(setq message-log-max t)
(defvar ein:testing-port nil)
(defvar ein:testing-token nil)
(defun ein:testing-start-server ()
(unless (and (boundp '%ein:jupyter-server-session%) (processp %ein:jupyter-server-session%) (bufferp (process-buffer %ein:jupyter-server-session%)))
(ein:log 'debug "TESTING-START-SERVER starting jupyter server.")
(ein:jupyter-server-start ein:testing-jupyter-server-command ein:testing-jupyter-server-directory t)
(ein:testing-wait-until "Jupyter notebook server."
#'(lambda (buf)
(with-current-buffer buf
(goto-char (point-min))
(search-forward "Copy/paste this URL into your browser" nil t)))
(list (process-buffer %ein:jupyter-server-session%))
5000)
(ein:log 'debug "TESTING-START-SERVER logging in.")
(multiple-value-bind (url token) (ein:jupyter-server-conn-info)
(ein:notebooklist-login url token)
(setq ein:testing-port url)
(setq ein:testing-token token))
(ein:log 'debug "TESTING-START-SERVER succesfully logged in.")))
(defun ein:testing-wait-until (message predicate &optional predargs max-count)
"Wait until PREDICATE function returns non-`nil'.
PREDARGS is argument list for the PREDICATE function.
Make MAX-COUNT larger \(default 50) to wait longer before timeout."
(ein:log 'debug "TESTING-WAIT-UNTIL start")
(ein:log 'debug "TESTING-WAIT-UNTIL waiting on: %s" message)
(unless (setq max-count 50))
(unless (loop repeat max-count
when (apply predicate predargs)
return t
;; borrowed from `deferred:sync!':
do (sit-for 0.2)
do (sleep-for 0.2))
(error "Timeout"))
(ein:log 'debug "TESTING-WAIT-UNTIL end"))
(defun ein:testing-get-notebook-by-name (url-or-port notebook-name &optional path)
(ein:log 'debug "TESTING-GET-NOTEBOOK-BY-NAME start")
;; Kill notebook list buffer here to make sure next
;; `ein:testing-wait-until' works properly.
(kill-buffer (ein:notebooklist-get-buffer url-or-port))
(when path
(setq notebook-name (format "%s/%s" path notebook-name)))
(let ((content (ein:notebooklist-open url-or-port path t)))
;; (sit-for 1.0) ;; Because some computers are too fast???
(ein:testing-wait-until "ein:notebooklist-open" (lambda () (ein:$content-url-or-port content)))
(with-current-buffer (ein:notebooklist-get-buffer (ein:$content-url-or-port content))
(prog1
(ignore-errors
(ein:notebooklist-open-notebook-by-name notebook-name (ein:$content-url-or-port content)))
(ein:log 'debug "TESTING-GET-NOTEBOOK-BY-NAME end")))))
(defun ein:testing-get-untitled0-or-create (url-or-port &optional path)
(unless path (setq path ""))
(ein:log 'debug "TESTING-GET-UNTITLED0-OR-CREATE start")
(let ((notebook (ein:testing-get-notebook-by-name url-or-port "Untitled.ipynb")))
(if notebook
(progn (ein:log 'debug
"TESTING-GET-UNTITLED0-OR-CREATE notebook already exists")
notebook)
(ein:log 'debug
"TESTING-GET-UNTITLED0-OR-CREATE creating notebook")
(let ((created nil)
(kernelspec (first (ein:list-available-kernels url-or-port))))
(ein:notebooklist-new-notebook url-or-port kernelspec path
(lambda (&rest -ignore-)
(setq created t)))
(ein:testing-wait-until "ein:notebooklist-new-notebook"
(lambda () created)))
(prog1
(ein:testing-get-notebook-by-name url-or-port "Untitled.ipynb" path)
(ein:log 'debug "TESTING-GET-UNTITLED0-OR-CREATE end")))))
(defvar ein:notebooklist-after-open-hook nil)
(defadvice ein:notebooklist-url-retrieve-callback
(after ein:testing-notebooklist-url-retrieve-callback activate)
"Advice to add `ein:notebooklist-after-open-hook'."
(run-hooks 'ein:notebooklist-after-open-hook))
(defun ein:testing-delete-notebook (url-or-port notebook &optional path)
(ein:log 'debug "TESTING-DELETE-NOTEBOOK start")
(ein:notebooklist-open url-or-port (ein:$notebook-notebook-path notebook) t)
(ein:testing-wait-until "ein:notebooklist-open"
(lambda () (bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(ein:log 'debug "TESTING-DELETE-NOTEBOOK deleting notebook")
(ein:notebooklist-delete-notebook (ein:$notebook-notebook-path notebook)))
(ein:log 'debug "TESTING-DELETE-NOTEBOOK end"))
(ert-deftest ein:testing-jupyter-start-server ()
(ein:log 'verbose "ERT TESTING-JUPYTER-START-SERVER start")
(ein:testing-start-server)
(should (processp %ein:jupyter-server-session%))
(ein:log 'verbose "ERT TESTING-JUPYTER-START-SERVER end"))
(ert-deftest ein:testing-get-untitled0-or-create ()
(ein:log 'verbose "ERT TESTING-GET-UNTITLED0-OR-CREATE start")
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(should (equal (ein:$notebook-notebook-name ein:%notebook%)
"Untitled.ipynb"))))
(ein:log 'verbose "ERT TESTING-GET-UNTITLED0-OR-CREATE end"))
(ert-deftest ein:testing-delete-untitled0 ()
(ein:log 'verbose "----------------------------------")
(ein:log 'verbose "ERT TESTING-DELETE-UNTITLED0 start")
(ein:log 'debug "ERT TESTING-DELETE-UNTITLED0 creating notebook")
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:test-get-untitled0-or-create"
(lambda () (ein:aand notebook
(ein:$notebook-kernel it)
(ein:kernel-live-p it))))
(ein:log 'debug "ERT TESTING-DELETE-UNTITLED0 delete notebook")
(ein:testing-delete-notebook ein:testing-port notebook))
(ein:log 'debug
"ERT TESTING-DELETE-UNTITLED0 check that the notebook is deleted")
(let ((num-notebook
(length (ein:testing-get-notebook-by-name ein:testing-port
"Untitled.ipynb"
""))))
(should (= num-notebook 0)))
(ein:log 'debug "ERT TESTING-DELETE-UNTITLED0 end"))
(ert-deftest ein:notebook-execute-current-cell-simple ()
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "a = 100\na")
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
(ein:testing-wait-until "ein:worksheet-execute-cell"
(lambda () (not (slot-value cell 'running)))))
;; (message "%s" (buffer-string))
(save-excursion
(should (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
(should (search-forward "100" nil t))))))
(defun ein:testing-image-type (image)
"Return the type of IMAGE.
See the definition of `create-image' for how it works."
(assert (and (listp image) (cl-find 'image image :key #'car)) nil
"%S is not an image." image)
(plist-get (cdr (cl-find 'image image :key #'car)) :type))
(ert-deftest ein:notebook-execute-current-cell-pyout-image ()
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
;; Use IPython.core.display rather than IPython.display to
;; test it with older (< 0.13) IPython.
(insert (concat "from IPython.core.display import SVG\n"
(format "SVG(data=\"\"\"%s\"\"\")"
ein:testing-example-svg)))
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
;; It seems in this case, watching `:running' does not work
;; well sometimes. Probably "output reply" (iopub) comes
;; before "execute reply" in this case.
(ein:testing-wait-until "ein:worksheet-execute-cell"
(lambda () (slot-value cell 'outputs)))
;; This cell has only one input
(should (= (length (oref cell :outputs)) 1))
;; This output is a SVG image
(let ((out (nth 0 (oref cell :outputs))))
(should (equal (plist-get out :output_type) "execute_result"))
(should (plist-get out :svg))))
;; Check the actual output in the buffer:
(save-excursion
(should (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
(should (= (forward-line) 0))
(if (image-type-available-p 'svg)
(let ((image (get-text-property (point) 'display)))
(should (eq (ein:testing-image-type image) 'svg)))
(ein:log 'info
"Skipping image check as SVG image type is not available."))))))
(ert-deftest ein:notebook-execute-current-cell-stream ()
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "print('Hello')")
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
(ein:testing-wait-until "ein:worksheet-execute-cell"
(lambda () (not (oref cell :running)))))
(save-excursion
(should-not (search-forward-regexp "Out \\[[0-9]+\\]" nil t))
(should (search-forward-regexp "^Hello$" nil t))))))
(ert-deftest ein:notebook-execute-current-cell-question ()
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(insert "range?")
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))
(ein:testing-wait-until
"ein:worksheet-execute-cell"
(lambda () (not (oref cell :running)))))
(with-current-buffer (get-buffer (ein:$notebook-pager notebook))
(should (search-forward "Docstring:"))))))
(ert-deftest ein:notebook-request-help ()
(ein:testing-start-server)
(let ((notebook (ein:testing-get-untitled0-or-create ein:testing-port)))
(ein:testing-wait-until
"ein:testing-get-untitled0-or-create"
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(with-current-buffer (ein:notebook-buffer notebook)
(call-interactively #'ein:worksheet-insert-cell-below)
(let ((pager-name (ein:$notebook-pager ein:%notebook%)))
(ein:aif (get-buffer pager-name)
(kill-buffer it))
(insert "file")
(call-interactively #'ein:pytools-request-help)
;; Pager buffer will be created when got the response
(ein:testing-wait-until
"ein:pythools-request-help"
(lambda () (get-buffer pager-name)))
(with-current-buffer (get-buffer pager-name)
(should (search-forward "Docstring:")))))))