;;; slime-tests.el --- Automated tests for slime.el
|
|
;;
|
|
;;;; License
|
|
;; Copyright (C) 2003 Eric Marsden, Luke Gorrie, Helmut Eller
|
|
;; Copyright (C) 2004,2005,2006 Luke Gorrie, Helmut Eller
|
|
;; Copyright (C) 2007,2008,2009 Helmut Eller, Tobias C. Rittweiler
|
|
;; Copyright (C) 2013
|
|
;;
|
|
;; For a detailed list of contributors, see the manual.
|
|
;;
|
|
;; This program is free software; you can redistribute it and/or
|
|
;; modify it under the terms of the GNU General Public License as
|
|
;; published by the Free Software Foundation; either version 2 of
|
|
;; the License, or (at your option) any later version.
|
|
;;
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
;;
|
|
;; You should have received a copy of the GNU General Public
|
|
;; License along with this program; if not, write to the Free
|
|
;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
|
;; MA 02111-1307, USA.
|
|
|
|
|
|
;;;; Tests
|
|
(require 'slime)
|
|
(require 'ert nil t)
|
|
(require 'ert "lib/ert" t) ;; look for bundled version for Emacs 23
|
|
(require 'cl-lib)
|
|
(require 'bytecomp) ; byte-compile-current-file
|
|
(eval-when-compile
|
|
(require 'cl)) ; lexical-let
|
|
|
|
(defun slime-shuffle-list (list)
|
|
(let* ((len (length list))
|
|
(taken (make-vector len nil))
|
|
(result (make-vector len nil)))
|
|
(dolist (e list)
|
|
(while (let ((i (random len)))
|
|
(cond ((aref taken i))
|
|
(t (aset taken i t)
|
|
(aset result i e)
|
|
nil)))))
|
|
(append result '())))
|
|
|
|
(defun slime-batch-test (&optional test-name randomize)
|
|
"Run the test suite in batch-mode.
|
|
Exits Emacs when finished. The exit code is the number of failed tests."
|
|
(interactive)
|
|
(let ((ert-debug-on-error nil)
|
|
(timeout 30)
|
|
(slime-background-message-function #'ignore))
|
|
(slime)
|
|
;; Block until we are up and running.
|
|
(lexical-let (timed-out)
|
|
(run-with-timer timeout nil
|
|
(lambda () (setq timed-out t)))
|
|
(while (not (slime-connected-p))
|
|
(sit-for 1)
|
|
(when timed-out
|
|
(when noninteractive
|
|
(kill-emacs 252)))))
|
|
(slime-sync-to-top-level 5)
|
|
(let* ((selector (if randomize
|
|
`(member ,@(slime-shuffle-list
|
|
(ert-select-tests (or test-name t) t)))
|
|
(or test-name t)))
|
|
(ert-fun (if noninteractive
|
|
'ert-run-tests-batch
|
|
'ert)))
|
|
(let ((stats (funcall ert-fun selector)))
|
|
(if noninteractive
|
|
(kill-emacs (ert-stats-completed-unexpected stats)))))))
|
|
|
|
(defun slime-skip-test (message)
|
|
;; ERT for Emacs 23 and earlier doesn't have `ert-skip'
|
|
(if (fboundp 'ert-skip)
|
|
(ert-skip message)
|
|
(message (concat "SKIPPING: " message))
|
|
(ert-pass)))
|
|
|
|
(defun slime-tests--undefine-all ()
|
|
(dolist (test (ert-select-tests t t))
|
|
(let* ((sym (ert-test-name test)))
|
|
(cl-assert (eq (get sym 'ert--test) test))
|
|
(cl-remprop sym 'ert--test))))
|
|
|
|
(slime-tests--undefine-all)
|
|
|
|
(eval-and-compile
|
|
(defun slime-tests-auto-tags ()
|
|
(append '(slime)
|
|
(let ((file-name (or load-file-name
|
|
byte-compile-current-file)))
|
|
(if (and file-name
|
|
(string-match "contrib/test/slime-\\(.*\\)\.elc?$"
|
|
file-name))
|
|
(list 'contrib (intern (match-string 1 file-name)))
|
|
'(core)))))
|
|
|
|
(defmacro define-slime-ert-test (name &rest args)
|
|
"Like `ert-deftest', but set tags automatically.
|
|
Also don't error if `ert.el' is missing."
|
|
(if (not (featurep 'ert))
|
|
(warn "No ert.el found: not defining test %s"
|
|
name)
|
|
(let* ((docstring (and (stringp (second args))
|
|
(second args)))
|
|
(args (if docstring
|
|
(cddr args)
|
|
(cdr args)))
|
|
(tags (slime-tests-auto-tags)))
|
|
`(ert-deftest ,name () ,(or docstring "No docstring for this test.")
|
|
:tags ',tags
|
|
,@args))))
|
|
|
|
(defun slime-test-ert-test-for (name input i doc body fails-for style fname)
|
|
`(define-slime-ert-test
|
|
,(intern (format "%s-%d" name i)) ()
|
|
,(format "For input %s, %s" (truncate-string-to-width
|
|
(format "%s" input)
|
|
15 nil nil 'ellipsis)
|
|
(replace-regexp-in-string "^.??\\(\\w+\\)"
|
|
(lambda (s) (downcase s))
|
|
doc
|
|
t))
|
|
,@(if fails-for
|
|
`(:expected-result '(satisfies
|
|
(lambda (result)
|
|
(ert-test-result-type-p
|
|
result
|
|
(if (member
|
|
(slime-lisp-implementation-name)
|
|
',fails-for)
|
|
:failed
|
|
:passed))))))
|
|
|
|
,@(when style
|
|
`((let ((style (slime-communication-style)))
|
|
(when (not (member style ',style))
|
|
(slime-skip-test (format "test not applicable for style %s"
|
|
style))))))
|
|
(apply #',fname ',input))))
|
|
|
|
(defmacro def-slime-test (name args doc inputs &rest body)
|
|
"Define a test case.
|
|
NAME ::= SYMBOL | (SYMBOL OPTION*) is a symbol naming the test.
|
|
OPTION ::= (:fails-for IMPLEMENTATION*) | (:style COMMUNICATION-STYLE*)
|
|
ARGS is a lambda-list.
|
|
DOC is a docstring.
|
|
INPUTS is a list of argument lists, each tested separately.
|
|
BODY is the test case. The body can use `slime-check' to test
|
|
conditions (assertions)."
|
|
(declare (debug (&define name sexp sexp sexp &rest def-form)))
|
|
(if (not (featurep 'ert))
|
|
(warn "No ert.el found: not defining test %s"
|
|
name)
|
|
`(progn
|
|
,@(cl-destructuring-bind (name &rest options)
|
|
(if (listp name) name (list name))
|
|
(let ((fname (intern (format "slime-test-%s" name))))
|
|
(cons `(defun ,fname ,args
|
|
(slime-sync-to-top-level 0.3)
|
|
,@body
|
|
(slime-sync-to-top-level 0.3))
|
|
(cl-loop for input in (eval inputs)
|
|
for i from 1
|
|
with fails-for = (cdr (assoc :fails-for options))
|
|
with style = (cdr (assoc :style options))
|
|
collect (slime-test-ert-test-for name
|
|
input
|
|
i
|
|
doc
|
|
body
|
|
fails-for
|
|
style
|
|
fname))))))))
|
|
|
|
(put 'def-slime-test 'lisp-indent-function 4)
|
|
|
|
(defmacro slime-check (check &rest body)
|
|
(declare (indent defun))
|
|
`(unless (progn ,@body)
|
|
(ert-fail ,(cl-etypecase check
|
|
(cons `(concat "Ooops, " ,(cons 'format check)))
|
|
(string `(concat "Check failed: " ,check))
|
|
(symbol `(concat "Check failed: " ,(symbol-name check)))))))
|
|
|
|
|
|
;;;;; Test case definitions
|
|
(defun slime-check-top-level () ;(&optional _test-name)
|
|
(accept-process-output nil 0.001)
|
|
(slime-check "At the top level (no debugging or pending RPCs)"
|
|
(slime-at-top-level-p)))
|
|
|
|
(defun slime-at-top-level-p ()
|
|
(and (not (sldb-get-default-buffer))
|
|
(null (slime-rex-continuations))))
|
|
|
|
(defun slime-wait-condition (name predicate timeout)
|
|
(let ((end (time-add (current-time) (seconds-to-time timeout))))
|
|
(while (not (funcall predicate))
|
|
(let ((now (current-time)))
|
|
(message "waiting for condition: %s [%s.%06d]" name
|
|
(format-time-string "%H:%M:%S" now) (third now)))
|
|
(cond ((time-less-p end (current-time))
|
|
(error "Timeout waiting for condition: %S" name))
|
|
(t
|
|
;; XXX if a process-filter enters a recursive-edit, we
|
|
;; hang forever
|
|
(accept-process-output nil 0.1))))))
|
|
|
|
(defun slime-sync-to-top-level (timeout)
|
|
(slime-wait-condition "top-level" #'slime-at-top-level-p timeout))
|
|
|
|
;; XXX: unused function
|
|
(defun slime-check-sldb-level (expected)
|
|
(let ((sldb-level (let ((sldb (sldb-get-default-buffer)))
|
|
(if sldb
|
|
(with-current-buffer sldb
|
|
sldb-level)))))
|
|
(slime-check ("SLDB level (%S) is %S" expected sldb-level)
|
|
(equal expected sldb-level))))
|
|
|
|
(defun slime-test-expect (_name expected actual &optional test)
|
|
(when (stringp expected) (setq expected (substring-no-properties expected)))
|
|
(when (stringp actual) (setq actual (substring-no-properties actual)))
|
|
(if test
|
|
(should (funcall test expected actual))
|
|
(should (equal expected actual))))
|
|
|
|
(defun sldb-level ()
|
|
(let ((sldb (sldb-get-default-buffer)))
|
|
(if sldb
|
|
(with-current-buffer sldb
|
|
sldb-level))))
|
|
|
|
(defun slime-sldb-level= (level)
|
|
(equal level (sldb-level)))
|
|
|
|
(eval-when-compile
|
|
(defvar slime-test-symbols
|
|
'(("foobar") ("foo@bar") ("@foobar") ("foobar@") ("\\@foobar")
|
|
("|asdf||foo||bar|")
|
|
("\\#<Foo@Bar>")
|
|
("\\(setf\\ car\\)"))))
|
|
|
|
(defun slime-check-symbol-at-point (prefix symbol suffix)
|
|
;; We test that `slime-symbol-at-point' works at every
|
|
;; character of the symbol name.
|
|
(with-temp-buffer
|
|
(lisp-mode)
|
|
(insert prefix)
|
|
(let ((start (point)))
|
|
(insert symbol suffix)
|
|
(dotimes (i (length symbol))
|
|
(goto-char (+ start i))
|
|
(slime-test-expect (format "Check `%s' (at %d)..."
|
|
(buffer-string) (point))
|
|
symbol
|
|
(slime-symbol-at-point)
|
|
#'equal)))))
|
|
|
|
|
|
|
|
(def-slime-test symbol-at-point.2 (sym)
|
|
"fancy symbol-name _not_ at BOB/EOB"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "(foo " sym " bar)"))
|
|
|
|
(def-slime-test symbol-at-point.3 (sym)
|
|
"fancy symbol-name with leading ,"
|
|
(remove-if (lambda (s) (eq (aref (car s) 0) ?@)) slime-test-symbols)
|
|
(slime-check-symbol-at-point "," sym ""))
|
|
|
|
(def-slime-test symbol-at-point.4 (sym)
|
|
"fancy symbol-name with leading ,@"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point ",@" sym ""))
|
|
|
|
(def-slime-test symbol-at-point.5 (sym)
|
|
"fancy symbol-name with leading `"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "`" sym ""))
|
|
|
|
(def-slime-test symbol-at-point.6 (sym)
|
|
"fancy symbol-name wrapped in ()"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "(" sym ")"))
|
|
|
|
(def-slime-test symbol-at-point.7 (sym)
|
|
"fancy symbol-name wrapped in #< {DEADBEEF}>"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "#<" sym " {DEADBEEF}>"))
|
|
|
|
;;(def-slime-test symbol-at-point.8 (sym)
|
|
;; "fancy symbol-name wrapped in #<>"
|
|
;; slime-test-symbols
|
|
;; (slime-check-symbol-at-point "#<" sym ">"))
|
|
|
|
(def-slime-test symbol-at-point.9 (sym)
|
|
"fancy symbol-name wrapped in #| ... |#"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "#|\n" sym "\n|#"))
|
|
|
|
(def-slime-test symbol-at-point.10 (sym)
|
|
"fancy symbol-name after #| )))(( |# (1)"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "#| )))(( #|\n" sym ""))
|
|
|
|
(def-slime-test symbol-at-point.11 (sym)
|
|
"fancy symbol-name after #| )))(( |# (2)"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "#| )))(( #|" sym ""))
|
|
|
|
(def-slime-test symbol-at-point.12 (sym)
|
|
"fancy symbol-name wrapped in \"...\""
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "\"\n" sym "\"\n"))
|
|
|
|
(def-slime-test symbol-at-point.13 (sym)
|
|
"fancy symbol-name wrapped in \" )))(( \" (1)"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "\" )))(( \"\n" sym ""))
|
|
|
|
(def-slime-test symbol-at-point.14 (sym)
|
|
"fancy symbol-name wrapped in \" )))(( \" (1)"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "\" )))(( \"" sym ""))
|
|
|
|
(def-slime-test symbol-at-point.15 (sym)
|
|
"symbol-at-point after #."
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "#." sym ""))
|
|
|
|
(def-slime-test symbol-at-point.16 (sym)
|
|
"symbol-at-point after #+"
|
|
slime-test-symbols
|
|
(slime-check-symbol-at-point "#+" sym ""))
|
|
|
|
|
|
(def-slime-test sexp-at-point.1 (string)
|
|
"symbol-at-point after #'"
|
|
'(("foo")
|
|
("#:foo")
|
|
("#'foo")
|
|
("#'(lambda (x) x)")
|
|
("()"))
|
|
(with-temp-buffer
|
|
(lisp-mode)
|
|
(insert string)
|
|
(goto-char (point-min))
|
|
(slime-test-expect (format "Check sexp `%s' (at %d)..."
|
|
(buffer-string) (point))
|
|
string
|
|
(slime-sexp-at-point)
|
|
#'equal)))
|
|
|
|
(def-slime-test narrowing ()
|
|
"Check that narrowing is properly sustained."
|
|
'()
|
|
(slime-check-top-level)
|
|
(let ((random-buffer-name (symbol-name (cl-gensym)))
|
|
(defun-pos) (tmpbuffer))
|
|
(with-temp-buffer
|
|
(dotimes (i 100) (insert (format ";;; %d. line\n" i)))
|
|
(setq tmpbuffer (current-buffer))
|
|
(setq defun-pos (point))
|
|
(insert (concat "(defun __foo__ (x y)" "\n"
|
|
" 'nothing)" "\n"))
|
|
(dotimes (i 100) (insert (format ";;; %d. line\n" (+ 100 i))))
|
|
(slime-check "Checking that newly created buffer is not narrowed."
|
|
(not (slime-buffer-narrowed-p)))
|
|
|
|
(goto-char defun-pos)
|
|
(narrow-to-defun)
|
|
(slime-check "Checking that narrowing succeeded."
|
|
(slime-buffer-narrowed-p))
|
|
|
|
(slime-with-popup-buffer (random-buffer-name)
|
|
(slime-check ("Checking that we're in Slime's temp buffer `%s'"
|
|
random-buffer-name)
|
|
(equal (buffer-name (current-buffer)) random-buffer-name)))
|
|
(with-current-buffer random-buffer-name
|
|
;; Notice that we cannot quit the buffer within the extent
|
|
;; of slime-with-output-to-temp-buffer.
|
|
(quit-window t))
|
|
(slime-check ("Checking that we've got back from `%s'"
|
|
random-buffer-name)
|
|
(and (eq (current-buffer) tmpbuffer)
|
|
(= (point) defun-pos)))
|
|
|
|
(slime-check "Checking that narrowing sustained \
|
|
after quitting Slime's temp buffer."
|
|
(slime-buffer-narrowed-p))
|
|
|
|
(let ((slime-buffer-package "SWANK")
|
|
(symbol '*buffer-package*))
|
|
(slime-edit-definition (symbol-name symbol))
|
|
(slime-check ("Checking that we've got M-. into swank.lisp. %S" symbol)
|
|
(string= (file-name-nondirectory (buffer-file-name))
|
|
"swank.lisp"))
|
|
(slime-pop-find-definition-stack)
|
|
(slime-check ("Checking that we've got back.")
|
|
(and (eq (current-buffer) tmpbuffer)
|
|
(= (point) defun-pos)))
|
|
|
|
(slime-check "Checking that narrowing sustained after M-,"
|
|
(slime-buffer-narrowed-p)))
|
|
))
|
|
(slime-check-top-level))
|
|
|
|
(defun slime-test--display-region-eval-arg (line window-height)
|
|
(cl-etypecase line
|
|
(number line)
|
|
(cons (slime-dcase line
|
|
((+h line)
|
|
(+ (slime-test--display-region-eval-arg line window-height)
|
|
window-height))
|
|
((-h line)
|
|
(- (slime-test--display-region-eval-arg line window-height)
|
|
window-height))))))
|
|
|
|
(defun slime-test--display-region-line-to-position (line window-height)
|
|
(let ((line (slime-test--display-region-eval-arg line window-height)))
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(forward-line (1- line))
|
|
(line-beginning-position))))
|
|
|
|
(def-slime-test display-region
|
|
(start end pos window-start expected-window-start expected-point)
|
|
"Test `slime-display-region'."
|
|
;; numbers are actually lines numbers
|
|
'(;; region visible, point in region
|
|
(2 4 3 1 1 3)
|
|
;; region visible, point visible but ouside region
|
|
(2 4 5 1 1 5)
|
|
;; end not visible, point at start
|
|
(2 (+h 2) 2 1 2 2)
|
|
;; start not visible, point at start
|
|
((+h 2) (+h 500) (+h 2) 1 (+h 2) (+h 2))
|
|
;; start not visible, point after end
|
|
((+h 2) (+h 500) (+h 6) 1 (+h 2) (+h 6))
|
|
;; end - start should be visible, point after end
|
|
((+h 2) (+h 7) (+h 10) 1 (-h (+h 7)) (+h 6))
|
|
;; region is window-height + 1 and ends with newline
|
|
((+h -2) (+h (+h -3)) (+h -2) 1 (+h -3) (+h -2))
|
|
(2 (+h 1) 3 1 1 3)
|
|
(2 (+h 0) 3 1 1 3)
|
|
(2 (+h -1) 3 1 1 3)
|
|
;; start and end are the beginning
|
|
(1 1 1 1 1 1)
|
|
;;
|
|
(1 (+h 1) (+h 22) (+h 20) 1 (+h 0))
|
|
)
|
|
(when noninteractive
|
|
(slime-skip-test "Can't test slime-display-region in batch mode"))
|
|
(with-temp-buffer
|
|
(dotimes (i 1000)
|
|
(insert (format "%09d\n" i)))
|
|
(let* ((win (display-buffer (current-buffer) t))
|
|
(wh (window-text-height win)))
|
|
(cl-macrolet ((l2p (l)
|
|
`(slime-test--display-region-line-to-position ,l wh)))
|
|
(select-window win)
|
|
(set-window-start win (l2p window-start))
|
|
(redisplay)
|
|
(goto-char (l2p pos))
|
|
(cl-assert (= (l2p window-start) (window-start win)))
|
|
(cl-assert (= (point) (l2p pos)))
|
|
(slime--display-region (l2p start) (l2p end))
|
|
(redisplay)
|
|
(cl-assert (= (l2p expected-window-start) (window-start)))
|
|
(cl-assert (= (l2p expected-point) (point)))
|
|
))))
|
|
|
|
(def-slime-test find-definition
|
|
(name buffer-package snippet)
|
|
"Find the definition of a function or macro in swank.lisp."
|
|
'(("start-server" "SWANK" "(defun start-server ")
|
|
("swank::start-server" "CL-USER" "(defun start-server ")
|
|
("swank:start-server" "CL-USER" "(defun start-server ")
|
|
("swank::connection" "CL-USER" "(defstruct (connection")
|
|
("swank::*emacs-connection*" "CL-USER" "(defvar \\*emacs-connection\\*")
|
|
)
|
|
(switch-to-buffer "*scratch*") ; not buffer of definition
|
|
(slime-check-top-level)
|
|
(let ((orig-buffer (current-buffer))
|
|
(orig-pos (point))
|
|
(enable-local-variables nil) ; don't get stuck on -*- eval: -*-
|
|
(slime-buffer-package buffer-package))
|
|
(slime-edit-definition name)
|
|
;; Postconditions
|
|
(slime-check ("Definition of `%S' is in swank.lisp." name)
|
|
(string= (file-name-nondirectory (buffer-file-name)) "swank.lisp"))
|
|
(slime-check ("Looking at '%s'." snippet) (looking-at snippet))
|
|
(slime-pop-find-definition-stack)
|
|
(slime-check "Returning from definition restores original buffer/position."
|
|
(and (eq orig-buffer (current-buffer))
|
|
(= orig-pos (point)))))
|
|
(slime-check-top-level))
|
|
|
|
(def-slime-test (find-definition.2 (:fails-for "allegro" "lispworks"))
|
|
(buffer-content buffer-package snippet)
|
|
"Check that we're able to find definitions even when
|
|
confronted with nasty #.-fu."
|
|
'(("#.(prog1 nil (defvar *foobar* 42))
|
|
|
|
(defun .foo. (x)
|
|
(+ x #.*foobar*))
|
|
|
|
#.(prog1 nil (makunbound '*foobar*))
|
|
"
|
|
"SWANK"
|
|
"[ \t]*(defun .foo. "
|
|
)
|
|
("#.(prog1 nil (defvar *foobar* 42))
|
|
|
|
;; some comment
|
|
(defun .foo. (x)
|
|
(+ x #.*foobar*))
|
|
|
|
#.(prog1 nil (makunbound '*foobar*))
|
|
"
|
|
"SWANK"
|
|
"[ \t]*(defun .foo. "
|
|
)
|
|
("(in-package swank)
|
|
(eval-when (:compile-toplevel) (defparameter *bar* 456))
|
|
(eval-when (:load-toplevel :execute) (makunbound '*bar*))
|
|
(defun bar () #.*bar*)
|
|
(defun .foo. () 123)"
|
|
"SWANK"
|
|
"[ \t]*(defun .foo. () 123)"))
|
|
(let ((slime-buffer-package buffer-package))
|
|
(with-temp-buffer
|
|
(insert buffer-content)
|
|
(slime-check-top-level)
|
|
(slime-eval
|
|
`(swank:compile-string-for-emacs
|
|
,buffer-content
|
|
,(buffer-name)
|
|
'((:position 0) (:line 1 1))
|
|
,nil
|
|
,nil))
|
|
(let ((bufname (buffer-name)))
|
|
(slime-edit-definition ".foo.")
|
|
(slime-check ("Definition of `.foo.' is in buffer `%s'." bufname)
|
|
(string= (buffer-name) bufname))
|
|
(slime-check "Definition now at point." (looking-at snippet))))))
|
|
|
|
(def-slime-test (find-definition.3
|
|
(:fails-for "abcl" "allegro" "clisp" "lispworks" "sbcl"
|
|
"ecl"))
|
|
(name source regexp)
|
|
"Extra tests for defstruct."
|
|
'(("swank::foo-struct"
|
|
"(progn
|
|
(defun foo-fun ())
|
|
(defstruct (foo-struct (:constructor nil) (:predicate nil)))
|
|
)"
|
|
"(defstruct (foo-struct"))
|
|
(switch-to-buffer "*scratch*")
|
|
(with-temp-buffer
|
|
(insert source)
|
|
(let ((slime-buffer-package "SWANK"))
|
|
(slime-eval
|
|
`(swank:compile-string-for-emacs
|
|
,source
|
|
,(buffer-name)
|
|
'((:position 0) (:line 1 1))
|
|
,nil
|
|
,nil)))
|
|
(let ((temp-buffer (current-buffer)))
|
|
(with-current-buffer "*scratch*"
|
|
(slime-edit-definition name)
|
|
(slime-check ("Definition of %S is in buffer `%s'."
|
|
name temp-buffer)
|
|
(eq (current-buffer) temp-buffer))
|
|
(slime-check "Definition now at point." (looking-at regexp)))
|
|
)))
|
|
|
|
(def-slime-test complete-symbol
|
|
(prefix expected-completions)
|
|
"Find the completions of a symbol-name prefix."
|
|
'(("cl:compile" ("cl:compile" "cl:compile-file" "cl:compile-file-pathname"
|
|
"cl:compiled-function" "cl:compiled-function-p"
|
|
"cl:compiler-macro" "cl:compiler-macro-function"))
|
|
("cl:foobar" ())
|
|
("swank::compile-file" ("swank::compile-file"
|
|
"swank::compile-file-for-emacs"
|
|
"swank::compile-file-if-needed"
|
|
"swank::compile-file-output"
|
|
"swank::compile-file-pathname"))
|
|
("cl:m-v-l" ()))
|
|
(let ((completions (slime-simple-completions prefix)))
|
|
(slime-test-expect "Completion set" expected-completions completions)))
|
|
|
|
(def-slime-test read-from-minibuffer
|
|
(input-keys expected-result)
|
|
"Test `slime-read-from-minibuffer' with INPUT-KEYS as events."
|
|
'(("( r e v e TAB SPC ' ( 1 SPC 2 SPC 3 ) ) RET"
|
|
"(reverse '(1 2 3))")
|
|
("( c l : c o n TAB s t a n t l TAB SPC 4 2 ) RET"
|
|
"(cl:constantly 42)"))
|
|
(when noninteractive
|
|
(slime-skip-test "Can't use unread-command-events in batch mode"))
|
|
(let ((keys (eval `(kbd ,input-keys)))) ; kbd is a macro in Emacs 23
|
|
(setq unread-command-events (listify-key-sequence keys)))
|
|
(let ((actual-result (slime-read-from-minibuffer "Test: ")))
|
|
(accept-process-output) ; run idle timers
|
|
(slime-test-expect "Completed string" expected-result actual-result)))
|
|
|
|
(def-slime-test arglist
|
|
;; N.B. Allegro apparently doesn't return the default values of
|
|
;; optional parameters. Thus the regexp in the start-server
|
|
;; expected value. In a perfect world we'd find a way to smooth
|
|
;; over this difference between implementations--perhaps by
|
|
;; convincing Franz to provide a function that does what we want.
|
|
(function-name expected-arglist)
|
|
"Lookup the argument list for FUNCTION-NAME.
|
|
Confirm that EXPECTED-ARGLIST is displayed."
|
|
'(("swank::operator-arglist" "(swank::operator-arglist name package)")
|
|
("swank::compute-backtrace" "(swank::compute-backtrace start end)")
|
|
("swank::emacs-connected" "(swank::emacs-connected)")
|
|
("swank::compile-string-for-emacs"
|
|
"(swank::compile-string-for-emacs \
|
|
string buffer position filename policy)")
|
|
("swank::connection.socket-io"
|
|
"(swank::connection.socket-io \
|
|
\\(struct\\(ure\\)?\\|object\\|instance\\|x\\|connection\\))")
|
|
("cl:lisp-implementation-type" "(cl:lisp-implementation-type)")
|
|
("cl:class-name"
|
|
"(cl:class-name \\(class\\|object\\|instance\\|structure\\))"))
|
|
(let ((arglist (slime-eval `(swank:operator-arglist ,function-name
|
|
"swank"))))
|
|
(slime-test-expect "Argument list is as expected"
|
|
expected-arglist (and arglist (downcase arglist))
|
|
(lambda (pattern arglist)
|
|
(and arglist (string-match pattern arglist))))))
|
|
|
|
(defun slime-test--compile-defun (program subform)
|
|
(slime-check-top-level)
|
|
(with-temp-buffer
|
|
(lisp-mode)
|
|
(insert program)
|
|
(let ((font-lock-verbose nil))
|
|
(setq slime-buffer-package ":swank")
|
|
(slime-compile-string (buffer-string) 1)
|
|
(setq slime-buffer-package ":cl-user")
|
|
(slime-sync-to-top-level 5)
|
|
(goto-char (point-max))
|
|
(slime-previous-note)
|
|
(slime-check error-location-correct
|
|
(equal (read (current-buffer)) subform))))
|
|
(slime-check-top-level))
|
|
|
|
(def-slime-test (compile-defun (:fails-for "allegro" "lispworks" "clisp"))
|
|
(program subform)
|
|
"Compile PROGRAM containing errors.
|
|
Confirm that SUBFORM is correctly located."
|
|
'(("(defun cl-user::foo () (cl-user::bar))" (cl-user::bar))
|
|
("(defun cl-user::foo ()
|
|
#\\space
|
|
;;Sdf
|
|
(cl-user::bar))"
|
|
(cl-user::bar))
|
|
("(defun cl-user::foo ()
|
|
#+(or)skipped
|
|
#| #||#
|
|
#||# |#
|
|
(cl-user::bar))"
|
|
(cl-user::bar))
|
|
("(defun cl-user::foo ()
|
|
\"\\\" bla bla \\\"\"
|
|
(cl-user::bar))"
|
|
(cl-user::bar))
|
|
("(defun cl-user::foo ()
|
|
#.*log-events*
|
|
(cl-user::bar))"
|
|
(cl-user::bar))
|
|
("#.'(defun x () (/ 1 0))
|
|
(defun foo ()
|
|
(cl-user::bar))
|
|
|
|
"
|
|
(cl-user::bar)))
|
|
(slime-test--compile-defun program subform))
|
|
|
|
;; This test ideally would be collapsed into the previous
|
|
;; compile-defun test, but only 1 case fails for ccl--and that's here
|
|
(def-slime-test (compile-defun-with-reader-conditionals
|
|
(:fails-for "allegro" "lispworks" "clisp" "ccl"))
|
|
(program subform)
|
|
"Compile PROGRAM containing errors.
|
|
Confirm that SUBFORM is correctly located."
|
|
'(("(defun foo ()
|
|
#+#.'(:and) (/ 1 0))"
|
|
(/ 1 0)))
|
|
(slime-test--compile-defun program subform))
|
|
|
|
;; SBCL used to pass this one but since they changed the
|
|
;; backquote/unquote reader it fails.
|
|
(def-slime-test (compile-defun-with-backquote
|
|
(:fails-for "allegro" "lispworks" "clisp" "sbcl"))
|
|
(program subform)
|
|
"Compile PROGRAM containing errors.
|
|
Confirm that SUBFORM is correctly located."
|
|
'(("(defun cl-user::foo ()
|
|
(list `(1 ,(random 10) 2 ,@(make-list (random 10)) 3
|
|
,(cl-user::bar))))"
|
|
(cl-user::bar)))
|
|
(slime-test--compile-defun program subform))
|
|
|
|
(def-slime-test (compile-file (:fails-for "allegro" "clisp"))
|
|
(string)
|
|
"Insert STRING in a file, and compile it."
|
|
`((,(pp-to-string '(defun foo () nil))))
|
|
(let ((filename "/tmp/slime-tmp-file.lisp"))
|
|
(with-temp-file filename
|
|
(insert string))
|
|
(let ((cell (cons nil nil)))
|
|
(slime-eval-async
|
|
`(swank:compile-file-for-emacs ,filename nil)
|
|
(slime-rcurry (lambda (result cell)
|
|
(setcar cell t)
|
|
(setcdr cell result))
|
|
cell))
|
|
(slime-wait-condition "Compilation finished" (lambda () (car cell))
|
|
0.5)
|
|
(let ((result (cdr cell)))
|
|
(slime-check "Compilation successfull"
|
|
(eq (slime-compilation-result.successp result) t))))))
|
|
|
|
(def-slime-test utf-8-source
|
|
(input output)
|
|
"Source code containing utf-8 should work"
|
|
(list (let* ((bytes "\343\201\212\343\201\257\343\202\210\343\201\206")
|
|
;;(encode-coding-string (string #x304a #x306f #x3088 #x3046)
|
|
;; 'utf-8)
|
|
(string (decode-coding-string bytes 'utf-8-unix)))
|
|
(assert (equal bytes (encode-coding-string string 'utf-8-unix)))
|
|
(list (concat "(defun cl-user::foo () \"" string "\")")
|
|
string)))
|
|
(slime-eval `(cl:eval (cl:read-from-string ,input)))
|
|
(slime-test-expect "Eval result correct"
|
|
output (slime-eval '(cl-user::foo)))
|
|
(let ((cell (cons nil nil)))
|
|
(let ((hook (slime-curry (lambda (cell &rest _) (setcar cell t)) cell)))
|
|
(add-hook 'slime-compilation-finished-hook hook)
|
|
(unwind-protect
|
|
(progn
|
|
(slime-compile-string input 0)
|
|
(slime-wait-condition "Compilation finished"
|
|
(lambda () (car cell))
|
|
0.5)
|
|
(slime-test-expect "Compile-string result correct"
|
|
output (slime-eval '(cl-user::foo))))
|
|
(remove-hook 'slime-compilation-finished-hook hook))
|
|
(let ((filename "/tmp/slime-tmp-file.lisp"))
|
|
(setcar cell nil)
|
|
(add-hook 'slime-compilation-finished-hook hook)
|
|
(unwind-protect
|
|
(with-temp-buffer
|
|
(when (fboundp 'set-buffer-multibyte)
|
|
(set-buffer-multibyte t))
|
|
(setq buffer-file-coding-system 'utf-8-unix)
|
|
(setq buffer-file-name filename)
|
|
(insert ";; -*- coding: utf-8-unix -*- \n")
|
|
(insert input)
|
|
(let ((coding-system-for-write 'utf-8-unix))
|
|
(write-region nil nil filename nil t))
|
|
(let ((slime-load-failed-fasl 'always))
|
|
(slime-compile-and-load-file)
|
|
(slime-wait-condition "Compilation finished"
|
|
(lambda () (car cell))
|
|
0.5))
|
|
(slime-test-expect "Compile-file result correct"
|
|
output (slime-eval '(cl-user::foo))))
|
|
(remove-hook 'slime-compilation-finished-hook hook)
|
|
(ignore-errors (delete-file filename)))))))
|
|
|
|
(def-slime-test async-eval-debugging (depth)
|
|
"Test recursive debugging of asynchronous evaluation requests."
|
|
'((1) (2) (3))
|
|
(lexical-let ((depth depth)
|
|
(debug-hook-max-depth 0))
|
|
(let ((debug-hook
|
|
(lambda ()
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(when (> sldb-level debug-hook-max-depth)
|
|
(setq debug-hook-max-depth sldb-level)
|
|
(if (= sldb-level depth)
|
|
;; We're at maximum recursion - time to unwind
|
|
(sldb-quit)
|
|
;; Going down - enter another recursive debug
|
|
;; Recursively debug.
|
|
(slime-eval-async '(error))))))))
|
|
(let ((sldb-hook (cons debug-hook sldb-hook)))
|
|
(slime-eval-async '(error))
|
|
(slime-sync-to-top-level 5)
|
|
(slime-check ("Maximum depth reached (%S) is %S."
|
|
debug-hook-max-depth depth)
|
|
(= debug-hook-max-depth depth))))))
|
|
|
|
(def-slime-test unwind-to-previous-sldb-level (level2 level1)
|
|
"Test recursive debugging and returning to lower SLDB levels."
|
|
'((2 1) (4 2))
|
|
(slime-check-top-level)
|
|
(lexical-let ((level2 level2)
|
|
(level1 level1)
|
|
(state 'enter)
|
|
(max-depth 0))
|
|
(let ((debug-hook
|
|
(lambda ()
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(setq max-depth (max sldb-level max-depth))
|
|
(ecase state
|
|
(enter
|
|
(cond ((= sldb-level level2)
|
|
(setq state 'leave)
|
|
(sldb-invoke-restart (sldb-first-abort-restart)))
|
|
(t
|
|
(slime-eval-async `(cl:aref cl:nil ,sldb-level)))))
|
|
(leave
|
|
(cond ((= sldb-level level1)
|
|
(setq state 'ok)
|
|
(sldb-quit))
|
|
(t
|
|
(sldb-invoke-restart (sldb-first-abort-restart))
|
|
))))))))
|
|
(let ((sldb-hook (cons debug-hook sldb-hook)))
|
|
(slime-eval-async `(cl:aref cl:nil 0))
|
|
(slime-sync-to-top-level 15)
|
|
(slime-check-top-level)
|
|
(slime-check ("Maximum depth reached (%S) is %S." max-depth level2)
|
|
(= max-depth level2))
|
|
(slime-check ("Final state reached.")
|
|
(eq state 'ok))))))
|
|
|
|
(defun sldb-first-abort-restart ()
|
|
(let ((case-fold-search t))
|
|
(cl-position-if (lambda (x) (string-match "abort" (car x)))
|
|
sldb-restarts)))
|
|
|
|
(def-slime-test loop-interrupt-quit
|
|
()
|
|
"Test interrupting a loop."
|
|
'(())
|
|
(slime-check-top-level)
|
|
(slime-eval-async '(cl:loop) (lambda (_) ) "CL-USER")
|
|
(accept-process-output nil 1)
|
|
(slime-check "In eval state." (slime-busy-p))
|
|
(slime-interrupt)
|
|
(slime-wait-condition "First interrupt" (lambda () (slime-sldb-level= 1)) 5)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-quit))
|
|
(slime-sync-to-top-level 5)
|
|
(slime-check-top-level))
|
|
|
|
(def-slime-test loop-interrupt-continue-interrupt-quit
|
|
()
|
|
"Test interrupting a previously interrupted but continued loop."
|
|
'(())
|
|
(slime-check-top-level)
|
|
(slime-eval-async '(cl:loop) (lambda (_) ) "CL-USER")
|
|
(sleep-for 1)
|
|
(slime-wait-condition "running" #'slime-busy-p 5)
|
|
(slime-interrupt)
|
|
(slime-wait-condition "First interrupt" (lambda () (slime-sldb-level= 1)) 5)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-continue))
|
|
(slime-wait-condition "running" (lambda ()
|
|
(and (slime-busy-p)
|
|
(not (sldb-get-default-buffer)))) 5)
|
|
(slime-interrupt)
|
|
(slime-wait-condition "Second interrupt" (lambda () (slime-sldb-level= 1)) 5)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-quit))
|
|
(slime-sync-to-top-level 5)
|
|
(slime-check-top-level))
|
|
|
|
(def-slime-test interactive-eval
|
|
()
|
|
"Test interactive eval and continuing from the debugger."
|
|
'(())
|
|
(slime-check-top-level)
|
|
(lexical-let ((done nil))
|
|
(let ((sldb-hook (lambda () (sldb-continue) (setq done t))))
|
|
(slime-interactive-eval
|
|
"(progn\
|
|
(cerror \"foo\" \"restart\")\
|
|
(cerror \"bar\" \"restart\")\
|
|
(+ 1 2))")
|
|
(while (not done) (accept-process-output))
|
|
(slime-sync-to-top-level 5)
|
|
(slime-check-top-level)
|
|
(unless noninteractive
|
|
(let ((message (current-message)))
|
|
(slime-check "Minibuffer contains: \"3\""
|
|
(equal "=> 3 (2 bits, #x3, #o3, #b11)" message)))))))
|
|
|
|
(def-slime-test report-condition-with-circular-list
|
|
(format-control format-argument)
|
|
"Test conditions involving circular lists."
|
|
'(("~a" "(let ((x (cons nil nil))) (setf (cdr x) x))")
|
|
("~a" "(let ((x (cons nil nil))) (setf (car x) x))")
|
|
("~a" "(let ((x (cons (make-string 100000 :initial-element #\\X) nil)))\
|
|
(setf (cdr x) x))"))
|
|
(slime-check-top-level)
|
|
(lexical-let ((done nil))
|
|
(let ((sldb-hook (lambda () (sldb-continue) (setq done t))))
|
|
(slime-interactive-eval
|
|
(format "(with-standard-io-syntax (cerror \"foo\" \"%s\" %s) (+ 1 2))"
|
|
format-control format-argument))
|
|
(while (not done) (accept-process-output))
|
|
(slime-sync-to-top-level 5)
|
|
(slime-check-top-level)
|
|
(unless noninteractive
|
|
(let ((message (current-message)))
|
|
(slime-check "Minibuffer contains: \"3\""
|
|
(equal "=> 3 (2 bits, #x3, #o3, #b11)" message)))))))
|
|
|
|
(def-slime-test interrupt-bubbling-idiot
|
|
()
|
|
"Test interrupting a loop that sends a lot of output to Emacs."
|
|
'(())
|
|
(accept-process-output nil 1)
|
|
(slime-check-top-level)
|
|
(slime-eval-async '(cl:loop :for i :from 0 :do (cl:progn (cl:print i)
|
|
(cl:finish-output)))
|
|
(lambda (_) )
|
|
"CL-USER")
|
|
(sleep-for 1)
|
|
(slime-interrupt)
|
|
(slime-wait-condition "Debugger visible"
|
|
(lambda ()
|
|
(and (slime-sldb-level= 1)
|
|
(get-buffer-window (sldb-get-default-buffer))))
|
|
30)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-quit))
|
|
(slime-sync-to-top-level 5))
|
|
|
|
(def-slime-test (interrupt-encode-message (:style :sigio))
|
|
()
|
|
"Test interrupt processing during swank::encode-message"
|
|
'(())
|
|
(slime-eval-async '(cl:loop :for i :from 0
|
|
:do (swank::background-message "foo ~d" i)))
|
|
(sleep-for 1)
|
|
(slime-eval-async '(cl:/ 1 0))
|
|
(slime-wait-condition "Debugger visible"
|
|
(lambda ()
|
|
(and (slime-sldb-level= 1)
|
|
(get-buffer-window (sldb-get-default-buffer))))
|
|
30)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-quit))
|
|
(slime-sync-to-top-level 5))
|
|
|
|
(def-slime-test inspector
|
|
(exp)
|
|
"Test basic inspector workingness."
|
|
'(((let ((h (make-hash-table)))
|
|
(loop for i below 10 do (setf (gethash i h) i))
|
|
h))
|
|
((make-array 10))
|
|
((make-list 10))
|
|
('cons)
|
|
(#'cons))
|
|
(slime-inspect (prin1-to-string exp))
|
|
(cl-assert (not (slime-inspector-visible-p)))
|
|
(slime-wait-condition "Inspector visible" #'slime-inspector-visible-p 5)
|
|
(with-current-buffer (window-buffer (selected-window))
|
|
(slime-inspector-quit))
|
|
(slime-wait-condition "Inspector closed"
|
|
(lambda () (not (slime-inspector-visible-p)))
|
|
5)
|
|
(slime-sync-to-top-level 1))
|
|
|
|
(defun slime-buffer-visible-p (name)
|
|
(let ((buffer (window-buffer (selected-window))))
|
|
(string-match name (buffer-name buffer))))
|
|
|
|
(defun slime-inspector-visible-p ()
|
|
(slime-buffer-visible-p (slime-buffer-name :inspector)))
|
|
|
|
(defun slime-execute-as-command (name)
|
|
"Execute `name' as if it was done by the user through the
|
|
Command Loop. Similiar to `call-interactively' but also pushes on
|
|
the buffer's undo-list."
|
|
(undo-boundary)
|
|
(call-interactively name))
|
|
|
|
(def-slime-test macroexpand
|
|
(macro-defs bufcontent expansion1 search-str expansion2)
|
|
"foo"
|
|
'((("(defmacro qwertz (&body body) `(list :qwertz ',body))"
|
|
"(defmacro yxcv (&body body) `(list :yxcv (qwertz ,@body)))")
|
|
"(yxcv :A :B :C)"
|
|
"(list :yxcv (qwertz :a :b :c))"
|
|
"(qwertz"
|
|
"(list :yxcv (list :qwertz '(:a :b :c)))"))
|
|
(slime-check-top-level)
|
|
(setq slime-buffer-package ":swank")
|
|
(with-temp-buffer
|
|
(lisp-mode)
|
|
(dolist (def macro-defs)
|
|
(slime-compile-string def 0)
|
|
(slime-sync-to-top-level 5))
|
|
(insert bufcontent)
|
|
(goto-char (point-min))
|
|
(slime-execute-as-command 'slime-macroexpand-1)
|
|
(slime-wait-condition "Macroexpansion buffer visible"
|
|
(lambda ()
|
|
(slime-buffer-visible-p
|
|
(slime-buffer-name :macroexpansion)))
|
|
5)
|
|
(with-current-buffer (get-buffer (slime-buffer-name :macroexpansion))
|
|
(slime-test-expect "Initial macroexpansion is correct"
|
|
expansion1
|
|
(downcase (buffer-string))
|
|
#'slime-test-macroexpansion=)
|
|
(search-forward search-str)
|
|
(backward-up-list)
|
|
(slime-execute-as-command 'slime-macroexpand-1-inplace)
|
|
(slime-sync-to-top-level 3)
|
|
(slime-test-expect "In-place macroexpansion is correct"
|
|
expansion2
|
|
(downcase (buffer-string))
|
|
#'slime-test-macroexpansion=)
|
|
(slime-execute-as-command 'slime-macroexpand-undo)
|
|
(slime-test-expect "Expansion after undo is correct"
|
|
expansion1
|
|
(downcase (buffer-string))
|
|
#'slime-test-macroexpansion=)))
|
|
(setq slime-buffer-package ":cl-user"))
|
|
|
|
(defun slime-test-macroexpansion= (string1 string2)
|
|
(let ((string1 (replace-regexp-in-string " *\n *" " " string1))
|
|
(string2 (replace-regexp-in-string " *\n *" " " string2)))
|
|
(equal string1 string2)))
|
|
|
|
(def-slime-test indentation (buffer-content point-markers)
|
|
"Check indentation update to work correctly."
|
|
'(("
|
|
\(in-package :swank)
|
|
|
|
\(defmacro with-lolipop (&body body)
|
|
`(progn ,@body))
|
|
|
|
\(defmacro lolipop (&body body)
|
|
`(progn ,@body))
|
|
|
|
\(with-lolipop
|
|
1
|
|
2
|
|
42)
|
|
|
|
\(lolipop
|
|
1
|
|
2
|
|
23)
|
|
"
|
|
("23" "42")))
|
|
(with-temp-buffer
|
|
(lisp-mode)
|
|
(slime-lisp-mode-hook)
|
|
(insert buffer-content)
|
|
(slime-compile-region (point-min) (point-max))
|
|
(slime-sync-to-top-level 3)
|
|
(slime-update-indentation)
|
|
(slime-sync-to-top-level 3)
|
|
(dolist (marker point-markers)
|
|
(search-backward marker)
|
|
(beginning-of-defun)
|
|
(indent-sexp))
|
|
(slime-test-expect "Correct buffer content"
|
|
buffer-content
|
|
(substring-no-properties (buffer-string)))))
|
|
|
|
(def-slime-test break
|
|
(times exp)
|
|
"Test whether BREAK invokes SLDB."
|
|
(let ((exp1 '(break)))
|
|
`((1 ,exp1) (2 ,exp1) (3 ,exp1)))
|
|
(accept-process-output nil 0.2)
|
|
(slime-check-top-level)
|
|
(slime-eval-async
|
|
`(cl:eval (cl:read-from-string
|
|
,(prin1-to-string `(dotimes (i ,times)
|
|
(unless (= i 0)
|
|
(swank::sleep-for 1))
|
|
,exp)))))
|
|
(dotimes (_i times)
|
|
(slime-wait-condition "Debugger visible"
|
|
(lambda ()
|
|
(and (slime-sldb-level= 1)
|
|
(get-buffer-window
|
|
(sldb-get-default-buffer))))
|
|
3)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-continue))
|
|
(slime-wait-condition "sldb closed"
|
|
(lambda () (not (sldb-get-default-buffer)))
|
|
0.5))
|
|
(slime-sync-to-top-level 1))
|
|
|
|
(def-slime-test (break2 (:fails-for "cmucl" "allegro"))
|
|
(times exp)
|
|
"Backends should arguably make sure that BREAK does not depend
|
|
on *DEBUGGER-HOOK*."
|
|
(let ((exp2
|
|
'(block outta
|
|
(let ((*debugger-hook* (lambda (c h) (return-from outta 42))))
|
|
(break)))))
|
|
`((1 ,exp2) (2 ,exp2) (3 ,exp2)))
|
|
(slime-test-break times exp))
|
|
|
|
(def-slime-test locally-bound-debugger-hook
|
|
()
|
|
"Test that binding *DEBUGGER-HOOK* locally works properly."
|
|
'(())
|
|
(accept-process-output nil 1)
|
|
(slime-check-top-level)
|
|
(slime-compile-string
|
|
(prin1-to-string `(defun cl-user::quux ()
|
|
(block outta
|
|
(let ((*debugger-hook*
|
|
(lambda (c hook)
|
|
(declare (ignore c hook))
|
|
(return-from outta 42))))
|
|
(error "FOO")))))
|
|
0)
|
|
(slime-sync-to-top-level 2)
|
|
(slime-eval-async '(cl-user::quux))
|
|
;; FIXME: slime-wait-condition returns immediately if the test returns true
|
|
(slime-wait-condition "Checking that Debugger does not popup"
|
|
(lambda ()
|
|
(not (sldb-get-default-buffer)))
|
|
3)
|
|
(slime-sync-to-top-level 5))
|
|
|
|
(def-slime-test end-of-file
|
|
(expr)
|
|
"Signalling END-OF-FILE should invoke the debugger."
|
|
'(((cl:error 'cl:end-of-file))
|
|
((cl:read-from-string "")))
|
|
(let ((value (slime-eval
|
|
`(cl:let ((condition nil))
|
|
(cl:with-simple-restart
|
|
(cl:continue "continue")
|
|
(cl:let ((cl:*debugger-hook*
|
|
(cl:lambda (c h)
|
|
(cl:setq condition c)
|
|
(cl:continue))))
|
|
,expr))
|
|
(cl:if (cl:typep condition 'cl:end-of-file) t)))))
|
|
(slime-test-expect "Debugger invoked" t value)))
|
|
|
|
(def-slime-test interrupt-at-toplevel
|
|
()
|
|
"Let's see what happens if we send a user interrupt at toplevel."
|
|
'(())
|
|
(slime-check-top-level)
|
|
(unless (and (eq (slime-communication-style) :spawn)
|
|
(not (featurep 'slime-repl)))
|
|
(slime-interrupt)
|
|
(slime-wait-condition
|
|
"Debugger visible"
|
|
(lambda ()
|
|
(and (slime-sldb-level= 1)
|
|
(get-buffer-window (sldb-get-default-buffer))))
|
|
5)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-quit))
|
|
(slime-sync-to-top-level 5)))
|
|
|
|
(def-slime-test interrupt-in-debugger (interrupts continues)
|
|
"Let's see what happens if we interrupt the debugger.
|
|
INTERRUPTS ... number of nested interrupts
|
|
CONTINUES ... how often the continue restart should be invoked"
|
|
'((1 0) (2 1) (4 2))
|
|
(slime-check "No debugger" (not (sldb-get-default-buffer)))
|
|
(when (and (eq (slime-communication-style) :spawn)
|
|
(not (featurep 'slime-repl)))
|
|
(slime-eval-async '(swank::without-slime-interrupts
|
|
(swank::receive)))
|
|
(sit-for 0.2))
|
|
(dotimes (i interrupts)
|
|
(slime-interrupt)
|
|
(let ((level (1+ i)))
|
|
(slime-wait-condition (format "Debug level %d reachend" level)
|
|
(lambda () (equal (sldb-level) level))
|
|
2)))
|
|
(dotimes (i continues)
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-continue))
|
|
(let ((level (- interrupts (1+ i))))
|
|
(slime-wait-condition (format "Return to debug level %d" level)
|
|
(lambda () (equal (sldb-level) level))
|
|
2)))
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-quit))
|
|
(slime-sync-to-top-level 1))
|
|
|
|
(def-slime-test flow-control
|
|
(n delay interrupts)
|
|
"Let Lisp produce output faster than Emacs can consume it."
|
|
`((400 0.03 3))
|
|
(when noninteractive
|
|
(slime-skip-test "test is currently unstable"))
|
|
(slime-check "No debugger" (not (sldb-get-default-buffer)))
|
|
(slime-eval-async `(swank:flow-control-test ,n ,delay))
|
|
(sleep-for 0.2)
|
|
(dotimes (_i interrupts)
|
|
(slime-interrupt)
|
|
(slime-wait-condition "In debugger" (lambda () (slime-sldb-level= 1)) 5)
|
|
(slime-check "In debugger" (slime-sldb-level= 1))
|
|
(with-current-buffer (sldb-get-default-buffer)
|
|
(sldb-continue))
|
|
(slime-wait-condition "No debugger" (lambda () (slime-sldb-level= nil)) 3)
|
|
(slime-check "Debugger closed" (slime-sldb-level= nil)))
|
|
(slime-sync-to-top-level 8))
|
|
|
|
(def-slime-test sbcl-world-lock
|
|
(n delay)
|
|
"Print something from *MACROEXPAND-HOOK*.
|
|
In SBCL, the compiler grabs a lock which can be problematic because
|
|
no method dispatch code can be generated for other threads.
|
|
This test will fail more likely before dispatch caches are warmed up."
|
|
'((10 0.03)
|
|
;;((cl:+ swank::send-counter-limit 10) 0.03)
|
|
)
|
|
(slime-test-expect "no error"
|
|
123
|
|
(slime-eval
|
|
`(cl:let ((cl:*macroexpand-hook*
|
|
(cl:lambda (fun form env)
|
|
(swank:flow-control-test ,n ,delay)
|
|
(cl:funcall fun form env))))
|
|
(cl:eval '(cl:macrolet ((foo () 123))
|
|
(foo)))))))
|
|
|
|
(def-slime-test (disconnect-one-connection (:style :spawn)) ()
|
|
"`slime-disconnect' should disconnect only the current connection"
|
|
'(())
|
|
(let ((connection-count (length slime-net-processes))
|
|
(old-connection slime-default-connection)
|
|
(slime-connected-hook nil))
|
|
(unwind-protect
|
|
(let ((slime-dispatching-connection
|
|
(slime-connect "localhost"
|
|
;; Here we assume that the request will
|
|
;; be evaluated in its own thread.
|
|
(slime-eval `(swank:create-server
|
|
:port 0 ; use random port
|
|
:style :spawn
|
|
:dont-close nil)))))
|
|
(slime-sync-to-top-level 3)
|
|
(slime-disconnect)
|
|
(slime-test-expect "Number of connections must remane the same"
|
|
connection-count
|
|
(length slime-net-processes)))
|
|
(slime-select-connection old-connection))))
|
|
|
|
(def-slime-test disconnect-and-reconnect
|
|
()
|
|
"Close the connetion.
|
|
Confirm that the subprocess continues gracefully.
|
|
Reconnect afterwards."
|
|
'(())
|
|
(slime-check-top-level)
|
|
(let* ((c (slime-connection))
|
|
(p (slime-inferior-process c)))
|
|
(with-current-buffer (process-buffer p)
|
|
(erase-buffer))
|
|
(delete-process c)
|
|
(assert (equal (process-status c) 'closed) nil "Connection not closed")
|
|
(accept-process-output nil 0.1)
|
|
(assert (equal (process-status p) 'run) nil "Subprocess not running")
|
|
(with-current-buffer (process-buffer p)
|
|
(assert (< (buffer-size) 500) nil "Unusual output"))
|
|
(slime-inferior-connect p (slime-inferior-lisp-args p))
|
|
(lexical-let ((hook nil) (p p))
|
|
(setq hook (lambda ()
|
|
(slime-test-expect
|
|
"We are connected again" p (slime-inferior-process))
|
|
(remove-hook 'slime-connected-hook hook)))
|
|
(add-hook 'slime-connected-hook hook)
|
|
(slime-wait-condition "Lisp restarted"
|
|
(lambda ()
|
|
(not (member hook slime-connected-hook)))
|
|
5))))
|
|
|
|
|
|
;;;; SLIME-loading tests that launch separate Emacsen
|
|
;;;;
|
|
(cl-defun slime-test-recipe-test-for (&key preflight
|
|
takeoff
|
|
landing)
|
|
(let ((success nil)
|
|
(test-file (make-temp-file "slime-recipe-" nil ".el"))
|
|
(test-forms
|
|
`((require 'cl)
|
|
(labels
|
|
((die
|
|
(reason &optional more)
|
|
(princ reason)
|
|
(terpri)
|
|
(and more (pp more))
|
|
(kill-emacs 254)))
|
|
(condition-case err
|
|
(progn ,@preflight)
|
|
(error
|
|
(die "Unexpected error running preflight forms"
|
|
err)))
|
|
(add-hook
|
|
'slime-connected-hook
|
|
#'(lambda ()
|
|
(condition-case err
|
|
(progn
|
|
,@landing
|
|
(kill-emacs 0))
|
|
(error
|
|
(die "Unexpected error running landing forms"
|
|
err))))
|
|
t)
|
|
(condition-case err
|
|
(progn
|
|
,@takeoff
|
|
,(when (null landing) '(kill-emacs 0)))
|
|
(error
|
|
(die "Unexpected error running takeoff forms"
|
|
err)))
|
|
(with-timeout
|
|
(20
|
|
(die "Timeout waiting for recipe test to finish."
|
|
takeoff))
|
|
(while t (sit-for 1)))))))
|
|
(unwind-protect
|
|
(progn
|
|
(with-temp-buffer
|
|
(mapc #'insert (mapcar #'pp-to-string test-forms))
|
|
(write-file test-file))
|
|
(with-temp-buffer
|
|
(let ((retval
|
|
(call-process (concat invocation-directory invocation-name)
|
|
nil (list t nil) nil
|
|
"-Q" "--batch"
|
|
"-l" test-file)))
|
|
(unless (= 0 retval)
|
|
(ert-fail (buffer-substring
|
|
(+ (goto-char (point-min))
|
|
(skip-chars-forward " \t\n"))
|
|
(+ (goto-char (point-max))
|
|
(skip-chars-backward " \t\n")))))))
|
|
(setq success t))
|
|
(if success (delete-file test-file)
|
|
(message "Test failed: keeping %s for inspection" test-file)))))
|
|
|
|
(define-slime-ert-test readme-recipe ()
|
|
"Test the README.md's autoload recipe."
|
|
(slime-test-recipe-test-for
|
|
:preflight `((add-to-list 'load-path ,slime-path)
|
|
(require 'slime-autoloads)
|
|
(setq inferior-lisp-program ,inferior-lisp-program)
|
|
(setq slime-contribs '(slime-fancy)))
|
|
:takeoff `((call-interactively 'slime))
|
|
:landing `((unless (and (featurep 'slime-repl)
|
|
(find 'swank-repl slime-required-modules))
|
|
(die "slime-repl not loaded properly"))
|
|
(with-current-buffer (slime-repl-buffer)
|
|
(unless (and (string-match "^; +SLIME" (buffer-string))
|
|
(string-match "CL-USER> *$" (buffer-string)))
|
|
(die "REPL prompt not properly setup"
|
|
(buffer-substring-no-properties (point-min)
|
|
(point-max))))))))
|
|
|
|
(define-slime-ert-test traditional-recipe ()
|
|
"Test the README.md's traditional recipe."
|
|
(slime-test-recipe-test-for
|
|
:preflight `((add-to-list 'load-path ,slime-path)
|
|
(require 'slime)
|
|
(setq inferior-lisp-program ,inferior-lisp-program)
|
|
(slime-setup '(slime-fancy)))
|
|
:takeoff `((call-interactively 'slime))
|
|
:landing `((unless (and (featurep 'slime-repl)
|
|
(find 'swank-repl slime-required-modules))
|
|
(die "slime-repl not loaded properly"))
|
|
(with-current-buffer (slime-repl-buffer)
|
|
(unless (and (string-match "^; +SLIME" (buffer-string))
|
|
(string-match "CL-USER> *$" (buffer-string)))
|
|
(die "REPL prompt not properly setup"
|
|
(buffer-substring-no-properties (point-min)
|
|
(point-max))))))))
|
|
|
|
(define-slime-ert-test readme-recipe-autoload-on-lisp-visit ()
|
|
"Test more autoload bits in README.md's installation recipe."
|
|
(slime-test-recipe-test-for
|
|
:preflight `((add-to-list 'load-path ,slime-path)
|
|
(require 'slime-autoloads))
|
|
:takeoff `((if (featurep 'slime)
|
|
(die "Didn't expect SLIME to be loaded so early!"))
|
|
(find-file ,(make-temp-file "slime-lisp-source-file" nil
|
|
".lisp"))
|
|
(unless (featurep 'slime)
|
|
(die "Expected SLIME to be fully loaded by now")))))
|
|
|
|
(defun slime-test-eval-now (string)
|
|
(second (slime-eval `(swank:eval-and-grab-output ,string))))
|
|
|
|
(def-slime-test (slime-recompile-all-xrefs (:fails-for "cmucl")) ()
|
|
"Test recompilation of all references within an xref buffer."
|
|
'(())
|
|
(let* ((cell (cons nil nil))
|
|
(hook (slime-curry (lambda (cell &rest _) (setcar cell t)) cell))
|
|
(filename (make-temp-file "slime-recompile-all-xrefs" nil ".lisp")))
|
|
(add-hook 'slime-compilation-finished-hook hook)
|
|
(unwind-protect
|
|
(with-temp-file filename
|
|
(set-visited-file-name filename)
|
|
(slime-test-eval-now "(defparameter swank::*.var.* nil)")
|
|
(insert "(in-package :swank)
|
|
(defun .fn1. ())
|
|
(defun .fn2. () (.fn1.) #.*.var.*)
|
|
(defun .fn3. () (.fn1.) #.*.var.*)")
|
|
(save-buffer)
|
|
(slime-compile-and-load-file)
|
|
(slime-wait-condition "Compilation finished"
|
|
(lambda () (car cell))
|
|
0.5)
|
|
(slime-test-eval-now "(setq *.var.* t)")
|
|
(setcar cell nil)
|
|
(slime-xref :calls ".fn1."
|
|
(lambda (&rest args)
|
|
(apply #'slime-show-xrefs args)
|
|
(setcar cell t)))
|
|
(slime-wait-condition "Xrefs computed and displayed"
|
|
(lambda () (car cell))
|
|
0.5)
|
|
(setcar cell nil)
|
|
(with-current-buffer slime-xref-last-buffer
|
|
(slime-recompile-all-xrefs)
|
|
(slime-wait-condition "Compilation finished"
|
|
(lambda () (car cell))
|
|
0.5))
|
|
(should (cl-equalp (list (slime-test-eval-now "(.fn2.)")
|
|
(slime-test-eval-now "(.fn3.)"))
|
|
'("T" "T"))))
|
|
(remove-hook 'slime-compilation-finished-hook hook)
|
|
(when slime-xref-last-buffer
|
|
(kill-buffer slime-xref-last-buffer)))))
|
|
|
|
(provide 'slime-tests)
|