aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--src/legacy/README.org175
-rw-r--r--src/legacy/package.lisp14
-rw-r--r--src/legacy/testiere.lisp115
-rw-r--r--src/legacy/testiere.lisp~115
-rw-r--r--src/package.lisp1
-rw-r--r--src/testiere.lisp63
-rw-r--r--testiere-examples.asd9
-rw-r--r--testiere.asd6
9 files changed, 80 insertions, 423 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8fcc9d2
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+ 1.3.1
+ -----
+
+ - Removed trivia dependency
+
diff --git a/src/legacy/README.org b/src/legacy/README.org
deleted file mode 100644
index 755a885..0000000
--- a/src/legacy/README.org
+++ /dev/null
@@ -1,175 +0,0 @@
-A [[https://en.wiktionary.org/wiki/testiere][testiere]] is armor for the head of a horse and ~testiere~ is armor
-for the head of your ~defun~ forms.
-
-* Testiere
-
-With ~testiere~ you can program in an interactive TDD-like
-fashion. Tests are included at the top of a ~defun/t~ form. When you
-recompile your functions interactively, the tests are run. If any
-fail, you are dropped into a debugger where you can decide to revert
-the definition to the last known working version, or you can choose to
-unbind it altogether.
-
-The system supports mocking and stubbing in your tests, so that you
-can, e.g. test the system in different dynamic contexts or by mocking
-network request functions.
-
-Here is an example:
-
-#+begin_src lisp
-
-(defun/t sum-3 (x y &key (z 10))
- "Sums three numbers, Z has a default value of 10"
- :tests
- (:program some-test-function)
- (= (1 2) 13) ; (sum-3 1 2) == 13
- (= (1 2 :z 3) 6) ; (sum-3 1 2 :z 3) == 6
- (:outputp (0 0) ; tests that (sum-3 0 0) passes the predicate
- (lambda (result) (= 10 result)))
- (:fails ; ensures that (sum-3 "strings" "ain't" :z "numbers") fails
- ("strings" "ain't" :z "numbers"))
- :end
- (+ x y z))
-
-#+end_src
-
-In the above, a function ~sum-3~ is defined with five embedded
-tests. The test specification syntax is detailed below. If any of the
-tests fail, the function will not be redefined and you will drop into
-the debugger, which asks you how you'd like to proceed.
-
-The approach to TDD-like development taking by ~testiere~ may not be
-appropriate to all circumstances, but it is good for interactive
-development of interactive applications (😉) whose "main loop"
-involves a good sized collection of unit-testable functions.
-
-** Test Specification
-
-There are a few kinds of tests available.
-
-*** For the Impatient, Just Use =:program= Tests
-
-Most users will probably benefit from the ~:program~ style test. Here
-is a quick example:
-
-#+begin_src lisp
-
-(defun test-fibble ()
- (assert (= 13 (fibble 1 2))))
-
-(defun/t fibble (x y &key (z 10))
- "Adds three numbers, one of which defaults to 10."
- :tests
- (:program test-fibble)
- :end
- (+ x y z))
-
-#+end_src
-
-In the above test, we insist that the ~test-fibble~ function not
-signal an error condition in order for ~fibble~ to be successfully
-(re)compiled.
-
-*** Basic Test Specifications
-
-A test suite is a list of forms that appear between ~:tests~ and
-~:end~ in the body of a ~defun/t~ form. The test suite must appear
-after any optional docstring and before the function body actually
-begins.
-
-A catalog of test form specifications follows.
-
-**** Comparator Test Specifications
-
-: (comparator (&rest args...) value)
-
-The ~comparator~ should be the name of a binary predicate (like ~<~ or
-~eql~). These tests proceed by calling ~(comparator (apply my-fun args) value)~
-If the comparison fails, an error condition is signaled.
-
-Amending the above example, we include a comparator test:
-
-
-#+begin_src lisp
-(defun/t fibble (x y &key (z 10))
- "Adds three numbers, one of which defaults to 10."
- :tests
- (:program test-fibble)
- (= (0 0 :z 30) 30) ; (assert (= (fibble 0 0 :z 30) 30))
- :end
- (+ x y z))
-
-#+end_src
-
-**** Other Test Specifications
-
-Every other form appearing in a test suite is a list that starts with
-a keyword.
-
-- ~(:program FUNCTION-NAME ARGS...)~ runs a function named
- FUNCTION-NAME with arguments ARGS. This function is meant to act as
- a test suite for the function being defined with defun/t. It may
- call that function and ASSERT things about it.
-- ~(:outputp (..ARGS...) PREDICATE)~ asserts that the output passes
- the one-argument predicate.
-- ~(:afterp (...ARGS...) THUNK)~ asserts that the thunk should return
- non-nil after the function has run. Good for testing values of
- dynamic variables that the function might interact with.
-- ~(:fails (...ARGS...))~ asserts that the function will produce an
- error with the given arguments.
-- ~(:signals (...ARGS...) CONDITION)~ where ~CONDITION~ is the name of
- a condition. Asserts that the function will signal a condition of
- the supplied type when called with the provided arguments.
-
-
-*** Mocking and Stubbing
-
-The following test forms allow for the running of tests inside a
-context in which certain functions or global values are bound:
-
-Binding variables looks like
-
-: (:let LET-BINDINGS TESTS)
-
-and are useful for binding dynamic variables for use during a set of
-tests.
-
-For example
-
-#+begin_src lisp
-
- (defvar *count*)
-
- (defun/t increment-count ()
- "Increments the *count* variable."
- :tests
- (:let ((*count* 4))
- (:afterp () (lambda () (= *count* 5))) ; 5 after the first call
- (= () 6) ; 6 after the second
- (:outputp () (lambda (x) (= x 7)))) ; and 7 after the third
- :end
- (incf *count*))
-#+end_src
-
-The ~:with-stubs~ form is similar, except that it binds temporary
-values to functions that might be called by the form in
-questions. Useful for mocking.
-
-#+begin_src lisp
-
-
- (defun just-a-function ()
- (print "Just a function."))
-
- (defun/t call-just-a-function ()
- "Calls JUST-A-FUNCTION."
- :tests
- (:with-stubs ((just-a-function () (print "TEMP JUST-A-FUNCTION.")))
- (equal () "TEMP JUST-A-FUNCTION."))
- :end
- (just-a-function))
-
-#+end_src
-
-In the above, the temporary redefinition of ~JUST-A-FUNCTION~ is used.
-
diff --git a/src/legacy/package.lisp b/src/legacy/package.lisp
deleted file mode 100644
index 3724728..0000000
--- a/src/legacy/package.lisp
+++ /dev/null
@@ -1,14 +0,0 @@
-;;;; package.lisp
-
-(defpackage #:testiere.legacy
- (:use #:cl)
- (:import-from #:trivia #:match)
- (:export #:defun/t #:with-stubs #:with-stub))
-
-
-
-
-
-
-
-
diff --git a/src/legacy/testiere.lisp b/src/legacy/testiere.lisp
deleted file mode 100644
index 010b12d..0000000
--- a/src/legacy/testiere.lisp
+++ /dev/null
@@ -1,115 +0,0 @@
-;;;; testiere.lisp
-
-(in-package #:testiere.legacy)
-
-(defun build-test (name spec)
- (match spec
- ((list :fails inputs)
- `(assert
- (handler-case (progn (,name ,@inputs) nil)
- (error (e) (declare (ignore e)) t))))
-
- ((list :signals inputs condition)
- `(assert
- (handler-case (progn (,name ,@inputs) nil)
- (,condition (c) (declare (ignore c)) t)
- (condition (c) (declare (ignore c)) nil))))
-
- ((list* :program function-name args)
- `(when (fboundp ',function-name)
- (funcall ',function-name ,@args)))
-
- ((list* :with-stubs redefs more-specs)
- (let* ((assoc-vars
- (loop for (stub-name . more) in redefs
- collect (list stub-name (gensym (symbol-name stub-name)))))
- (cache-binding-forms
- (loop for (stub-name tmp-var) in assoc-vars
- collect `(,tmp-var (fdefinition ',stub-name))))
- (redef-forms
- (loop for (stub-name lambda-list . body) in redefs
- collect `(setf (fdefinition ',stub-name)
- (function (lambda ,lambda-list ,@body)))))
- (clean-up
- (loop for (stub-name . more) in redefs
- for binding = (assoc stub-name assoc-vars)
- collect `(setf (fdefinition ',stub-name) ,(second binding)))))
- `(let ,cache-binding-forms
- (unwind-protect
- (progn
- ,@redef-forms
- ,@(mapcar (lambda (s) (build-test name s)) more-specs))
- ,@clean-up))))
- ((list* :let bindings more-specs)
- `(let ,bindings
- ,@(mapcar (lambda (s) (build-test name s)) more-specs)))
- ((list :afterp inputs thunk-test)
- `(progn (,name ,@inputs)
- (assert (funcall ,thunk-test))))
- ((list :outputp inputs output-test)
- `(assert (funcall ,output-test (,name ,@inputs))))
- ((list comparator-function inputs expected-output)
- `(assert (,comparator-function (,name ,@inputs) ,expected-output)))))
-
-(defun extract-tests (name body)
- (let ((end-pos (position :end body))
- (start-pos (position :tests body)))
- (when (and end-pos start-pos)
- (let ((specs (subseq body (1+ start-pos) end-pos))
- (before (subseq body 0 start-pos))
- (after (nthcdr (1+ end-pos) body)))
- (list (mapcar (lambda (spec) (build-test name spec)) specs)
- (append before after))))))
-
-(defmacro with-stub ((name lambda-list &body body) &body forms)
- "Runs forms in a context where NAME is temporarily rebound to a
-different function. If NAME is not FBOUNDP then it is temporarily
-defined."
- (let ((cached (gensym)))
- `(let ((,cached
- (when (fboundp ',name)
- (fdefinition ',name))))
- (unwind-protect
- (progn
- (setf (fdefinition ',name)
- (lambda ,lambda-list ,@body))
- ,@forms)
- (if ,cached
- (setf (fdefinition ',name)
- ,cached)
- (fmakunbound ',name))))))
-
-(defmacro with-stubs (redefinitions &body forms)
- "Like WITH-STUB, but REDEFINITIONS is a list of (NAME LAMBDA-LIST
-. BODY) list, suitable for defining a function."
- (loop
- with inner = `(progn ,@forms)
- for (name lambda-list . body) in (reverse redefinitions)
- do (setf inner `(with-stub (,name ,lambda-list ,@body)
- ,inner))
- finally (return inner)))
-
-
-(defmacro defun/t (name lambda-list &body body)
- "Like regular DEFUN, but with embedded unit tests. If those tests
- would fail, the function fails to be defined. "
- (destructuring-bind (tests function-body) (extract-tests name body)
- (let ((cached (gensym)))
- `(let ((,cached
- (when (fboundp ',name)
- (fdefinition ',name))))
- (restart-case
- (eval-when (:compile-toplevel :load-toplevel :execute)
- (progn
- (defun ,name ,lambda-list ,@function-body)
- ,@tests))
- (make-unbound () (fmakunbound ',name))
- (revert-to-last-good-version ()
- (if ,cached
- (setf (symbol-function ',name)
- ,cached)
- (fmakunbound ',name))))))))
-
-
-
-
diff --git a/src/legacy/testiere.lisp~ b/src/legacy/testiere.lisp~
deleted file mode 100644
index b606b9f..0000000
--- a/src/legacy/testiere.lisp~
+++ /dev/null
@@ -1,115 +0,0 @@
-;;;; testiere.lisp
-
-(in-package #:testiere)
-
-(defun build-test (name spec)
- (match spec
- ((list :fails inputs)
- `(assert
- (handler-case (progn (,name ,@inputs) nil)
- (error (e) (declare (ignore e)) t))))
-
- ((list :signals inputs condition)
- `(assert
- (handler-case (progn (,name ,@inputs) nil)
- (,condition (c) (declare (ignore c)) t)
- (condition (c) (declare (ignore c)) nil))))
-
- ((list* :program function-name args)
- `(when (fboundp ',function-name)
- (funcall ',function-name ,@args)))
-
- ((list* :with-stubs redefs more-specs)
- (let* ((assoc-vars
- (loop for (stub-name . more) in redefs
- collect (list stub-name (gensym (symbol-name stub-name)))))
- (cache-binding-forms
- (loop for (stub-name tmp-var) in assoc-vars
- collect `(,tmp-var (fdefinition ',stub-name))))
- (redef-forms
- (loop for (stub-name lambda-list . body) in redefs
- collect `(setf (fdefinition ',stub-name)
- (function (lambda ,lambda-list ,@body)))))
- (clean-up
- (loop for (stub-name . more) in redefs
- for binding = (assoc stub-name assoc-vars)
- collect `(setf (fdefinition ',stub-name) ,(second binding)))))
- `(let ,cache-binding-forms
- (unwind-protect
- (progn
- ,@redef-forms
- ,@(mapcar (lambda (s) (build-test name s)) more-specs))
- ,@clean-up))))
- ((list* :let bindings more-specs)
- `(let ,bindings
- ,@(mapcar (lambda (s) (build-test name s)) more-specs)))
- ((list :afterp inputs thunk-test)
- `(progn (,name ,@inputs)
- (assert (funcall ,thunk-test))))
- ((list :outputp inputs output-test)
- `(assert (funcall ,output-test (,name ,@inputs))))
- ((list comparator-function inputs expected-output)
- `(assert (,comparator-function (,name ,@inputs) ,expected-output)))))
-
-(defun extract-tests (name body)
- (let ((end-pos (position :end body))
- (start-pos (position :tests body)))
- (when (and end-pos start-pos)
- (let ((specs (subseq body (1+ start-pos) end-pos))
- (before (subseq body 0 start-pos))
- (after (nthcdr (1+ end-pos) body)))
- (list (mapcar (lambda (spec) (build-test name spec)) specs)
- (append before after))))))
-
-(defmacro with-stub ((name lambda-list &body body) &body forms)
- "Runs forms in a context where NAME is temporarily rebound to a
-different function. If NAME is not FBOUNDP then it is temporarily
-defined."
- (let ((cached (gensym)))
- `(let ((,cached
- (when (fboundp ',name)
- (fdefinition ',name))))
- (unwind-protect
- (progn
- (setf (fdefinition ',name)
- (lambda ,lambda-list ,@body))
- ,@forms)
- (if ,cached
- (setf (fdefinition ',name)
- ,cached)
- (fmakunbound ',name))))))
-
-(defmacro with-stubs (redefinitions &body forms)
- "Like WITH-STUB, but REDEFINITIONS is a list of (NAME LAMBDA-LIST
-. BODY) list, suitable for defining a function."
- (loop
- with inner = `(progn ,@forms)
- for (name lambda-list . body) in (reverse redefinitions)
- do (setf inner `(with-stub (,name ,lambda-list ,@body)
- ,inner))
- finally (return inner)))
-
-
-(defmacro defun/t (name lambda-list &body body)
- "Like regular DEFUN, but with embedded unit tests. If those tests
- would fail, the function fails to be defined. "
- (destructuring-bind (tests function-body) (extract-tests name body)
- (let ((cached (gensym)))
- `(let ((,cached
- (when (fboundp ',name)
- (fdefinition ',name))))
- (restart-case
- (eval-when (:compile-toplevel :load-toplevel :execute)
- (progn
- (defun ,name ,lambda-list ,@function-body)
- ,@tests))
- (make-unbound () (fmakunbound ',name))
- (revert-to-last-good-version ()
- (if ,cached
- (setf (symbol-function ',name)
- ,cached)
- (fmakunbound ',name))))))))
-
-
-
-
diff --git a/src/package.lisp b/src/package.lisp
index ce58b63..ec5b5fe 100644
--- a/src/package.lisp
+++ b/src/package.lisp
@@ -2,7 +2,6 @@
(defpackage #:testiere
(:use #:cl)
- (:import-from #:trivia #:ematch)
(:export
#:on
#:off
diff --git a/src/testiere.lisp b/src/testiere.lisp
index 451d886..5255ed3 100644
--- a/src/testiere.lisp
+++ b/src/testiere.lisp
@@ -138,7 +138,68 @@ restarts to try when tests fail."
(defun expand-test-forms (key forms)
(loop :for form :in forms :collect (expand-test-form key form)))
+
(defun expand-test-form (key form)
+ (assert (and (listp form) form) (form) "Form must be a non-null list, not ~s" form)
+ (case (first form)
+ (:fails
+ `(unless (handler-case (progn ,(second form) nil)
+ (error () t))
+ (failed ',key "~s should have signalled an error but did not."
+ ',(second form))))
+ (:signals
+ `(unless (handler-case (progn ,(third form))
+ (,(second form) () t)
+ (condition () nil))
+ (failed ',key "~s should have signalled ~s but did not."
+ ',(third form)
+ ',(second form))))
+
+ (:funcall
+ (let ((err (gensym "ERROR-")))
+ `(handler-case (funcall ,(second form) ,@(cddr form))
+ (error (,err) (failed ',key "~s signalled an error: ~s"
+ '(funcall ,(second form) ,@(cddr form))
+ ,err)))))
+
+ (:with-defuns
+ (assert (listp (second form))
+ (form)
+ "stubbed defuns must be a list alá labels, not ~s"
+ (second form))
+ (expand-defun-stubs key (second form) (cddr form)))
+
+ (:with-generic
+ (assert (listp (third form)) (form)
+ "stubbed generic must be a list as per defgeneric bodies, not ~s"
+ (third form))
+ (expand-generic-stub key (second form) (third form) (cdddr form)))
+
+ (:let `(let ,(second form) ,@(expand-test-forms key (cddr form))))
+
+ (:do
+ (let ((err (gensym "ERROR-")))
+ `(handler-case ,(second form)
+ (error (,err)
+ (failed ',key "Side effecting code ~s signalled an error ~s"
+ ',(second form) ,err)))))
+
+ (:is
+ `(unless ,(second form)
+ (failed ',key "~s returned NIL" ',(second form))))
+
+ (t
+ (destructuring-bind (comparator a b) form
+ (let ((left (gensym "LEFT-"))
+ (right (gensym "RIGHT-"))
+ (pred-result (gensym "RESULT-")))
+ `(let* ((,left ,a)
+ (,right ,b)
+ (,pred-result (,comparator ,left ,right)))
+ (unless ,pred-result
+ (failed ',key "~s is NIL" ',form))))))))
+
+#+off(defun expand-test-form (key form)
(ematch form
((list :fails failing-form)
`(unless (handler-case (progn ,failing-form nil)
@@ -254,3 +315,5 @@ restarts to try when tests fail."
+
+
diff --git a/testiere-examples.asd b/testiere-examples.asd
new file mode 100644
index 0000000..2f919cf
--- /dev/null
+++ b/testiere-examples.asd
@@ -0,0 +1,9 @@
+(asdf:defsystem #:testiere-examples
+ :description "A demo of testiere"
+ :author "Colin <colin@cicadas.surf>"
+ :license "GPLv3"
+ :version "0.0.1"
+ :depends-on (:testiere)
+ :pathname "examples/"
+ :serial t
+ :components ((:file "examples")))
diff --git a/testiere.asd b/testiere.asd
index 3703823..f3748c3 100644
--- a/testiere.asd
+++ b/testiere.asd
@@ -2,12 +2,12 @@
(asdf:defsystem #:testiere
:description "TDD system for Common Lisp"
- :author "Colin OKeefe <colin@cicadas.surf>"
+ :author "Colin <colin@cicadas.surf>"
:license "GPLv3"
- :version "1.3.0"
- :depends-on (:trivia)
+ :version "1.3.1"
:pathname "src/"
:serial t
:components ((:file "package")
(:file "testiere")
(:file "standard-hooks")))
+