Merge pull request #525 from dickmao/already-open-bug

Cleanup save-before-quit logic, and polymode kill buffer bugfix
This commit is contained in:
John Miller 2019-05-11 07:31:33 -05:00 committed by GitHub
commit 7c7691c26d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 246 additions and 207 deletions

View file

@ -6,7 +6,9 @@ Fork the repo on github. Clone the fork to your home directory.
Install cask. Run `make dist` to ensure correct cask functionality.
Run `make test` to ensure a correct baseline. This locally replicates the travis ci build. You may need to install other software such as jupyter, R, matplotlib, etc.
Run `make test` to ensure a correct baseline.
Until we institute a virtualenv for the required testing software (jupyter, R, matplotlib, etc.), out-of-the-box `make test` remains problematic.
Remove the MELPA-installed EIN by deleting the package directory (on my system, it's `~/.emacs.d/elpa/ein-20190122.1341`) or running `M-x package-delete`.

View file

@ -41,6 +41,7 @@ autoloads:
clean:
cask clean-elc
rm -rf test/test-install
rm -rf log/*websocket*
.PHONY: dist-clean
dist-clean: clean

View file

@ -305,7 +305,6 @@ created via `ein:notebook-create-checkpoint`.
.. el:function:: ein:worksheet-execute-all-cell
.. el:function:: ein:worksheet-delete-cell
.. el:function:: ein:junk-rename
.. el:function:: ein:notebook-kill-all-buffers
.. el:function:: ein:iexec-mode
.. el:function:: ein:notebook-create-checkpoint
.. el:function:: ein:notebook-restore-to-checkpoint
@ -516,7 +515,6 @@ Notebook
.. el:variable:: ein:worksheet-enable-undo
.. el:variable:: ein:notebook-modes
.. el:variable:: ein:notebook-kill-buffer-ask
.. el:variable:: ein:notebook-querty-timeout-open
.. el:variable:: ein:notebook-querty-timeout-save
.. el:variable:: ein:cell-traceback-level

View file

@ -130,3 +130,27 @@ Scenario: kernel reconnect succeeds
And header says "Kernel requires reconnect \<ein:notebook-mode-map>\[ein:notebook-reconnect-session-command]"
And I clear log expr "ein:log-all-buffer-name"
And my reconnect is questioned
@exit
Scenario: Saving fails upon quit, need to consult user
Given new default notebook
When I type "import math"
And I wait for cell to execute
And I cannot save upon quit
@kill
Scenario: Assign variable, save, kill notebook buffer, get it back, check variable
Given new default notebook
When I type "import math"
And I press "RET"
And I type "b = math.pi"
And I press "RET"
And I wait for cell to execute
And I press "C-x C-s"
And I wait for the smoke to clear
And I kill buffer and reopen
And I press "C-c C-b"
And I type "b"
And I wait for cell to execute
Then I should see "3.1415"
And I press "C-x k"

View file

@ -49,6 +49,21 @@
(ein:kernel-reconnect-session (ein:$notebook-kernel ein:%notebook%)
(lambda (kernel session-p)
(should-not session-p))))))
(When "^I cannot save upon quit$"
(lambda ()
(let ((always-errback
(lambda (args)
(cl-destructuring-bind (notebook callback cbargs errback)
args
(list notebook errback nil errback)))))
(add-function :filter-args (symbol-function 'ein:notebook-save-notebook)
always-errback)
(cl-letf (((symbol-function 'y-or-n-p)
(lambda (prompt) (message "%s" prompt) t)))
(should (ein:notebook-opened-notebooks))
(ein:notebook-close-notebooks)
(Then "I should see message \"Some notebooks could not be saved. Exit anyway?\""))
(remove-function (symbol-function 'ein:notebook-save-notebook) always-errback))))
(When "I restart kernel$"
(lambda ()
@ -144,6 +159,11 @@
(ein:$notebook-notebook-name notebook))))
(switch-to-buffer buf-name)
(Then "I should be in buffer \"%s\"" buf-name))))))
(When "^I kill buffer and reopen$"
(lambda ()
(let ((name (ein:$notebook-notebook-name ein:%notebook%)))
(When "I press \"C-x k\"")
(And (format "old notebook \"%s\"" name)))))
(When "^I \\(finally \\)?stop the server\\(\\)$"
(lambda (final-p _workaround)

View file

@ -37,7 +37,7 @@
(if ein:%notebooklist%
(loop for notebook in (ein:notebook-opened-notebooks)
for path = (ein:$notebook-notebook-path notebook)
do (ein:notebook-kill-kernel-then-close-command notebook t)
do (ein:notebook-kill-kernel-then-close-command notebook)
do (loop repeat 8
until (not (ein:notebook-live-p notebook))
do (sleep-for 0 500)
@ -65,6 +65,7 @@
(setq ein:testing-dump-file-log (concat default-directory "log/ecukes.log"))
(setq ein:testing-dump-file-messages (concat default-directory "log/ecukes.messages"))
(setq ein:testing-dump-file-server (concat default-directory "log/ecukes.server"))
(setq ein:testing-dump-file-websocket (concat default-directory "log/ecukes.websocket"))
(setq ein:testing-dump-file-request (concat default-directory "log/ecukes.request"))
(setq org-confirm-babel-evaluate nil)
(setq transient-mark-mode t)

View file

@ -61,7 +61,6 @@
(defun ein:dev-reload ()
"Reload ein-*.el modules."
(interactive)
(ein:notebook-kill-all-buffers)
(makunbound 'ein:notebook-mode-map) ; so defvar works.
(load "ein-notebook") ; ... but make sure it will be defined first.
(ein:load-files "^ein-.*\\.el$"))

View file

@ -203,31 +203,11 @@ the log of the running jupyter server."
(interactive)
(ein:and-let* ((proc (ein:jupyter-server-process))
(_ok (or force (y-or-n-p "Stop server and close notebooks?"))))
(let ((unsaved (ein:notebook-opened-notebooks #'ein:notebook-modified-p))
(check-for-saved (make-hash-table :test #'equal)))
(when unsaved
(loop for nb in unsaved
for nb-name = (ein:$notebook-notebook-name nb)
when (y-or-n-p (format "Save %s?" nb-name))
do (progn
(setf (gethash nb-name check-for-saved) t)
(ein:notebook-save-notebook
nb
(lambda (name check-hash) (remhash name check-hash))
(list nb-name check-for-saved)))))
(loop for x upfrom 0 by 1
until (or (zerop (hash-table-count check-for-saved))
(> x 20))
do (sleep-for 0 500)))
(mapc #'ein:notebook-close (ein:notebook-opened-notebooks))
(ein:notebook-close-notebooks t)
(loop repeat 10
do (ein:query-gc-running-process-table)
when (zerop (hash-table-count ein:query-running-process-table))
return t
do (ein:query-running-process-table)
until (zerop (hash-table-count ein:query-running-process-table))
do (sleep-for 0 500))
;; Both (quit-process) and (delete-process) leaked child kernels, so signal
(if (eql system-type 'windows-nt)
(delete-process proc)

View file

@ -266,7 +266,7 @@ combo must match exactly these url/port you used format
`ein:notebooklist-login'."
(interactive (list
(ein:notebooklist-ask-url-or-port)
(ein:get-notebook-or-error)))
(ein:notebook--get-nb-or-error)))
(message "Updating server info and restarting kernel for notebooklist %s"
(ein:$notebook-notebook-name notebook))
(setf (ein:$notebook-url-or-port notebook) new-url-or-port)
@ -277,14 +277,11 @@ combo must match exactly these url/port you used format
(ein:$notebook-notebook-name notebook)))))
(defun ein:notebook-buffer (notebook)
"Return the buffer that is associated with NOTEBOOK."
;; FIXME: Find a better way to define notebook buffer!
;; For example, the last accessed buffer.
(let ((first-buffer
(lambda (ws-list)
(loop for ws in ws-list if (ein:worksheet-buffer ws) return it))))
(or (funcall first-buffer (ein:$notebook-worksheets notebook))
(funcall first-buffer (ein:$notebook-scratchsheets notebook)))))
"Return first buffer in NOTEBOOK's worksheets."
(loop for ws in (append (ein:$notebook-worksheets notebook)
(ein:$notebook-scratchsheets notebook))
if (ein:worksheet-buffer ws)
return it))
(defun ein:notebook-buffer-list (notebook)
"Return the buffers associated with NOTEBOOK's kernel.
@ -823,7 +820,7 @@ This is equivalent to do ``C-c`` in the console program."
(ein:$notebook-worksheets notebook))
(ein:notebook-save-notebook notebook callback cbargs)))
(defun ein:notebook-save-notebook (notebook &optional callback cbargs)
(defun ein:notebook-save-notebook (notebook &optional callback cbargs errback)
(unless (ein:notebook-buffer notebook)
(let ((buf (format ein:notebook-buffer-name-template
(ein:$notebook-url-or-port notebook)
@ -845,7 +842,7 @@ This is equivalent to do ``C-c`` in the console program."
#'ein:notebook-save-notebook-success
(list notebook callback cbargs)
#'ein:notebook-save-notebook-error
(list notebook))))
(list notebook errback))))
(defun ein:notebook-save-notebook-command ()
"Save the notebook."
@ -923,31 +920,79 @@ NAME is any non-empty string that does not contain '/' or '\\'.
(ein:$notebook-scratchsheets notebook)))
(ein:log 'info "Notebook renamed to %s." (ein:$content-name content)))
(defun ein:notebook-close (notebook)
"Close NOTEBOOK and kill its buffer."
(interactive (prog1 (list (ein:notebook--get-nb-or-error))
(or (ein:notebook-ask-before-kill-buffer)
(error "Quit"))))
(let ((ein:notebook-kill-buffer-ask nil))
;; Let `ein:notebook-kill-buffer-callback' do its job.
(mapc #'kill-buffer (ein:notebook-buffer-list notebook))))
(defmacro ein:notebook-avoid-recursion (&rest body)
`(let ((kill-buffer-query-functions
(remove-if (lambda (x) (eq 'ein:notebook-kill-buffer-query x))
kill-buffer-query-functions)))
,@body))
(defun ein:notebook-kill-kernel-then-close-command (notebook &optional force)
(defun ein:notebook-kill-notebook-buffers (notebook)
"Kill all of NOTEBOOK's buffers"
(mapc #'ein:notebook-kill-current-buffer (ein:notebook-buffer-list notebook)))
(defun ein:notebook-kill-current-buffer (buf)
"Kill BUF and avoid recursion in kill-buffer-query-functions"
(ein:notebook-avoid-recursion
(let ((notebook (buffer-local-value 'ein:%notebook% buf)))
(when (kill-buffer buf)
(ein:notebook-tidy-opened-notebooks notebook)))))
(defsubst ein:notebook-kill-buffer-query ()
(ein:aif (ein:get-notebook--notebook)
(let ((buf (or (buffer-base-buffer (current-buffer))
(current-buffer))))
(ein:notebook-ask-save it (apply-partially
#'ein:notebook-kill-current-buffer
buf))
;; don't kill buffer!
nil)
;; kill buffer!
t))
(defun ein:notebook-ask-save (notebook callback0)
(unless callback0
(setq callback0 #'ignore))
(if (ein:notebook-modified-p notebook)
(if (y-or-n-p (format "Save %s?" (ein:$notebook-notebook-name notebook)))
(lexical-let ((success-positive 0))
(add-function :before callback0 (lambda () (setq success-positive 1)))
(ein:notebook-save-notebook notebook callback0 nil
(lambda () (setq success-positive -1)))
(loop repeat 10
until (not (zerop success-positive))
do (sleep-for 0 200)
finally return (> success-positive 0)))
(when (ein:worksheet-p ein:%worksheet%)
(ein:worksheet-dont-save-cells ein:%worksheet%)) ;; TODO de-obfuscate
(funcall callback0)
t)
(funcall callback0)
t))
(defun ein:notebook-close (notebook &optional callback &rest cbargs)
(interactive (list (ein:notebook--get-nb-or-error)))
(lexical-let* ((notebook (or notebook (ein:notebook--get-nb-or-error)))
(callback0 (apply-partially #'ein:notebook-kill-notebook-buffers notebook)))
(when callback
(add-function :after callback0
(apply #'apply-partially callback cbargs)))
(ein:notebook-ask-save notebook callback0)))
(defun ein:notebook-kill-kernel-then-close-command (notebook)
"Kill kernel and then kill notebook buffer.
To close notebook without killing kernel, just close the buffer
as usual."
(interactive (list ein:%notebook%))
(when (or force (ein:notebook-ask-before-kill-buffer))
(interactive (list (ein:notebook--get-nb-or-error)))
(let ((kernel (ein:$notebook-kernel notebook))
(callback (apply-partially
(callback1 (apply-partially
(lambda (notebook* kernel)
(ein:notebook-close notebook*))
notebook)))
(if (ein:kernel-live-p kernel)
(ein:message-whir "Ending session"
(add-function :before callback done-callback)
(ein:kernel-delete-session kernel callback))
(funcall callback nil)))))
(add-function :before callback1 done-callback)
(ein:kernel-delete-session kernel callback1))
(funcall callback1 nil))))
(defun ein:fast-content-from-notebook (notebook)
"Quickly generate a basic content structure from notebook. This
@ -1201,8 +1246,7 @@ When used as a lisp function, delete worksheet WS from NOTEBOOk."
(setf (ein:$notebook-worksheets notebook)
(delq ws (ein:$notebook-worksheets notebook)))
(setf (ein:$notebook-dirty notebook) t)
(let ((ein:notebook-kill-buffer-ask nil))
(kill-buffer (ein:worksheet-buffer ws))))
(kill-buffer (ein:worksheet-buffer ws)))
(defun ein:notebook-worksheet-move-prev (notebook ws)
"Switch the current worksheet with the previous one."
@ -1243,7 +1287,7 @@ Scratch sheet is almost identical to worksheet. However, EIN
will not save the buffer. Use this buffer like of normal IPython
console. Note that you can always copy cells into the normal
worksheet to save result."
(interactive (list (ein:get-notebook-or-error)
(interactive (list (ein:notebook--get-nb-or-error)
current-prefix-arg
t))
(let ((ss (or (unless new
@ -1260,7 +1304,6 @@ worksheet to save result."
"A map: (URL-OR-PORT NOTEBOOK-ID) => notebook instance.")
(defun ein:notebook-get-opened-notebook (url-or-port path)
(ein:notebook-opened-notebooks) ;; garbage collects dead notebooks -- TODO refactor
(gethash (list url-or-port path) ein:notebook--opened-map))
(defun ein:notebook-get-opened-buffer (url-or-port path)
@ -1273,6 +1316,11 @@ worksheet to save result."
notebook
ein:notebook--opened-map))
(defun ein:notebook-tidy-opened-notebooks (notebook)
"Remove NOTEBOOK from ein:notebook--opened-map if it's not ein:notebook-live-p"
(unless (ein:notebook-live-p notebook)
(ein:notebook-remove-opened-notebook notebook)))
(defun ein:notebook-remove-opened-notebook (notebook)
(remhash (list (ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-path notebook))
@ -1283,11 +1331,7 @@ worksheet to save result."
If PREDICATE is given, notebooks are filtered by PREDICATE.
PREDICATE is called with each notebook and notebook is included
in the returned list only when PREDICATE returns non-nil value."
(let (notebooks)
(maphash (lambda (k n) (if (ein:notebook-live-p n)
(push n notebooks)
(remhash k ein:notebook--opened-map)))
ein:notebook--opened-map)
(let ((notebooks (hash-table-values ein:notebook--opened-map)))
(if predicate
(seq-filter predicate notebooks)
notebooks)))
@ -1321,10 +1365,6 @@ PREDICATE is called with the buffer name for each opened notebook."
;;; Predicate
(defun ein:notebook-buffer-p ()
"Return non-`nil' if current buffer is notebook buffer."
ein:%notebook%)
(defun ein:notebook-live-p (notebook)
"Return non-`nil' if NOTEBOOK has live buffer."
(buffer-live-p (ein:notebook-buffer notebook)))
@ -1482,8 +1522,7 @@ Use simple `python-mode' based notebook mode when MuMaMo is not installed::
'(("Save notebook" ein:notebook-save-notebook-command)
("Copy and rename notebook" ein:notebook-save-to-command)
("Rename notebook" ein:notebook-rename-command)
("Close notebook without saving"
ein:notebook-close)
("Close notebook" ein:notebook-close)
("Kill kernel then close notebook"
ein:notebook-kill-kernel-then-close-command))))
("Edit"
@ -1691,55 +1730,20 @@ the first argument and CBARGS as the rest of arguments."
(apply callback data cbargs)))
callback cbargs))))
;;; Buffer and kill hooks
(add-hook 'kill-buffer-query-functions 'ein:notebook-kill-buffer-query)
(defcustom ein:notebook-kill-buffer-ask t
"Whether EIN should ask before killing unsaved notebook buffer."
:type '(choice (const :tag "Yes" t)
(const :tag "No" nil))
:group 'ein)
(defun ein:notebook-close-notebooks (&optional blithely)
"Used in `ein:jupyter-server-stop' and `kill-emacs-query-functions' hook."
(ein:aif (ein:notebook-opened-notebooks)
(if (and (cl-notevery #'identity (mapcar #'ein:notebook-close it))
(not blithely))
(y-or-n-p "Some notebooks could not be saved. Exit anyway?")
t)
t))
;; -- `kill-buffer-query-functions'
(defun ein:notebook-ask-before-kill-buffer ()
"Return `nil' to prevent killing the notebook buffer.
Called via `kill-buffer-query-functions'."
(not (or (and ein:notebook-kill-buffer-ask
(ein:worksheet-p ein:%worksheet%) ; it's not `ein:scratchsheet'
(ein:notebook-modified-p)
(not (y-or-n-p
"This notebook has unsaved changes. Discard those changes?")))
(when (ein:worksheet-p ein:%worksheet%)
;; To make `ein:worksheet-save-cells' no-op.
(ein:worksheet-dont-save-cells ein:%worksheet%)
nil))))
(add-hook 'kill-emacs-query-functions 'ein:notebook-close-notebooks t)
(add-hook 'kill-buffer-query-functions 'ein:notebook-ask-before-kill-buffer)
;; -- `kill-emacs-query-functions'
(defun ein:notebook-ask-before-kill-emacs ()
"Return `nil' to prevent killing Emacs when unsaved notebook exists.
Called via `kill-emacs-query-functions'."
(condition-case err
(let ((unsaved (seq-filter #'ein:notebook-modified-p
(ein:notebook-opened-notebooks))))
(if (null unsaved)
t
(let ((answer
(y-or-n-p
(format "You have %s unsaved notebook(s). Discard changes?"
(length unsaved)))))
;; kill all unsaved buffers forcefully
(when answer
(mapc #'ein:notebook-close unsaved))
answer)))
((debug error)
(ein:log 'error "Got error: %S" err)
(y-or-n-p "Error while examine notebooks. Kill Emacs anyway? "))))
(add-hook 'kill-emacs-query-functions 'ein:notebook-ask-before-kill-emacs)
;; -- `kill-buffer-hook'
(defun ein:notebook-kill-buffer-callback ()
"Call notebook destructor. This function is called via `kill-buffer-hook'."
;; TODO - it remains a bug that neither `ein:notebook-kill-buffer-callback'
@ -1753,25 +1757,6 @@ Called via `kill-emacs-query-functions'."
"Add \"notebook destructor\" to `kill-buffer-hook'."
(add-hook 'kill-buffer-hook 'ein:notebook-kill-buffer-callback nil t))
;; Useful command to close notebooks.
(defun ein:notebook-kill-all-buffers ()
"Close all opened notebooks."
(interactive)
(let* ((notebooks (ein:notebook-opened-notebooks))
(unsaved (seq-filter #'ein:notebook-modified-p notebooks)))
(if notebooks
(if (y-or-n-p
(format (concat "You have %s opened notebook(s). "
(when unsaved
(format "%s are UNSAVED. " (length unsaved)))
"Really kill all of them?")
(length notebooks)))
(progn (ein:log 'info "Killing all notebook buffers...")
(mapc #'ein:notebook-close notebooks)
(ein:log 'info "Killing all notebook buffers... Done!"))
(ein:log 'info "Canceled to kill all notebooks."))
(ein:log 'info "No opened notebooks."))))
(if (boundp 'undo-tree-incompatible-major-modes)
(nconc undo-tree-incompatible-major-modes (list (ein:notebook-choose-mode))))

View file

@ -118,7 +118,7 @@ KEY, then call `request' with URL and SETTINGS. KEY is compared by
(with-local-quit
(when timeout
(setq settings (plist-put settings :timeout (/ timeout 1000.0))))
(loop do (ein:query-gc-running-process-table)
(loop do (ein:query-running-process-table)
for running = (hash-table-count ein:query-running-process-table)
until (< running ein:max-simultaneous-queries)
do (ein:log 'warn "ein:query-singleton-ajax: %d running processes"
@ -132,8 +132,8 @@ KEY, then call `request' with URL and SETTINGS. KEY is compared by
(puthash key response ein:query-running-process-table)
response)))
(defun ein:query-gc-running-process-table ()
"Garbage collect dead processes in `ein:query-running-process-table'."
(defun ein:query-running-process-table ()
"Keep track of unfinished curl requests."
(maphash
(lambda (key buffer)
(when (request-response-done-p buffer)

View file

@ -75,16 +75,28 @@
"Websocket gets its cookies using the url-cookie API, so we need to copy over
any cookies that are made and stored during the contents API calls via
emacs-request."
(let* ((parsed-url (url-generic-parse-url url))
(lexical-let*
((parsed-url (url-generic-parse-url url))
(host-port (if (url-port-if-non-default parsed-url)
(format "%s:%s" (url-host parsed-url) (url-port parsed-url))
(url-host parsed-url)))
(securep (string-match "^wss://" url))
(cookies (request-cookie-alist (url-host parsed-url) "/" securep))
(hub-cookies (request-cookie-alist (url-host parsed-url) "/hub/" securep))
(user-cookies (request-cookie-alist (url-host parsed-url) (ein:maybe-get-jhconn-user url) securep)))
(dolist (c (append cookies (append hub-cookies user-cookies)))
(ein:websocket-store-cookie c host-port (car (url-path-and-query parsed-url)) securep))))
(read-cookies-func (lambda (path)
(request-cookie-alist
(url-host parsed-url) path securep)))
(cookies (loop repeat 4
for cand = (mapcan read-cookies-func
`("/" "/hub/"
,(ein:maybe-get-jhconn-user url)))
until (cl-some (lambda (x) (string= "_xsrf" (car x))) cand)
do (ein:log 'info
"ein:websocket--prepare-cookies: no _xsrf among %s, retrying."
cand)
do (sleep-for 0 300)
finally return cand)))
(dolist (c cookies)
(ein:websocket-store-cookie c host-port
(car (url-path-and-query parsed-url)) securep))))
(defun ein:websocket (url kernel on-message on-close on-open)
(ein:websocket--prepare-cookies url)

View file

@ -212,7 +212,7 @@ TYPE can be 'body, nil."
(save-restriction
(widen)
(let ((range (pm-innermost-range
(or (when (car args) (funcall modifier (car args)))
(or (when (car args) (max (point-min) (funcall modifier (car args))))
(point)))))
(narrow-to-region (car range) (cdr range))
(apply f args)))))

View file

@ -46,11 +46,24 @@
(defun ein:testing-save-buffer (buffer-or-name file-name)
(when (and buffer-or-name (get-buffer buffer-or-name) file-name)
(with-current-buffer buffer-or-name
(write-region (point-min) (point-max) file-name))))
(let ((coding-system-for-write 'raw-text))
(write-region (point-min) (point-max) file-name)))))
(defun ein:testing-dump-logs ()
(ein:testing-save-buffer "*Messages*" ein:testing-dump-file-messages)
(ein:testing-save-buffer "*ein:jupyter-server*" ein:testing-dump-file-server)
(mapc (lambda (b)
(ein:and-let* ((bname (buffer-name b))
(prefix "kernels/")
(is-websocket (search "*websocket" bname))
(kernel-start (search prefix bname))
(sofar (subseq bname (+ kernel-start (length prefix))))
(kernel-end (search "/" sofar)))
(ein:testing-save-buffer
bname
(concat ein:testing-dump-file-websocket "."
(seq-take sofar kernel-end)))))
(buffer-list))
(ein:testing-save-buffer ein:log-all-buffer-name ein:testing-dump-file-log)
(ein:testing-save-buffer request-log-buffer-name ein:testing-dump-file-request))
@ -59,7 +72,7 @@
if I call this between links in a deferred chain. Adding a flush-queue."
(deferred:flush-queue!)
(ein:testing-wait-until (lambda ()
(ein:query-gc-running-process-table)
(ein:query-running-process-table)
(zerop (hash-table-count ein:query-running-process-table)))
nil ms interval t))
@ -124,6 +137,16 @@ is not run in batch mode before Emacs 24.1."
(add-hook 'kill-emacs-hook #'ein:testing-dump-logs)
(with-eval-after-load "ein-notebook"
;; if y-or-n-p isn't specially overridden, make it always "no"
(lexical-let ((original-y-or-n-p (symbol-function 'y-or-n-p)))
(add-function :around (symbol-function 'ein:notebook-ask-save)
(lambda (f &rest args)
(if (not (eq (symbol-function 'y-or-n-p) original-y-or-n-p))
(apply f args)
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
(apply f args)))))))
(provide 'ein-testing)
;;; ein-testing.el ends here

View file

@ -208,8 +208,7 @@ When NUM-OPEN = NUM-CLOSE, notebook should be closed."
(setq ss-list
(append ss-list (list (make-instance 'ein:scratchsheet)))))
;; Close worksheet
(let ((ws (car (ein:$notebook-worksheets notebook)))
(ein:notebook-kill-buffer-ask nil))
(let ((ws (car (ein:$notebook-worksheets notebook))))
(ein:notebook-close-worksheet notebook ws)
(kill-buffer (ein:worksheet-buffer ws)))
;; Make sure adding scratchsheet work.
@ -233,7 +232,6 @@ When NUM-OPEN = NUM-CLOSE, notebook should be closed."
(ert-deftest ein:notebook-close-scratchsheet/open-two-close-one ()
(ein:testing-notebook-close-scratchsheet-open-and-close 2 1))
;;; Insertion and deletion of cells
(ert-deftest ein:notebook-insert-cell-below-command-simple ()
@ -320,10 +318,7 @@ some text
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1))
(call-interactively #'ein:worksheet-kill-cell)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 0))
(flet ((y-or-n-p (&rest ignore) t)
(ein:notebook-del (&rest ignore)))
;; FIXME: are there anyway to skip confirmation?
(kill-buffer)))
(kill-buffer))
(with-current-buffer (ein:testing-notebook-make-empty "NB2")
(call-interactively #'ein:worksheet-yank-cell)
(should (equal (ein:worksheet-ncells ein:%worksheet%) 1)))))
@ -900,7 +895,7 @@ defined."
(defun ein:testing-notebook-data-assert-nb4-worksheet-contents (notebook &optional text)
(let* ((data (ein:notebook-to-json notebook))
(cells (assoc-default 'cells data #'eq)))
(cells (assoc-default 'cells data)))
(if text
(progn
(should (= (length cells) 1))
@ -945,9 +940,9 @@ defined."
;; Open scratch sheet.
(ein:notebook-scratchsheet-open notebook)
;; Discard a worksheet buffer
(should (ein:notebook-modified-p notebook))
(let (ein:notebook-kill-buffer-ask)
(kill-buffer buffer))
(should-not (kill-buffer buffer))
(ein:testing-wait-until (lambda ()
(not (buffer-live-p buffer))))
(should (ein:notebook-live-p notebook))
;; to-json should still work
(if (< (ein:$notebook-nbformat notebook) 4)
@ -961,8 +956,7 @@ defined."
(ert-deftest ein:notebook-kill-kernel-then-close-when-its-alive ()
(with-current-buffer (ein:testing-notebook-make-new)
(let ((buffer (current-buffer))
(notebook ein:%notebook%)
(ein:notebook-kill-buffer-ask nil))
(notebook ein:%notebook%))
(cl-letf (((symbol-function 'ein:kernel-live-p) (lambda (&rest args) t))
((symbol-function 'ein:kernel-delete-session) (lambda (kernel callback) (funcall callback kernel))))
(call-interactively #'ein:notebook-kill-kernel-then-close-command))
@ -972,8 +966,7 @@ defined."
(with-current-buffer (ein:testing-notebook-make-new)
(let ((buffer (current-buffer))
(notebook ein:%notebook%)
(kernel (ein:$notebook-kernel ein:%notebook%))
(ein:notebook-kill-buffer-ask nil))
(kernel (ein:$notebook-kernel ein:%notebook%)))
(mocker-let
((ein:kernel-live-p
(kernel)
@ -1210,7 +1203,6 @@ value of `ein:worksheet-enable-undo'."
(ert-deftest ein:notebook-ask-before-kill-emacs-simple ()
(let ((ein:notebook--opened-map (make-hash-table :test 'equal)))
(should (ein:notebook-ask-before-kill-emacs))
(with-current-buffer
(ein:testing-notebook-make-empty "Modified Notebook.ipynb")
(call-interactively #'ein:worksheet-insert-cell-below)
@ -1219,57 +1211,57 @@ value of `ein:worksheet-enable-undo'."
(ein:testing-notebook-make-empty "Saved Notebook.ipynb")
(ein:notebook-save-notebook-success ein:%notebook%)
(should-not (ein:notebook-modified-p)))
(flet ((y-or-n-p (&rest ignore) t)
(ein:notebook-del (&rest ignore)))
(kill-buffer
(ein:testing-notebook-make-empty "Killed Notebook.ipynb")))
(kill-buffer (ein:testing-notebook-make-empty "Killed Notebook.ipynb"))
(should (gethash `(,ein:testing-notebook-dummy-url "Modified Notebook.ipynb") ein:notebook--opened-map))
(should (gethash `(,ein:testing-notebook-dummy-url "Saved Notebook.ipynb") ein:notebook--opened-map))
(should (gethash `(,ein:testing-notebook-dummy-url "Killed Notebook.ipynb") ein:notebook--opened-map))
(should (= (hash-table-count ein:notebook--opened-map) 3))
(mocker-let ((y-or-n-p
(prompt)
((:input '("You have 1 unsaved notebook(s). Discard changes?")
:output t))))
(should (ein:notebook-ask-before-kill-emacs)))))
(should-not (gethash `(,ein:testing-notebook-dummy-url "Killed Notebook.ipynb") ein:notebook--opened-map))
(should (= (hash-table-count ein:notebook--opened-map) 2))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest args) nil)))
(ein:notebook-close-notebooks t)
(should-not (ein:notebook-opened-notebooks)))))
;;; Buffer and kill hooks
(ert-deftest ein:notebook-ask-before-kill-buffer/no-ein-buffer ()
(with-temp-buffer
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer)))))
(should (kill-buffer))))
(ert-deftest ein:notebook-ask-before-kill-buffer/new-notebook ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer)))))
(lexical-let ((buf (current-buffer)))
(should-not (kill-buffer buf))
(ein:testing-wait-until (lambda ()
(not (buffer-live-p buf)))))))
(ert-deftest ein:notebook-ask-before-kill-buffer/modified-notebook ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(lexical-let ((buffer (current-buffer)))
(call-interactively #'ein:worksheet-insert-cell-below)
(mocker-let ((y-or-n-p
(prompt)
((:input '("This notebook has unsaved changes. Discard those changes?")
:output t))))
(should (ein:notebook-ask-before-kill-buffer)))))
(should-not (kill-buffer buffer))
(ein:testing-wait-until (lambda ()
(not (buffer-live-p buffer)))))))
(ert-deftest ein:notebook-ask-before-kill-buffer/modified-scratchsheet ()
(with-current-buffer (ein:testing-make-notebook-with-outputs '(nil))
(lexical-let ((buf (current-buffer)))
(should (buffer-live-p buf))
(with-current-buffer (ein:worksheet-buffer
(ein:notebook-scratchsheet-open ein:%notebook%))
(lexical-let ((buf2 (current-buffer)))
(should-not (eq buf buf2))
(should (= (ein:worksheet-ncells ein:%worksheet%) 1))
(call-interactively #'ein:worksheet-insert-cell-below)
(should (= (ein:worksheet-ncells ein:%worksheet%) 2))
(should (ein:worksheet-modified-p ein:%worksheet%))
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer))))
(should-not (kill-buffer buf2))
(ein:testing-wait-until (lambda ()
(not (buffer-live-p buf2))))))
(should-not (ein:worksheet-modified-p ein:%worksheet%))
(mocker-let ((y-or-n-p (prompt) ()))
(should (ein:notebook-ask-before-kill-buffer)))))
(should-not (kill-buffer buf))
(ein:testing-wait-until (lambda ()
(not (buffer-live-p buf)))))))
;; Misc unit tests
(ert-deftest ein:notebook-test-notebook-name-simple ()

View file

@ -3,4 +3,5 @@
(ein:setq-if-not ein:testing-dump-file-log "./log/testein.log")
(ein:setq-if-not ein:testing-dump-file-messages "./log/testein.messages")
(setq message-log-max t)

View file

@ -18,6 +18,7 @@
(setq ein:testing-dump-file-log (concat default-directory "log/testfunc.log"))
(setq ein:testing-dump-file-messages (concat default-directory "log/testfunc.messages"))
(setq ein:testing-dump-file-server (concat default-directory "log/testfunc.server"))
(setq ein:testing-dump-file-websocket (concat default-directory "log/testfunc.websocket"))
(setq ein:testing-dump-file-request (concat default-directory "log/testfunc.request"))
(with-eval-after-load "python"
(setq python-indent-guess-indent-offset-verbose nil))

View file

@ -23,8 +23,8 @@ cask_install_or_reset() {
mv ./Cask.tmp ./Cask
set +x
fi
cask install || { rm -rf .cask && false; }
cask update
cask install </dev/null
cask update </dev/null
# travis cache
rsync -vazSHe ssh .cask $HOME/
}