[slime-devel] REPL-to-regression-test made easy.

Vladimir Sedach vsedach at gmail.com
Mon Sep 25 00:44:37 UTC 2006

Hello everybody,

At Friday's inaugural Calgary Lisp user group meeting, Michael
Beauregard suggested a feature to SLIME that would let you take an
expression-result pair from REPL interaction and easily
add it as a new regression test to a test
suite. Here is some code that does something like that:

(defvar *slime-last-repl-input*)
(defvar *slime-last-repl-result*)
(defvar *slime-last-repl-input-matches-last-result* nil) ;; set to T if last
input expression returned normally and it is safe to save the input-result
pair as a unit test, nil otherwise

(defvar *slime-regression-test-file* nil "Pathname of file containing test
cases to which the next test case will be appended to.")

(defvar *slime-regression-test-framework* nil "The unit test framework
should be a function that takes as arguments the input and expected result
of the unit test as strings and produces an object that is suitable to be
serialized and appended to a file containing unit tests for that particular

(defun slime-MIT-regression-testing-framework (input result)
(princ "(deftest " (current-buffer))
(princ (gensym) (current-buffer))
(princ "(multiple-value-list " (current-buffer))
(princ input (current-buffer))
(princ ")" (current-buffer))
(princ result (current-buffer))
(princ ")" (current-buffer)))

(defun slime-save-last-input-as-regression-test ()
"When called, takes the last REPL expression sent to Lisp by SLIME and the
output it returns, and adds them as a unit test to the unit test file
specified by *slime-regression-test-file*. If the last REPL command resulted
in an error, does nothing. The specific brand of regression testing
framework you're using is controlled by *slime-regression-test-framework*."
(unless *slime-regression-test-file*
(call-interactively #'slime-set-regression-test-file))
(when *slime-last-repl-input-matches-last-result*
(let* ((res *slime-last-repl-result*)
(results (cons 'list (cond ((equal :values (car res)) (second res))
((equal :present (car res)) (mapcar #'car (second res)))
(t "nil")))))
(funcall *slime-regression-test-framework* *slime-last-repl-input* results))
(indent-region (point-min) (point-max) nil)
(append-to-file (point-min) (point-max) *slime-regression-test-file*))))

(defun slime-set-regression-test-file (file)
(interactive "FRegression test file: ")
(setf *slime-regression-test-file* file))

(defadvice slime-repl-eval-string (before regression-testing-string-capturer
"Get the string passed to slime-repl-eval-string before it gets mangled."
(setf *slime-last-repl-input* (copy-seq string)))

;;; need to redefine slime-repl-eval-string
(defun slime-repl-eval-string (string)
(slime-rex ()
((list 'swank:listener-eval string) (slime-lisp-package))
((:ok result)
;;; modified code be here
(setf *slime-last-repl-result* result
*slime-last-repl-input-matches-last-result* t)
;;; end modified code
(with-current-buffer (slime-output-buffer)
(slime-repl-insert-prompt result)))
;;; modified code be here
(setf *slime-last-repl-input-matches-last-result* nil)
;;; end modified code

Comments? I don't use regression testing tools very much myself, so the
first thing I want to ask, is this approach going to work for the commonly
used CL testing frameworks, or is something more flexible necessary? If this
were to become part of SLIME, the big question is, what key should
slime-save-last-input-as-regression-test be bound to? Having just the last
repl-expression-result pair is probably good enough, but is there anyone
wanting a history of expression-result pair interactions and a nice
colorful-buffer way of choosing which ones to save?

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/slime-devel/attachments/20060924/bdcc3ec9/attachment.html>

More information about the slime-devel mailing list