changelog shortlog graph tags branches files raw help

Mercurial > core / changeset: lib/rt upgrades and refactoring

changeset 632: bbd9024f2fe2
parent 631: 0b82a2893d26
child 633: 88a3f078c185
author: Richard Westhaver <ellis@rwest.io>
date: Sat, 31 Aug 2024 22:34:56 -0400
files: emacs/lib/scrum.el lisp/lib/rt/err.lisp lisp/lib/rt/fuzz.lisp lisp/lib/rt/obj.lisp lisp/lib/rt/pkg.lisp lisp/lib/rt/proto.lisp lisp/lib/rt/rt.asd lisp/lib/rt/rt.lisp lisp/lib/rt/tests.lisp lisp/lib/rt/util.lisp lisp/lib/rt/var.lisp lisp/lib/skel/tests.lisp
description: lib/rt upgrades and refactoring
     1.1--- a/emacs/lib/scrum.el	Fri Aug 30 21:29:55 2024 -0400
     1.2+++ b/emacs/lib/scrum.el	Sat Aug 31 22:34:56 2024 -0400
     1.3@@ -57,7 +57,9 @@
     1.4 (defgroup scrum nil
     1.5   "CC Scrum Framework.")
     1.6 
     1.7-(defvar scrum-properties '("SPRINT" "RELEASE" "TASKID"))
     1.8+(defvar scrum-properties '("SPRINT" "EPIC" "RELEASE" "TASKID" "PROJECT" "COMMIT" "GOAL"))
     1.9+
    1.10+(defvar scrum-tags '("demo" "mvp" "release" "major-release" "ua" "qa"))
    1.11 
    1.12 (provide 'scrum)
    1.13 ;;; scrum.el ends here
     2.1--- a/lisp/lib/rt/err.lisp	Fri Aug 30 21:29:55 2024 -0400
     2.2+++ b/lisp/lib/rt/err.lisp	Sat Aug 31 22:34:56 2024 -0400
     2.3@@ -6,4 +6,14 @@
     2.4 
     2.5 (in-package :rt)
     2.6 
     2.7-(deferror test-error () ())
     2.8+(define-condition test-condition () ())
     2.9+
    2.10+(define-condition test-failed (test-condition error)
    2.11+  ((reason :accessor fail-reason :initarg :reason :initform "unknown")
    2.12+   (name :accessor fail-name :initarg :name)
    2.13+   (form :accessor fail-form :initarg :form))
    2.14+  (:documentation "Signaled when a test fails.")
    2.15+  (:report (lambda (c s)
    2.16+             (format s "The following expression failed: ~S~%~A."
    2.17+                     (fail-form c)
    2.18+                     (fail-reason c)))))
     3.1--- a/lisp/lib/rt/fuzz.lisp	Fri Aug 30 21:29:55 2024 -0400
     3.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3@@ -1,51 +0,0 @@
     3.4-;;; fuzz.lisp --- RT Fuzz
     3.5-
     3.6-;; FUZZER API
     3.7-
     3.8-;;; Commentary:
     3.9-
    3.10-;; 
    3.11-;; wiki: https://en.wikipedia.org/wiki/Fuzzing
    3.12-
    3.13-;;; Code:
    3.14-(in-package :rt/fuzz)
    3.15-
    3.16-(defvar *default-fuzz-generator*
    3.17-  (lambda (state)
    3.18-    (random most-positive-fixnum state)))
    3.19-
    3.20-(defclass fuzzer ()
    3.21-  ((state :initform (make-random-state t)
    3.22-    :initarg :state
    3.23-          :accessor fuzz-state)
    3.24-   (generator :initform *default-fuzz-generator*
    3.25-              :initarg :generator
    3.26-              :type function
    3.27-              :accessor fuzz-generator))
    3.28-  (:documentation "An object which provides invalid, unexpected or random data as inputs to some
    3.29-program."))
    3.30-
    3.31-(defgeneric fuzz (self &key &allow-other-keys)
    3.32-  (:method ((self fuzzer) &key &allow-other-keys)
    3.33-    (funcall (the function (fuzz-generator self)) (fuzz-state self)))
    3.34-  (:method ((self fuzzer) &key count)
    3.35-    (if count
    3.36-        (let ((ret))
    3.37-          (dotimes (i count ret)
    3.38-            (push (funcall (the function (fuzz-generator self)) (fuzz-state self)) ret)))
    3.39-        (fuzz self))))
    3.40-
    3.41-(defgeneric fuzz* (state generator &key &allow-other-keys)
    3.42-  (:method ((state list) (generator function) &key (count 1))
    3.43-    (let ((ret))
    3.44-      (dotimes (i count ret)
    3.45-        (push (funcall generator state) ret))))
    3.46-  (:method ((state vector) (generator function) &key (count 1))
    3.47-    (let ((ret (make-array count :fill-pointer 0)))
    3.48-      (dotimes (i count ret)
    3.49-        (setf (aref ret i) (funcall generator state)))))
    3.50-  (:method ((state hash-table) (generator function) &key (count 1))
    3.51-    (let ((ret (make-hash-table)))
    3.52-      (dotimes (i count ret)
    3.53-        (destructuring-bind (k v) (funcall generator state)
    3.54-          (setf (gethash k ret) v))))))
     4.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2+++ b/lisp/lib/rt/obj.lisp	Sat Aug 31 22:34:56 2024 -0400
     4.3@@ -0,0 +1,306 @@
     4.4+;;; obj.lisp --- Test Objects
     4.5+
     4.6+;; 
     4.7+
     4.8+;;; Code:
     4.9+(in-package :rt)
    4.10+
    4.11+;;; Result
    4.12+(deftype result-tag ()
    4.13+  '(or (member :pass :fail :skip) null))
    4.14+
    4.15+(declaim (inline %make-test-result))
    4.16+(defstruct (test-result (:constructor %make-test-result)
    4.17+                        (:conc-name  tr-))
    4.18+  (tag nil :type result-tag :read-only t)
    4.19+  (form nil :type form))
    4.20+
    4.21+(defmethod print-object ((self test-result) stream)
    4.22+  (print-unreadable-object (self stream :identity t)
    4.23+    (format stream "~A ~A" (tr-tag self) (tr-form self))))
    4.24+
    4.25+(defun make-test-result (tag &optional form)
    4.26+  (%make-test-result :tag tag :form form))
    4.27+
    4.28+(defmethod test-pass-p ((res test-result))
    4.29+  (when (eq :pass (tr-tag res)) t))
    4.30+
    4.31+(defmethod test-fail-p ((res test-result))
    4.32+  (when (eq :fail (tr-tag res)) t))
    4.33+
    4.34+(defmethod test-skip-p ((res test-result))
    4.35+  (when (eq :skip (tr-tag res)) t))
    4.36+
    4.37+(defmethod print-object ((self test-result) stream)
    4.38+  (print-unreadable-object (self stream)
    4.39+    (format stream "~A ~A"
    4.40+            (tr-tag self)
    4.41+            (tr-form self))))
    4.42+
    4.43+;;; Test Object
    4.44+(defclass test-object ()
    4.45+  ((name :initarg :name :initform (required-argument) :type string :accessor test-name)
    4.46+   #+nil (cached :initarg :cache :allocation :class :accessor test-cached-p :type boolean))
    4.47+  (:documentation "Super class for all test-related objects."))
    4.48+
    4.49+(defmethod print-object ((self test-object) stream)
    4.50+  "test"
    4.51+  (print-unreadable-object (self stream :type t :identity t)
    4.52+    (format stream "~A"
    4.53+            (test-name self))))
    4.54+
    4.55+;;;; Tests
    4.56+(defclass test (test-object)
    4.57+  ((fn :type symbol :accessor test-fn)
    4.58+   (bench :type (or boolean fixnum) :accessor test-bench :initform nil :initarg :bench)
    4.59+   (profile :type list :accessor test-profile :initform nil :initarg :profile)
    4.60+   (args :type list :accessor test-args :initform nil :initarg :args)
    4.61+   (declare :type list :accessor test-declare :initform nil :initarg :declare)
    4.62+   (form :initarg :form :initform nil :accessor test-form)
    4.63+   (doc :initarg :doc :type string :accessor test-doc)
    4.64+   (lock :initarg :lock :type boolean :accessor test-lock-p)
    4.65+   (persist :initarg :persist :initform nil :type boolean :accessor test-persist-p)
    4.66+   (results :initarg :results :type (array test-result) :accessor test-results))
    4.67+  (:documentation "Test class typically made with `deftest'."))
    4.68+
    4.69+(defmethod initialize-instance ((self test) &key name)
    4.70+  ;; (debug! "building test" name)
    4.71+  (setf (test-fn self)
    4.72+        (make-symbol
    4.73+         (format nil "~A~A"
    4.74+                 name
    4.75+                 (gensym *test-suffix*))))
    4.76+  (setf (test-lock-p self) t)
    4.77+  ;; TODO 2023-09-21: we should count how many checks are in the :form
    4.78+  ;; slot and infer the array dimensions.
    4.79+  (setf (test-results self) (make-array 0 :element-type 'test-result))
    4.80+  (call-next-method))
    4.81+
    4.82+(defmethod print-object ((self test) stream)
    4.83+  (print-unreadable-object (self stream :type t :identity t)
    4.84+    (format stream "~A :fn ~A"
    4.85+            (test-name self)
    4.86+            (test-fn self))))
    4.87+
    4.88+(defmethod push-result ((self test-result) (place test))
    4.89+  (with-slots (results) place
    4.90+    (push self results)))
    4.91+
    4.92+(defmethod pop-result ((self test))
    4.93+  (pop (test-results self)))
    4.94+
    4.95+(defmethod eval-test ((self test))
    4.96+  (eval `(progn ,@(test-form self))))
    4.97+
    4.98+(defmethod funcall-test ((self test) &key declare)
    4.99+  (unless (functionp (test-fn self))
   4.100+    (trace! (setf (symbol-function (test-fn self))
   4.101+                  (eval `(lambda ()
   4.102+                           ,(when declare `(declare ,declare))
   4.103+                           ,@(test-form self))))))
   4.104+  (funcall (test-fn self)))
   4.105+
   4.106+(defmethod compile-test ((self test) &key declare &allow-other-keys)
   4.107+  (with-compilation-unit (:policy '(optimize debug))
   4.108+    (compile
   4.109+     (test-fn self)
   4.110+     `(lambda ()
   4.111+        ,(when declare `(declare ,declare))
   4.112+        ,@(test-form self)))))
   4.113+
   4.114+(defun fail! (form &optional fmt &rest args)
   4.115+  (let ((reason (and fmt (apply #'format nil fmt args))))
   4.116+    (with-simple-restart (ignore-fail "Continue testing.")
   4.117+      (error 'test-failed :reason reason :form form))))
   4.118+
   4.119+(defmacro with-test-env (self &body body)
   4.120+  `(catch '%in-test
   4.121+     (setf (test-lock-p ,self) t)
   4.122+     (let* ((*testing* ,self)
   4.123+            (%test-bail nil)
   4.124+            %test-result)
   4.125+       (block %test-bail
   4.126+         ,@body
   4.127+         (setf (test-lock-p ,self) %test-bail))
   4.128+       %test-result)))
   4.129+
   4.130+(defmethod do-test ((self test) &optional fx)
   4.131+  (declare (ignorable fx))
   4.132+  (with-test-env self
   4.133+    (trace! "running test: " *testing*)
   4.134+    (flet ((%do ()
   4.135+             (if-let ((opt *compile-tests*))
   4.136+               ;; RESEARCH 2023-08-31: with-compilation-unit?
   4.137+               (progn
   4.138+                 (if (eq opt t)
   4.139+                     (setq opt *test-opts*)
   4.140+                     (setq opt (push *test-opts* opt)))
   4.141+                 ;; TODO 2023-09-21: handle failures here
   4.142+                 (funcall (compile-test self :declare opt))
   4.143+                 (setf %test-result (make-test-result :pass (test-fn self))))
   4.144+               (progn
   4.145+                 (funcall-test self :declare '(optimize (debug 3) (safety 0)))
   4.146+                 (setf %test-result (make-test-result :pass (test-name self)))))))
   4.147+      (if *catch-test-errors*
   4.148+          (handler-bind
   4.149+              ((error 
   4.150+                 (lambda (c)
   4.151+                   (setf %test-bail t)
   4.152+                   (setf %test-result (make-test-result :fail c))
   4.153+                   (return-from %test-bail %test-result))))
   4.154+            (%do))
   4.155+          (%do)))))
   4.156+
   4.157+(defmethod do-test ((self simple-string) &optional fixture)
   4.158+  (when-let ((test (find-test *test-suite* self)))
   4.159+    (do-test test fixture)))
   4.160+
   4.161+(defmethod do-test ((self symbol) &optional fixture)
   4.162+  (when-let ((test (find-test *test-suite* (symbol-name self))))
   4.163+    (do-test test fixture)))
   4.164+
   4.165+;;;; Suites
   4.166+(defclass test-suite (test-object)
   4.167+  ((tests :initarg :set :initform nil :type list :accessor tests
   4.168+          :documentation "test-suite tests")
   4.169+   (results :initarg :results :initform nil :type list :accessor test-results
   4.170+            :documentation "test-suite results")
   4.171+   (stream :initarg :stream :initform *standard-output* :type stream :accessor test-stream)
   4.172+   (fixtures :initarg :fixtures :initform nil :type list :accessor test-fixtures))
   4.173+  (:documentation "A class for collections of related `test' objects."))
   4.174+
   4.175+(defmethod print-object ((self test-suite) stream)
   4.176+  (print-unreadable-object (self stream :type t :identity t)
   4.177+    (format stream "~A [~d:~d:~d:~d]"
   4.178+            (test-name self)
   4.179+            (length (tests self))
   4.180+            (count t (map-tests self #'test-lock-p))
   4.181+            (count t (map-tests self #'test-persist-p))
   4.182+            (length (test-results self)))))
   4.183+
   4.184+;; (defmethod reinitialize-instance ((self test-suite) &rest initargs &key &allow-other-keys))
   4.185+
   4.186+(deftype test-suite-designator ()
   4.187+  "Either nil, a symbol, a string, or a `test-suite' object."
   4.188+  '(or null symbol string test-suite keyword))
   4.189+
   4.190+(defun find-suite (name)
   4.191+  (declare (test-suite-designator name))
   4.192+  (find name *test-suite-list* :test #'test-name=))
   4.193+
   4.194+(defmethod map-tests ((self test-suite) function)
   4.195+  ;; tests are stored in reverse order. run LIFO.
   4.196+  (mapcar function (reverse (tests self))))
   4.197+
   4.198+(defmethod push-test ((self test) (place test-suite))
   4.199+  (push self (tests place)))
   4.200+
   4.201+(defmethod pop-test ((self test-suite))
   4.202+  (pop (tests self)))
   4.203+
   4.204+(defmethod push-result ((self test-result) (place test-suite))
   4.205+  (with-slots (results) place
   4.206+    (push self results)))
   4.207+
   4.208+(defmethod pop-result ((self test-suite))
   4.209+  (pop (test-results self)))
   4.210+
   4.211+(defmethod find-test ((self test-suite) name &key (test #'test-name=))
   4.212+  (declare (type (or string symbol) name)
   4.213+           (type function test))
   4.214+  (find name (tests self) :test test))
   4.215+
   4.216+(defmethod do-test ((self test-suite) &optional test)
   4.217+  (push-result 
   4.218+   (if test
   4.219+       (do-test
   4.220+           (etypecase test
   4.221+             (test test)
   4.222+             (string (find-test self test))
   4.223+             (symbol (find-test self (symbol-name test)))))
   4.224+       (do-test (pop-test self)))
   4.225+   self))
   4.226+
   4.227+;; HACK 2023-09-01: find better method of declaring failures from
   4.228+;; within the body of `deftest'.
   4.229+(defmethod do-suite ((self test-suite) &key stream force)
   4.230+  (when stream (setf (test-stream self) stream))
   4.231+  (with-slots (name stream) self
   4.232+    (format stream "in suite ~x:~%"
   4.233+            name)
   4.234+    (format stream "; with ~A~A tests~%"
   4.235+            (if force
   4.236+                ""
   4.237+                (format nil "~A/"
   4.238+                        (count t (tests self)
   4.239+                               :key (lambda (x) (or (test-lock-p x) (test-persist-p x))))))
   4.240+            (length (tests self)))
   4.241+    ;; loop over each test, calling `do-test'. if locked or
   4.242+    ;; persistent, test is performed. if FORCE is non-nil all tests
   4.243+    ;; are performed.
   4.244+    (map-tests self 
   4.245+               (lambda (x)
   4.246+                 (when (or force (test-lock-p x) (test-persist-p x))
   4.247+                   (let ((res (do-test x)))
   4.248+                     (push-result res self)
   4.249+                     (format stream "~@[~<~%~:;~:@(~S~) ~>~]~%" res)))))
   4.250+    ;; compare locked vs expected
   4.251+    (let ((locked (remove-if #'null (map-tests self (lambda (x) (when (test-lock-p x) x)))))
   4.252+          (fails
   4.253+            ;; collect if locked test not expected
   4.254+            (loop for r in (test-results self)
   4.255+                  unless (test-pass-p r)
   4.256+                  collect r)))
   4.257+      (if (null locked)
   4.258+          (format stream "~&No tests failed.~%")
   4.259+          (progn
   4.260+            ;;  RESEARCH 2023-09-04: print fails ??
   4.261+            (format stream "~&~A out of ~A ~
   4.262+                   total tests failed: ~
   4.263+                   ~:@(~{~<~%   ~1:;~S~>~
   4.264+                         ~^, ~}~)."
   4.265+                    (length locked)
   4.266+                    (length (tests self))
   4.267+                    locked)
   4.268+            (unless (null fails)
   4.269+              (format stream "~&~A unexpected failures: ~
   4.270+                   ~:@(~{~<~%   ~1:;~S~>~
   4.271+                         ~^, ~}~)."
   4.272+                      (length fails)
   4.273+                      fails))))
   4.274+      ;; close stream
   4.275+      (finish-output stream)
   4.276+      ;; return values (PASS? LOCKED)
   4.277+      (values (not fails) locked))))
   4.278+
   4.279+(defmethod do-suite ((self string) &key stream)
   4.280+  (do-suite (ensure-suite self) :stream stream))
   4.281+
   4.282+(defmethod do-suite ((self symbol) &key stream)
   4.283+  (do-suite (ensure-suite self) :stream stream))
   4.284+
   4.285+(defmethod do-suite ((self null) &key stream)
   4.286+  (do-suite *test-suite* :stream stream))
   4.287+
   4.288+;;; Fixtures
   4.289+;; Our fixtures are objects which can be inherited to build different fixture
   4.290+;; classes. Fixtures inherit from TEST-OBJECT and have a NAME which usually
   4.291+;; indicates the key used to initialize this object with MAKE-INSTANCE.
   4.292+
   4.293+;; You can use fixtures inside a test or use the push-fixture method on a
   4.294+;; `test-suite' object to make it accessible within that suite.
   4.295+
   4.296+(defclass fixture (test-object) ())
   4.297+
   4.298+(defclass tmp-fixture (fixture)
   4.299+  ((directory :initform #P"/tmp/" :type directory :initarg :directory)
   4.300+   (file :initform nil :type (or null pathname string) :initarg :file))
   4.301+  (:default-initargs
   4.302+   :name :tmp))
   4.303+
   4.304+(defmethod make-fixture ((kind (eql :tmp)) &rest args)
   4.305+  (apply 'make-instance 'tmp-fixture args))
   4.306+
   4.307+(defmacro with-fixture ((var (kind &rest args)) &body body)
   4.308+  `(let ((,var (make-fixture ,kind ,@args)))
   4.309+     ,@body))
     5.1--- a/lisp/lib/rt/pkg.lisp	Fri Aug 30 21:29:55 2024 -0400
     5.2+++ b/lisp/lib/rt/pkg.lisp	Sat Aug 31 22:34:56 2024 -0400
     5.3@@ -1,4 +1,4 @@
     5.4-;;; rt.lisp --- regression testing
     5.5+;;; pkg.lisp --- regression testing packages
     5.6 
     5.7 ;; Regression Testing framework. inspired by PCL, the original CMUCL
     5.8 ;; code, and the SBCL port.
     5.9@@ -96,7 +96,11 @@
    5.10    :test-name
    5.11    :tests
    5.12    :test-form
    5.13-   :test-results))
    5.14+   :test-results
    5.15+   :*tmp*
    5.16+   :*default-tmp-directory*
    5.17+   :with-tmp-directory
    5.18+   :with-tmp-file))
    5.19 
    5.20 (defpackage :rt/bench
    5.21   (:nicknames :bench)
    5.22@@ -138,632 +142,3 @@
    5.23            :fuzz*
    5.24            :fuzz-generator
    5.25            :fuzz-state))
    5.26-
    5.27-(in-package :rt)
    5.28-(in-readtable :std)
    5.29-
    5.30-;;; Vars
    5.31-(defvar *test-opts* '(optimize sb-c::instrument-consing))
    5.32-(defvar *compile-tests* nil
    5.33-  "When nil do not compile tests. With a value of t, tests are compiled
    5.34-with default optimizations else the value is used to configure
    5.35-compiler optimizations.")
    5.36-(defvar *catch-test-errors* t "When non-nil, cause errors in a test to be caught.")
    5.37-(defvar *test-suffix* "-TEST" "A suffix to append to every `test' defined with `deftest'.")
    5.38-(defvar *test-suite-list* nil "List of available `test-suite' objects.")
    5.39-(defvar *test-suite* nil "A 'test-suite-designator' which identifies the current `test-suite'.")
    5.40-(eval-when (:compile-toplevel :load-toplevel :execute)
    5.41-  (defvar *default-test-suite-name* "default"))
    5.42-(declaim (type (or stream boolean string) *test-input*))
    5.43-(defvar *test-input* nil "When non-nil, specifies an input stream or buffer for `*testing*'.")
    5.44-(defvar *testing* nil "Testing state var.")
    5.45-
    5.46-;;; Utils
    5.47-
    5.48-;; random
    5.49-(defvar *simple-charset* "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
    5.50-
    5.51-(defun random-elt (seq)
    5.52-  (elt seq (random (length seq))))
    5.53-
    5.54-(defun random-ref (vec)
    5.55-  (aref vec (random (length vec))))
    5.56-
    5.57-(defun random-char ()
    5.58-  (random-ref *simple-charset*))
    5.59-
    5.60-(defun random-chars (dim)
    5.61-  (let ((r (make-array dim :element-type 'character)))
    5.62-    (dotimes (i (array-total-size r) r)
    5.63-      (setf (row-major-aref r i) (random-char)))))
    5.64-
    5.65-(defun random-byte () (random 255))
    5.66-
    5.67-(defun random-bytes (dim)
    5.68-  (let ((r (make-array dim :element-type 'octet)))
    5.69-    (dotimes (i (array-total-size r) r)
    5.70-      (setf (row-major-aref r i) (random-byte)))))
    5.71-
    5.72-(eval-when (:compile-toplevel :load-toplevel :execute)
    5.73-  (defun make-test (&rest slots)
    5.74-    (apply #'make-instance 'test slots))
    5.75-  (defun make-suite (&rest slots)
    5.76-    (apply #'make-instance 'test-suite slots)))
    5.77-
    5.78-;; TODO 2023-09-04: optimize
    5.79-;;(declaim (inline do-tests))
    5.80-(defun do-tests (&optional (suite *test-suite*) force (output *standard-output*))
    5.81-  (if (pathnamep output)
    5.82-      (with-open-file (stream output :direction :output)
    5.83-	(do-suite (ensure-suite suite) :stream stream :force force))
    5.84-      (do-suite (ensure-suite suite) :stream output :force force)))
    5.85-
    5.86-(defvar *test-output-mutex* (sb-thread:make-mutex :name "tests-output"))
    5.87-
    5.88-;; TODO
    5.89-(defun do-tests-concurrently (&optional (suite *test-suite*) force (output *standard-output*))
    5.90-  (declare (ignore suite force))
    5.91-  (sb-thread:with-mutex (*test-output-mutex*)
    5.92-    (let ((stream (make-synonym-stream output)))
    5.93-      (let ((*standard-output* stream)
    5.94-            (*error-output* stream))
    5.95-        (nyi!)))))
    5.96-
    5.97-(defun reset-tests ()
    5.98-  (setq *testing* nil
    5.99-        *test-suite* nil
   5.100-        *test-suite-list* nil
   5.101-        *test-input* nil))
   5.102-
   5.103-;; this assumes that *test-suite* is re-initialized correctly to the
   5.104-;; correct test-suite object.
   5.105-(defun continue-testing ()
   5.106-  (if-let ((test *testing*))
   5.107-    (throw '%in-test test)
   5.108-    (do-suite *test-suite*)))
   5.109-
   5.110-;; NOTE 2023-09-01: `pushnew' does not return an indication of whether
   5.111-;; place is changed - it returns place. This is functionally sound but
   5.112-;; means that if we want to do something else in the event that place
   5.113-;; is unchanged, we run into some friction,
   5.114-;; https://stackoverflow.com/questions/56228832/adapting-common-lisp-pushnew-to-return-success-failure
   5.115-(defun spush (item lst &key (test #'equal))
   5.116-  "Substituting `push'"
   5.117-  (declare (type function test))
   5.118-  (cond
   5.119-    ((null lst) (push item lst))
   5.120-    ((list lst)
   5.121-     (if-let ((found (member item lst
   5.122-			     :test test)))
   5.123-       (progn
   5.124-	 (rplaca found item)
   5.125-	 lst)
   5.126-       (push item lst)))
   5.127-    #|(or nil '(t (cons item lst)))|#))
   5.128-
   5.129-;; FIX 2023-08-31: spush, replace with `add-test' method.
   5.130-;; (declaim (inline normalize-test-name))
   5.131-(defun normalize-test-name (a)
   5.132-  "Return the normalized `test-suite-designator' of A."
   5.133-  (etypecase a
   5.134-    (string (string-upcase a))
   5.135-    (symbol (symbol-name a))
   5.136-    (test-object (normalize-test-name (test-name a)))
   5.137-    (t (format nil "~A" a))))
   5.138-
   5.139-(defun test-name= (a b)
   5.140-  "Return t if A and B are similar `test-suite-designator's."
   5.141-  (let ((a (normalize-test-name a))
   5.142-	(b (normalize-test-name b)))
   5.143-    (string= a b)))
   5.144-
   5.145-;; (declaim (inline assert-suite ensure-suite))
   5.146-(defun ensure-suite (name)
   5.147-  (if-let ((ok (member name *test-suite-list* :test #'test-name=)))
   5.148-    (car ok)
   5.149-    (when (or (eq name t) (null name)) (make-suite :name *default-test-suite-name*))))
   5.150-
   5.151-(defun check-suite-designator (suite) (check-type suite test-suite-designator))
   5.152-
   5.153-(defun assert-suite (name)
   5.154-  (check-suite-designator name)
   5.155-  (assert (ensure-suite name)))
   5.156-
   5.157-(declaim (inline test-opt-key-p test-opt-valid-p))
   5.158-(defun test-opt-key-p (k)
   5.159-  "Test if K is a `test-opt-key'."
   5.160-  (member k '(:profile :save :stream)))
   5.161-
   5.162-(defun test-opt-valid-p (f)
   5.163-  "Test if F is a valid `test-opt' form. If so, return F else nil."
   5.164-  (when (test-opt-key-p (car f))
   5.165-    f))
   5.166-
   5.167-;;; Conditions
   5.168-(define-condition test-failed (error)
   5.169-  ((reason :accessor fail-reason :initarg :reason :initform "unknown")
   5.170-   (name :accessor fail-name :initarg :name)
   5.171-   (form :accessor fail-form :initarg :form))
   5.172-  (:documentation "Signaled when a test fails.")
   5.173-  (:report (lambda (c s)
   5.174-	     (format s "The following expression failed: ~S~%~A."
   5.175-		     (fail-form c)
   5.176-		     (fail-reason c)))))
   5.177-
   5.178-;;; Protocol
   5.179-(defgeneric eval-test (self)
   5.180-  (:documentation "Eval a `test'."))
   5.181-
   5.182-(defgeneric compile-test (self &key &allow-other-keys)
   5.183-  (:documentation "Compile a `test'."))
   5.184-
   5.185-(defgeneric locked-tests (self)
   5.186-  (:documentation "Return a list of locked tests in `test-suite' object SELF."))
   5.187-
   5.188-(defgeneric push-test (self place)
   5.189-  (:documentation
   5.190-   "Push `test' SELF to the value of slot ':tests' in `test-suite' object PLACE."))
   5.191-
   5.192-(defgeneric pop-test (self)
   5.193-  (:documentation
   5.194-   "Pop the first `test' from the slot-value of ':tests' in `test-suite' object SELF."))
   5.195-
   5.196-(defgeneric push-result (self place)
   5.197-  (:documentation
   5.198-   "Push object SELF to the value of slot ':results' in object PLACE."))
   5.199-
   5.200-(defgeneric pop-result (self)
   5.201-  (:documentation
   5.202-   "Pop the first `test-result' from the slot-value of ':tests' from object SELF."))
   5.203-
   5.204-(defgeneric push-fixture (self place)
   5.205-  (:documentation
   5.206-   "Push object SELF to the value of slot ':results' in object PLACE."))
   5.207-
   5.208-(defgeneric delete-test (self &key &allow-other-keys)
   5.209-  (:documentation "Delete `test' object specified by `test-object' SELF and optional keys."))
   5.210-
   5.211-(defgeneric find-test (self name &key &allow-other-keys)
   5.212-  (:documentation "Find `test' object specified by name and optional keys."))
   5.213-
   5.214-(defgeneric do-test (self &optional context)
   5.215-  (:documentation "Run test SELF, printing results to *standard-output*. The second
   5.216-argument is an optional fixture.
   5.217-
   5.218-SELF can also be a `test-suite', in which case the TESTS slot is
   5.219-queried for the value of TEST. If TEST is not provided, pops the car
   5.220-from TESTS."))
   5.221-
   5.222-(defgeneric do-suite (self &key &allow-other-keys)
   5.223-  (:documentation
   5.224-   "Perform actions on `test-suite' object SELF with optional keys."))
   5.225-
   5.226-;;;; Results
   5.227-(deftype result-tag ()
   5.228-  '(or (member :pass :fail :skip) null))
   5.229-
   5.230-(declaim (inline %make-test-result))
   5.231-(defstruct (test-result (:constructor %make-test-result)
   5.232-			(:conc-name  tr-))
   5.233-  (tag nil :type result-tag :read-only t)
   5.234-  (form nil :type form))
   5.235-
   5.236-(defmethod print-object ((self test-result) stream)
   5.237-  (print-unreadable-object (self stream :identity t)
   5.238-    (format stream "~A ~A" (tr-tag self) (tr-form self))))
   5.239-
   5.240-(defun make-test-result (tag &optional form)
   5.241-  (%make-test-result :tag tag :form form))
   5.242-
   5.243-(defmethod test-pass-p ((res test-result))
   5.244-  (when (eq :pass (tr-tag res)) t))
   5.245-
   5.246-(defmethod test-fail-p ((res test-result))
   5.247-  (when (eq :fail (tr-tag res)) t))
   5.248-
   5.249-(defmethod test-skip-p ((res test-result))
   5.250-  (when (eq :skip (tr-tag res)) t))
   5.251-
   5.252-(defmethod print-object ((self test-result) stream)
   5.253-  (print-unreadable-object (self stream)
   5.254-    (format stream "~A ~A"
   5.255-	    (tr-tag self)
   5.256-	    (tr-form self))))
   5.257-
   5.258-;;; Objects
   5.259-(defclass test-object ()
   5.260-  ((name :initarg :name :initform (required-argument) :type string :accessor test-name)
   5.261-   #+nil (cached :initarg :cache :allocation :class :accessor test-cached-p :type boolean))
   5.262-  (:documentation "Super class for all test-related objects."))
   5.263-
   5.264-(defmethod print-object ((self test-object) stream)
   5.265-  "test"
   5.266-  (print-unreadable-object (self stream :type t :identity t)
   5.267-    (format stream "~A"
   5.268-	    (test-name self))))
   5.269-
   5.270-;;;; Tests
   5.271-;; HACK 2023-08-31: inherit sxp?
   5.272-
   5.273-(defclass test (test-object)
   5.274-  ((fn :type symbol :accessor test-fn)
   5.275-   (bench :type (or boolean fixnum) :accessor test-bench :initform nil :initarg :bench)
   5.276-   (profile :type list :accessor test-profile :initform nil :initarg :profile)
   5.277-   (args :type list :accessor test-args :initform nil :initarg :args)
   5.278-   (declare :type list :accessor test-declare :initform nil :initarg :declare)
   5.279-   (form :initarg :form :initform nil :accessor test-form)
   5.280-   (doc :initarg :doc :type string :accessor test-doc)
   5.281-   (lock :initarg :lock :type boolean :accessor test-lock-p)
   5.282-   (persist :initarg :persist :initform nil :type boolean :accessor test-persist-p)
   5.283-   (results :initarg :results :type (array test-result) :accessor test-results))
   5.284-  (:documentation "Test class typically made with `deftest'."))
   5.285-
   5.286-(defmethod initialize-instance ((self test) &key name)
   5.287-  ;; (debug! "building test" name)
   5.288-  (setf (test-fn self)
   5.289-	(make-symbol
   5.290-	 (format nil "~A~A"
   5.291-		 name
   5.292-		 (gensym *test-suffix*))))
   5.293-  (setf (test-lock-p self) t)
   5.294-  ;; TODO 2023-09-21: we should count how many checks are in the :form
   5.295-  ;; slot and infer the array dimensions.
   5.296-  (setf (test-results self) (make-array 0 :element-type 'test-result))
   5.297-  (call-next-method))
   5.298-
   5.299-(defmethod print-object ((self test) stream)
   5.300-  (print-unreadable-object (self stream :type t :identity t)
   5.301-    (format stream "~A :fn ~A"
   5.302-	    (test-name self)
   5.303-	    (test-fn self))))
   5.304-
   5.305-(defmethod push-result ((self test-result) (place test))
   5.306-  (with-slots (results) place
   5.307-    (push self results)))
   5.308-
   5.309-(defmethod pop-result ((self test))
   5.310-  (pop (test-results self)))
   5.311-
   5.312-(defmethod eval-test ((self test))
   5.313-  (eval `(progn ,@(test-form self))))
   5.314-
   5.315-(defmethod funcall-test ((self test) &key declare)
   5.316-  (unless (functionp (test-fn self))
   5.317-    (trace! (setf (symbol-function (test-fn self))
   5.318-                  (eval `(lambda ()
   5.319-                           ,(when declare `(declare ,declare))
   5.320-                           ,@(test-form self))))))
   5.321-  (funcall (test-fn self)))
   5.322-
   5.323-(defmethod compile-test ((self test) &key declare &allow-other-keys)
   5.324-  (with-compilation-unit (:policy '(optimize debug))
   5.325-    (compile
   5.326-     (test-fn self)
   5.327-     `(lambda ()
   5.328-        ,(when declare `(declare ,declare))
   5.329-        ,@(test-form self)))))
   5.330-
   5.331-(defun fail! (form &optional fmt &rest args)
   5.332-  (let ((reason (and fmt (apply #'format nil fmt args))))
   5.333-    (with-simple-restart (ignore-fail "Continue testing.")
   5.334-      (error 'test-failed :reason reason :form form))))
   5.335-
   5.336-(defmacro with-test-env (self &body body)
   5.337-  `(catch '%in-test
   5.338-     (setf (test-lock-p ,self) t)
   5.339-     (let* ((*testing* ,self)
   5.340-	    (%test-bail nil)
   5.341-	    %test-result)
   5.342-       (block %test-bail
   5.343-	 ,@body
   5.344-	 (setf (test-lock-p ,self) %test-bail))
   5.345-       %test-result)))
   5.346-
   5.347-(defmethod do-test ((self test) &optional fx)
   5.348-  (declare (ignorable fx))
   5.349-  (with-test-env self
   5.350-    (trace! "running test: " *testing*)
   5.351-    (flet ((%do ()
   5.352-	     (if-let ((opt *compile-tests*))
   5.353-	       ;; RESEARCH 2023-08-31: with-compilation-unit?
   5.354-	       (progn
   5.355-		 (if (eq opt t)
   5.356-                     (setq opt *test-opts*)
   5.357-                     (setq opt (push *test-opts* opt)))
   5.358-		 ;; TODO 2023-09-21: handle failures here
   5.359-		 (funcall (compile-test self :declare opt))
   5.360-		 (setf %test-result (make-test-result :pass (test-fn self))))
   5.361-	       (progn
   5.362-                 (funcall-test self :declare '(optimize (debug 3) (safety 0)))
   5.363-		 (setf %test-result (make-test-result :pass (test-name self)))))))
   5.364-      (if *catch-test-errors*
   5.365-	  (handler-bind
   5.366-	      ((error 
   5.367-		 (lambda (c)
   5.368-		   (setf %test-bail t)
   5.369-		   (setf %test-result (make-test-result :fail c))
   5.370-		   (return-from %test-bail %test-result))))
   5.371-	    (%do))
   5.372-	  (%do)))))
   5.373-
   5.374-(defmethod do-test ((self simple-string) &optional fixture)
   5.375-  (when-let ((test (find-test *test-suite* self)))
   5.376-    (do-test test fixture)))
   5.377-
   5.378-(defmethod do-test ((self symbol) &optional fixture)
   5.379-  (when-let ((test (find-test *test-suite* (symbol-name self))))
   5.380-    (do-test test fixture)))
   5.381-
   5.382-;;;; Fixtures
   5.383-
   5.384-;; Our fixtures are just closures - with a pandoric environment. You
   5.385-;; might call it a domain-specific object protocol.
   5.386-
   5.387-;; You can build fixtures inside a test or use the push-fixture
   5.388-;; method on a `test-suite' object.
   5.389-
   5.390-(deftype fixture () 'form)
   5.391-
   5.392-(declaim (inline %make-fixture-prototype))
   5.393-(defstruct (fixture-prototype (:constructor %make-fixture-prototype)
   5.394-			      (:conc-name fxp))
   5.395-  (kind :empty :type keyword)
   5.396-  (form nil :type form))
   5.397-
   5.398-(defun make-fixture-prototype (kind form)
   5.399-  (%make-fixture-prototype :kind kind :form form))
   5.400-
   5.401-(defmacro make-fixture (letargs &body ds)
   5.402-  (let ((letargs (let-binding-transform letargs)))
   5.403-    `(let (,@letargs)
   5.404-       (dlambda ,@ds))))
   5.405-
   5.406-(defmacro with-fixture ((var fx) &body body)
   5.407-  `(let ((,var ,fx))
   5.408-     ,@body))
   5.409-
   5.410-;;;; Suites
   5.411-(defclass test-suite (test-object)
   5.412-  ((tests :initarg :set :initform nil :type list :accessor tests
   5.413-	  :documentation "test-suite tests")
   5.414-   (results :initarg :results :initform nil :type list :accessor test-results
   5.415-	    :documentation "test-suite results")
   5.416-   (stream :initarg :stream :initform *standard-output* :type stream :accessor test-stream)
   5.417-   (fixtures :initarg :fixtures :initform nil :type list :accessor test-fixtures))
   5.418-  (:documentation "A class for collections of related `test' objects."))
   5.419-
   5.420-(defmethod print-object ((self test-suite) stream)
   5.421-  (print-unreadable-object (self stream :type t :identity t)
   5.422-    (format stream "~A [~d:~d:~d:~d]"
   5.423-	    (test-name self)
   5.424-	    (length (tests self))
   5.425-	    (count t (map-tests self #'test-lock-p))
   5.426-	    (count t (map-tests self #'test-persist-p))
   5.427-	    (length (test-results self)))))
   5.428-
   5.429-;; (defmethod reinitialize-instance ((self test-suite) &rest initargs &key &allow-other-keys))
   5.430-
   5.431-(deftype test-suite-designator ()
   5.432-  "Either nil, a symbol, a string, or a `test-suite' object."
   5.433-  '(or null symbol string test-suite keyword))
   5.434-
   5.435-(defun find-suite (name)
   5.436-  (declare (test-suite-designator name))
   5.437-  (find name *test-suite-list* :test #'test-name=))
   5.438-
   5.439-(defmethod map-tests ((self test-suite) function)
   5.440-  ;; tests are stored in reverse order. run LIFO.
   5.441-  (mapcar function (reverse (tests self))))
   5.442-
   5.443-(defmethod push-test ((self test) (place test-suite))
   5.444-  (push self (tests place)))
   5.445-
   5.446-(defmethod pop-test ((self test-suite))
   5.447-  (pop (tests self)))
   5.448-
   5.449-(defmethod push-result ((self test-result) (place test-suite))
   5.450-  (with-slots (results) place
   5.451-    (push self results)))
   5.452-
   5.453-(defmethod pop-result ((self test-suite))
   5.454-  (pop (test-results self)))
   5.455-
   5.456-(defmethod find-test ((self test-suite) name &key (test #'test-name=))
   5.457-  (declare (type (or string symbol) name)
   5.458-	   (type function test))
   5.459-  (find name (tests self) :test test))
   5.460-
   5.461-(defmethod do-test ((self test-suite) &optional test)
   5.462-  (push-result 
   5.463-   (if test
   5.464-       (do-test
   5.465-           (etypecase test
   5.466-             (test test)
   5.467-             (string (find-test self test))
   5.468-             (symbol (find-test self (symbol-name test)))))
   5.469-       (do-test (pop-test self)))
   5.470-   self))
   5.471-
   5.472-;; HACK 2023-09-01: find better method of declaring failures from
   5.473-;; within the body of `deftest'.
   5.474-(defmethod do-suite ((self test-suite) &key stream force)
   5.475-  (when stream (setf (test-stream self) stream))
   5.476-  (with-slots (name stream) self
   5.477-    (format stream "in suite ~x:~%"
   5.478-	    name)
   5.479-    (format stream "; with ~A~A tests~%"
   5.480-            (if force
   5.481-                ""
   5.482-                (format nil "~A/"
   5.483-                        (count t (tests self)
   5.484-                               :key (lambda (x) (or (test-lock-p x) (test-persist-p x))))))
   5.485-            (length (tests self)))
   5.486-    ;; loop over each test, calling `do-test'. if locked or
   5.487-    ;; persistent, test is performed. if FORCE is non-nil all tests
   5.488-    ;; are performed.
   5.489-    (map-tests self 
   5.490-	       (lambda (x)
   5.491-		 (when (or force (test-lock-p x) (test-persist-p x))
   5.492-		   (let ((res (do-test x)))
   5.493-		     (push-result res self)
   5.494-		     (format stream "~@[~<~%~:;~:@(~S~) ~>~]~%" res)))))
   5.495-    ;; compare locked vs expected
   5.496-    (let ((locked (remove-if #'null (map-tests self (lambda (x) (when (test-lock-p x) x)))))
   5.497-	  (fails
   5.498-	    ;; collect if locked test not expected
   5.499-	    (loop for r in (test-results self)
   5.500-		  unless (test-pass-p r)
   5.501-		  collect r)))
   5.502-      (if (null locked)
   5.503-	  (format stream "~&No tests failed.~%")
   5.504-	  (progn
   5.505-	    ;;  RESEARCH 2023-09-04: print fails ??
   5.506-	    (format stream "~&~A out of ~A ~
   5.507-                   total tests failed: ~
   5.508-                   ~:@(~{~<~%   ~1:;~S~>~
   5.509-                         ~^, ~}~)."
   5.510-		    (length locked)
   5.511-		    (length (tests self))
   5.512-		    locked)
   5.513-	    (unless (null fails)
   5.514-	      (format stream "~&~A unexpected failures: ~
   5.515-                   ~:@(~{~<~%   ~1:;~S~>~
   5.516-                         ~^, ~}~)."
   5.517-		      (length fails)
   5.518-		      fails))))
   5.519-      ;; close stream
   5.520-      (finish-output stream)
   5.521-      ;; return values (PASS? LOCKED)
   5.522-      (values (not fails) locked))))
   5.523-
   5.524-(defmethod do-suite ((self string) &key stream)
   5.525-  (do-suite (ensure-suite self) :stream stream))
   5.526-
   5.527-(defmethod do-suite ((self symbol) &key stream)
   5.528-  (do-suite (ensure-suite self) :stream stream))
   5.529-
   5.530-(defmethod do-suite ((self null) &key stream)
   5.531-  (do-suite *test-suite* :stream stream))
   5.532-
   5.533-;;; Checks
   5.534-(eval-always
   5.535-  (defun %test (val &optional form)
   5.536-    (let ((r
   5.537-	    (if val 
   5.538-	        (make-test-result :pass form)
   5.539-	        (make-test-result :fail form))))
   5.540-      ;; (print r *standard-output*)
   5.541-      r)))
   5.542-
   5.543-(defmacro is (test &rest args)
   5.544-  "The DWIM Check.
   5.545-
   5.546-(is (= 1 1)) ;=> #S(TEST-RESULT :TAG :PASS :FORM (= 1 1))
   5.547-If TEST returns a truthy value, return a PASS test-result, else return
   5.548-a FAIL. The TEST is parameterized by ARGS which is a plist or nil.
   5.549-
   5.550-If ARGS is nil, TEST is bound to to the RESULT slot of the test-result
   5.551-and evaluated 'as-is'.
   5.552-
   5.553-(nyi!)
   5.554-ARGS may contain the following keywords followed by a corresponding
   5.555-value:
   5.556-
   5.557-:EXPECTED
   5.558-
   5.559-:TIMEOUT
   5.560-
   5.561-:THEN
   5.562-
   5.563-All other values are treated as let bindings.
   5.564-"
   5.565-    (with-gensyms (form)
   5.566-      `(if ,(null args)
   5.567-	   (if *testing*
   5.568-	       (push-result (trace! (funcall #'rt::%test ,test ',test)) *testing*)
   5.569-	       (trace! (funcall #'rt::%test ,test ',test)))
   5.570-	   (macrolet ((,form (test) `(let ,,(group args 2) ,test)))
   5.571-	     ;; TODO 2023-09-21: does this work...
   5.572-	     (if *testing*
   5.573-		 (push-result (trace! (funcall #'rt::%test (,form ,test) ',test) *testing*))
   5.574-		 (trace! (funcall #'rt::%test (,form ,test) ',test)))))))
   5.575-
   5.576-(defmacro signals (condition-spec &body body)
   5.577-  "Generates a passing TEST-RESULT if body signals a condition of type
   5.578-CONDITION-SPEC. BODY is evaluated in a block named NIL, CONDITION-SPEC
   5.579-is not evaluated."
   5.580-  (let ((block-name (gensym)))
   5.581-    (destructuring-bind (condition &optional reason-control &rest reason-args)
   5.582-        (ensure-list condition-spec)
   5.583-      `(block ,block-name
   5.584-         (handler-bind ((,condition (lambda (c)
   5.585-                                      (declare (ignore c))
   5.586-                                      ;; ok, body threw condition
   5.587-				      ;; TODO 2023-09-05: result collectors
   5.588-                                      ;; (add-result 'test-passed
   5.589-                                      ;;            :test-expr ',condition)
   5.590-                                      (return-from ,block-name (make-test-result :pass ',body)))))
   5.591-           (block nil
   5.592-             (locally (declare (sb-ext:muffle-conditions warning))
   5.593-               ,@body)))
   5.594-         (fail!
   5.595-          ',condition
   5.596-          ,@(if reason-control
   5.597-                `(,reason-control ,@reason-args)
   5.598-                `("Failed to signal a ~S" ',condition)))
   5.599-         (return-from ,block-name nil)))))
   5.600-
   5.601-;;; Macros
   5.602-(defmacro deftest (name props &body body)
   5.603-  "Build a test with NAME, parameterized by PROPS and with a test form of BODY.
   5.604-
   5.605-PROPS is a plist which currently accepts the following parameters:
   5.606-
   5.607-:PERSIST - re-run this test even if it passes
   5.608-
   5.609-:ARGS - nyi
   5.610-
   5.611-:PROFILE - enable profiling of this test
   5.612-
   5.613-:SKIP - don't push this test to the current *TEST-SUITE*
   5.614-
   5.615-:BENCH - enable benchmarking of this test
   5.616-
   5.617-BODY is parsed with SB-INT:PARSE-BODY and will fill in documentation
   5.618-and declarations for the test body.
   5.619-"
   5.620-  (destructuring-bind (pr doc dec fn)
   5.621-      (multiple-value-bind (forms dec doc)
   5.622-	  ;; parse body with docstring allowed
   5.623-	  (parse-body (or body) :documentation t :whole t)
   5.624-	`(,props ,doc ,dec ',forms))
   5.625-    ;; TODO 2023-09-21: parse plist
   5.626-    `(let ((obj (make-test
   5.627-		 :name ,(format nil "~A" name)
   5.628-		 :form ,fn
   5.629-		 ,@(when-let ((v (getf pr :persist))) `(:persist ,v))
   5.630-		 ,@(when-let ((v (getf pr :args))) `(:args ',v))
   5.631-		 ,@(when-let ((v (getf pr :bench))) `(:bench ,v))
   5.632-		 ,@(when-let ((v (getf pr :profile))) `(:profile ,v))
   5.633-		 ,@(when doc `(:doc ,doc))
   5.634-		 ,@(when dec `(:declare ,dec)))))
   5.635-       ,(unless (getf pr :skip) '(push-test obj *test-suite*))
   5.636-       obj)))
   5.637-
   5.638-(defmacro defsuite (suite-name &rest props)
   5.639-  "Define a TEST-SUITE with provided keys. The object returned can be
   5.640-enabled using the IN-SUITE macro, similiar to the DEFPACKAGE API."
   5.641-  (check-type suite-name (or symbol string))
   5.642-  `(eval-when (:compile-toplevel :load-toplevel :execute)
   5.643-     (let ((obj (make-suite
   5.644-		 :name (format nil "~A" ',suite-name)
   5.645-		 ,@(when-let ((v (getf props :stream))) `(:stream ,v)))))
   5.646-       (setq *test-suite-list* (spush obj *test-suite-list* :test #'test-name=))
   5.647-       obj)))
   5.648-
   5.649-(defmacro in-suite (name)
   5.650-  "Set *TEST-SUITE* to the TEST-SUITE object referred to by symbol
   5.651-NAME. Return the object."
   5.652-  (assert-suite name)
   5.653-  `(progn
   5.654-     (setq *test-suite* (ensure-suite ,name))))
     6.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2+++ b/lisp/lib/rt/proto.lisp	Sat Aug 31 22:34:56 2024 -0400
     6.3@@ -0,0 +1,57 @@
     6.4+;;; proto.lisp --- Test Protocols
     6.5+
     6.6+;; 
     6.7+
     6.8+;;; Code:
     6.9+(in-package :rt)
    6.10+
    6.11+(defgeneric eval-test (self)
    6.12+  (:documentation "Eval a `test'."))
    6.13+
    6.14+(defgeneric compile-test (self &key &allow-other-keys)
    6.15+  (:documentation "Compile a `test'."))
    6.16+
    6.17+(defgeneric locked-tests (self)
    6.18+  (:documentation "Return a list of locked tests in `test-suite' object SELF."))
    6.19+
    6.20+(defgeneric push-test (self place)
    6.21+  (:documentation
    6.22+   "Push `test' SELF to the value of slot ':tests' in `test-suite' object PLACE."))
    6.23+
    6.24+(defgeneric pop-test (self)
    6.25+  (:documentation
    6.26+   "Pop the first `test' from the slot-value of ':tests' in `test-suite' object SELF."))
    6.27+
    6.28+(defgeneric push-result (self place)
    6.29+  (:documentation
    6.30+   "Push object SELF to the value of slot ':results' in object PLACE."))
    6.31+
    6.32+(defgeneric pop-result (self)
    6.33+  (:documentation
    6.34+   "Pop the first `test-result' from the slot-value of ':tests' from object SELF."))
    6.35+
    6.36+(defgeneric push-fixture (self place)
    6.37+  (:documentation
    6.38+   "Push object SELF to the value of slot ':results' in object PLACE."))
    6.39+
    6.40+(defgeneric delete-test (self &key &allow-other-keys)
    6.41+  (:documentation "Delete `test' object specified by `test-object' SELF and optional keys."))
    6.42+
    6.43+(defgeneric find-test (self name &key &allow-other-keys)
    6.44+  (:documentation "Find `test' object specified by name and optional keys."))
    6.45+
    6.46+(defgeneric do-test (self &optional context)
    6.47+  (:documentation "Run test SELF, printing results to *standard-output*. The second
    6.48+argument is an optional fixture.
    6.49+
    6.50+SELF can also be a `test-suite', in which case the TESTS slot is
    6.51+queried for the value of TEST. If TEST is not provided, pops the car
    6.52+from TESTS."))
    6.53+
    6.54+(defgeneric do-suite (self &key &allow-other-keys)
    6.55+  (:documentation
    6.56+   "Perform actions on `test-suite' object SELF with optional keys."))
    6.57+
    6.58+(defgeneric make-fixture (kind &rest args &key &allow-other-keys)
    6.59+  (:documentation
    6.60+   "Make a FIXTURE object with optional init ARGS."))
     7.1--- a/lisp/lib/rt/rt.asd	Fri Aug 30 21:29:55 2024 -0400
     7.2+++ b/lisp/lib/rt/rt.asd	Sat Aug 31 22:34:56 2024 -0400
     7.3@@ -2,11 +2,17 @@
     7.4 (defsystem :rt
     7.5   :depends-on (:std :log :dat :sb-sprof)
     7.6   :components ((:file "pkg")
     7.7-               (:file "bench" :depends-on ("pkg"))
     7.8-               (:file "tracing" :depends-on ("pkg"))
     7.9-               (:file "flamegraph" :depends-on ("pkg"))
    7.10-               (:file "cover" :depends-on ("pkg"))
    7.11-               (:file "fuzz" :depends-on ("pkg")))
    7.12+               (:file "var" :depends-on ("pkg"))
    7.13+               (:file "err" :depends-on ("pkg"))
    7.14+               (:file "util" :depends-on ("err" "var"))
    7.15+               (:file "proto" :depends-on ("pkg"))
    7.16+               (:file "obj" :depends-on ("proto" "util"))
    7.17+               (:file "rt" :depends-on ("obj"))
    7.18+               (:file "bench" :depends-on ("rt"))
    7.19+               (:file "tracing" :depends-on ("rt"))
    7.20+               (:file "flamegraph" :depends-on ("rt"))
    7.21+               (:file "cover" :depends-on ("rt"))
    7.22+               (:file "fuzz" :depends-on ("rt")))
    7.23   :in-order-to ((test-op (test-op "rt/tests"))))
    7.24 
    7.25 (defsystem :rt/tests
     8.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2+++ b/lisp/lib/rt/rt.lisp	Sat Aug 31 22:34:56 2024 -0400
     8.3@@ -0,0 +1,129 @@
     8.4+;;; rt.lisp --- Regression Testing
     8.5+
     8.6+;; 
     8.7+
     8.8+;;; Code:
     8.9+(in-package :rt)
    8.10+
    8.11+;;; Checks
    8.12+(eval-always
    8.13+  (defun %test (val &optional form)
    8.14+    (let ((r
    8.15+            (if val 
    8.16+                (make-test-result :pass form)
    8.17+                (make-test-result :fail form))))
    8.18+      ;; (print r *standard-output*)
    8.19+      r)))
    8.20+
    8.21+(defmacro is (test &rest args)
    8.22+  "The DWIM Check.
    8.23+
    8.24+(is (= 1 1)) ;=> #S(TEST-RESULT :TAG :PASS :FORM (= 1 1))
    8.25+If TEST returns a truthy value, return a PASS test-result, else return
    8.26+a FAIL. The TEST is parameterized by ARGS which is a plist or nil.
    8.27+
    8.28+If ARGS is nil, TEST is bound to to the RESULT slot of the test-result
    8.29+and evaluated 'as-is'.
    8.30+
    8.31+(nyi!)
    8.32+ARGS may contain the following keywords followed by a corresponding
    8.33+value:
    8.34+
    8.35+:EXPECTED
    8.36+
    8.37+:TIMEOUT
    8.38+
    8.39+:THEN
    8.40+
    8.41+All other values are treated as let bindings.
    8.42+"
    8.43+    (with-gensyms (form)
    8.44+      `(if ,(null args)
    8.45+           (if *testing*
    8.46+               (push-result (trace! (funcall #'rt::%test ,test ',test)) *testing*)
    8.47+               (trace! (funcall #'rt::%test ,test ',test)))
    8.48+           (macrolet ((,form (test) `(let ,,(group args 2) ,test)))
    8.49+             ;; TODO 2023-09-21: does this work...
    8.50+             (if *testing*
    8.51+                 (push-result (trace! (funcall #'rt::%test (,form ,test) ',test) *testing*))
    8.52+                 (trace! (funcall #'rt::%test (,form ,test) ',test)))))))
    8.53+
    8.54+(defmacro signals (condition-spec &body body)
    8.55+  "Generates a passing TEST-RESULT if body signals a condition of type
    8.56+CONDITION-SPEC. BODY is evaluated in a block named NIL, CONDITION-SPEC
    8.57+is not evaluated."
    8.58+  (let ((block-name (gensym)))
    8.59+    (destructuring-bind (condition &optional reason-control &rest reason-args)
    8.60+        (ensure-list condition-spec)
    8.61+      `(block ,block-name
    8.62+         (handler-bind ((,condition (lambda (c)
    8.63+                                      (declare (ignore c))
    8.64+                                      ;; ok, body threw condition
    8.65+                                      ;; TODO 2023-09-05: result collectors
    8.66+                                      ;; (add-result 'test-passed
    8.67+                                      ;;            :test-expr ',condition)
    8.68+                                      (return-from ,block-name (make-test-result :pass ',body)))))
    8.69+           (block nil
    8.70+             (locally (declare (sb-ext:muffle-conditions warning))
    8.71+               ,@body)))
    8.72+         (fail!
    8.73+          ',condition
    8.74+          ,@(if reason-control
    8.75+                `(,reason-control ,@reason-args)
    8.76+                `("Failed to signal a ~S" ',condition)))
    8.77+         (return-from ,block-name nil)))))
    8.78+
    8.79+;;; Macros
    8.80+(defmacro deftest (name props &body body)
    8.81+  "Build a test with NAME, parameterized by PROPS and with a test form of BODY.
    8.82+
    8.83+PROPS is a plist which currently accepts the following parameters:
    8.84+
    8.85+:PERSIST - re-run this test even if it passes
    8.86+
    8.87+:ARGS - nyi
    8.88+
    8.89+:PROFILE - enable profiling of this test
    8.90+
    8.91+:SKIP - don't push this test to the current *TEST-SUITE*
    8.92+
    8.93+:BENCH - enable benchmarking of this test
    8.94+
    8.95+BODY is parsed with SB-INT:PARSE-BODY and will fill in documentation
    8.96+and declarations for the test body.
    8.97+"
    8.98+  (destructuring-bind (pr doc dec fn)
    8.99+      (multiple-value-bind (forms dec doc)
   8.100+          ;; parse body with docstring allowed
   8.101+          (parse-body (or body) :documentation t :whole t)
   8.102+        `(,props ,doc ,dec ',forms))
   8.103+    ;; TODO 2023-09-21: parse plist
   8.104+    `(let ((obj (make-test
   8.105+                 :name ,(format nil "~A" name)
   8.106+                 :form ,fn
   8.107+                 ,@(when-let ((v (getf pr :persist))) `(:persist ,v))
   8.108+                 ,@(when-let ((v (getf pr :args))) `(:args ',v))
   8.109+                 ,@(when-let ((v (getf pr :bench))) `(:bench ,v))
   8.110+                 ,@(when-let ((v (getf pr :profile))) `(:profile ,v))
   8.111+                 ,@(when doc `(:doc ,doc))
   8.112+                 ,@(when dec `(:declare ,dec)))))
   8.113+       ,(unless (getf pr :skip) '(push-test obj *test-suite*))
   8.114+       obj)))
   8.115+
   8.116+(defmacro defsuite (suite-name &rest props)
   8.117+  "Define a TEST-SUITE with provided keys. The object returned can be
   8.118+enabled using the IN-SUITE macro, similiar to the DEFPACKAGE API."
   8.119+  (check-type suite-name (or symbol string))
   8.120+  `(eval-when (:compile-toplevel :load-toplevel :execute)
   8.121+     (let ((obj (make-suite
   8.122+                 :name (format nil "~A" ',suite-name)
   8.123+                 ,@(when-let ((v (getf props :stream))) `(:stream ,v)))))
   8.124+       (setq *test-suite-list* (spush obj *test-suite-list* :test #'test-name=))
   8.125+       obj)))
   8.126+
   8.127+(defmacro in-suite (name)
   8.128+  "Set *TEST-SUITE* to the TEST-SUITE object referred to by symbol
   8.129+NAME. Return the object."
   8.130+  (assert-suite name)
   8.131+  `(progn
   8.132+     (setq *test-suite* (ensure-suite ,name))))
     9.1--- a/lisp/lib/rt/tests.lisp	Fri Aug 30 21:29:55 2024 -0400
     9.2+++ b/lisp/lib/rt/tests.lisp	Sat Aug 31 22:34:56 2024 -0400
     9.3@@ -7,15 +7,8 @@
     9.4 (in-suite :rt)
     9.5 
     9.6 (deftest rt (:profile t :persist t)
     9.7-  (is (typep (make-fixture-prototype :empty nil) 'fixture-prototype))
     9.8-  (with-fixture (fx (make-fixture ((a 1) (b 2))
     9.9-		      (:+ () (+ (incf a) (incf b)))
    9.10-		      (:- () (- (decf a) (decf b)))
    9.11-		      (t () 0)))
    9.12-    (is (= 5 (funcall fx :+)))
    9.13-    (is (= 7 (funcall fx :+)))
    9.14-    (is (= -1 (funcall fx :-)))
    9.15-    (is (= 0 (funcall fx))))
    9.16+  (with-fixture (fx (:tmp :directory "/tmp/"))
    9.17+    (is fx))
    9.18   (signals (error t) (test-form (make-instance 'test-result))))
    9.19 
    9.20 (deftest flamegraph (:profile t)
    9.21@@ -48,3 +41,15 @@
    9.22   (start-coverage)
    9.23   (stop-coverage)
    9.24   (coverage-report))
    9.25+
    9.26+(deftest tmp ()
    9.27+  (is (null (with-tmp-directory ())))
    9.28+  (is (null (with-tmp-file ())))
    9.29+  (is (with-tmp-file (f1 :name "temporary-file")
    9.30+        (is (probe-file *tmp*))
    9.31+        (write-string "1 2 3 4" f1)
    9.32+        (force-output f1)
    9.33+        (is (= 7 (file-length f1)))))
    9.34+  (is (with-tmp-directory ("foobar")
    9.35+        (is (directory-path-p (probe-file *tmp*))))))
    9.36+   
    10.1--- a/lisp/lib/rt/util.lisp	Fri Aug 30 21:29:55 2024 -0400
    10.2+++ b/lisp/lib/rt/util.lisp	Sat Aug 31 22:34:56 2024 -0400
    10.3@@ -9,3 +9,144 @@
    10.4 
    10.5 ;;; Code:
    10.6 (in-package :rt)
    10.7+
    10.8+;;; tmp
    10.9+(defmacro with-tmp-directory ((&optional (name (string (gensym "tmp")))
   10.10+                                         (defaults *default-tmp-directory*))
   10.11+                              &body body)
   10.12+  `(let ((*tmp* (directory-path (merge-pathnames ,name ,defaults))))
   10.13+     (ensure-directories-exist *tmp*)
   10.14+     (unwind-protect (progn ,@body)
   10.15+       (sb-ext:delete-directory *tmp* :recursive t))))
   10.16+
   10.17+(defmacro with-tmp-file ((stream-var &key (name (string (gensym "tmp")))
   10.18+                                          type
   10.19+                                          (directory *default-tmp-directory*)
   10.20+                                          (direction :output)
   10.21+                                          (if-exists :supersede)
   10.22+                                          (element-type ''character))
   10.23+                         &body body)
   10.24+  `(let ((*tmp* (make-pathname :name ,name :type ,type :directory ,(namestring directory))))
   10.25+     (with-open-file (,stream-var *tmp* :direction ,direction :element-type ,element-type
   10.26+                                  :if-exists ,if-exists)
   10.27+       (unwind-protect (progn ,@body)
   10.28+         (delete-file *tmp*)))))
   10.29+
   10.30+;;; random
   10.31+(defvar *simple-charset* "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
   10.32+
   10.33+(defun random-elt (seq)
   10.34+  (elt seq (random (length seq))))
   10.35+
   10.36+(defun random-ref (vec)
   10.37+  (aref vec (random (length vec))))
   10.38+
   10.39+(defun random-char ()
   10.40+  (random-ref *simple-charset*))
   10.41+
   10.42+(defun random-chars (dim)
   10.43+  (let ((r (make-array dim :element-type 'character)))
   10.44+    (dotimes (i (array-total-size r) r)
   10.45+      (setf (row-major-aref r i) (random-char)))))
   10.46+
   10.47+(defun random-byte () (random 255))
   10.48+
   10.49+(defun random-bytes (dim)
   10.50+  (let ((r (make-array dim :element-type 'octet)))
   10.51+    (dotimes (i (array-total-size r) r)
   10.52+      (setf (row-major-aref r i) (random-byte)))))
   10.53+
   10.54+(eval-when (:compile-toplevel :load-toplevel :execute)
   10.55+  (defun make-test (&rest slots)
   10.56+    (apply #'make-instance 'test slots))
   10.57+  (defun make-suite (&rest slots)
   10.58+    (apply #'make-instance 'test-suite slots)))
   10.59+
   10.60+;; TODO 2023-09-04: optimize
   10.61+;;(declaim (inline do-tests))
   10.62+(defun do-tests (&optional (suite *test-suite*) force (output *standard-output*))
   10.63+  (if (pathnamep output)
   10.64+      (with-open-file (stream output :direction :output)
   10.65+        (do-suite (ensure-suite suite) :stream stream :force force))
   10.66+      (do-suite (ensure-suite suite) :stream output :force force)))
   10.67+
   10.68+(defvar *test-output-mutex* (sb-thread:make-mutex :name "tests-output"))
   10.69+
   10.70+;; TODO
   10.71+(defun do-tests-concurrently (&optional (suite *test-suite*) force (output *standard-output*))
   10.72+  (declare (ignore suite force))
   10.73+  (sb-thread:with-mutex (*test-output-mutex*)
   10.74+    (let ((stream (make-synonym-stream output)))
   10.75+      (let ((*standard-output* stream)
   10.76+            (*error-output* stream))
   10.77+        (nyi!)))))
   10.78+
   10.79+(defun reset-tests ()
   10.80+  (setq *testing* nil
   10.81+        *test-suite* nil
   10.82+        *test-suite-list* nil
   10.83+        *test-input* nil))
   10.84+
   10.85+;; this assumes that *test-suite* is re-initialized correctly to the
   10.86+;; correct test-suite object.
   10.87+(defun continue-testing ()
   10.88+  (if-let ((test *testing*))
   10.89+    (throw '%in-test test)
   10.90+    (do-suite *test-suite*)))
   10.91+
   10.92+;; NOTE 2023-09-01: `pushnew' does not return an indication of whether
   10.93+;; place is changed - it returns place. This is functionally sound but
   10.94+;; means that if we want to do something else in the event that place
   10.95+;; is unchanged, we run into some friction,
   10.96+;; https://stackoverflow.com/questions/56228832/adapting-common-lisp-pushnew-to-return-success-failure
   10.97+(defun spush (item lst &key (test #'equal))
   10.98+  "Substituting `push'"
   10.99+  (declare (type function test))
  10.100+  (cond
  10.101+    ((null lst) (push item lst))
  10.102+    ((list lst)
  10.103+     (if-let ((found (member item lst
  10.104+                             :test test)))
  10.105+       (progn
  10.106+         (rplaca found item)
  10.107+         lst)
  10.108+       (push item lst)))
  10.109+    #|(or nil '(t (cons item lst)))|#))
  10.110+
  10.111+;; FIX 2023-08-31: spush, replace with `add-test' method.
  10.112+;; (declaim (inline normalize-test-name))
  10.113+(defun normalize-test-name (a)
  10.114+  "Return the normalized `test-suite-designator' of A."
  10.115+  (etypecase a
  10.116+    (string (string-upcase a))
  10.117+    (symbol (symbol-name a))
  10.118+    (test-object (normalize-test-name (test-name a)))
  10.119+    (t (format nil "~A" a))))
  10.120+
  10.121+(defun test-name= (a b)
  10.122+  "Return t if A and B are similar `test-suite-designator's."
  10.123+  (let ((a (normalize-test-name a))
  10.124+        (b (normalize-test-name b)))
  10.125+    (string= a b)))
  10.126+
  10.127+;; (declaim (inline assert-suite ensure-suite))
  10.128+(defun ensure-suite (name)
  10.129+  (if-let ((ok (member name *test-suite-list* :test #'test-name=)))
  10.130+    (car ok)
  10.131+    (when (or (eq name t) (null name)) (make-suite :name *default-test-suite-name*))))
  10.132+
  10.133+(defun check-suite-designator (suite) (check-type suite test-suite-designator))
  10.134+
  10.135+(defun assert-suite (name)
  10.136+  (check-suite-designator name)
  10.137+  (assert (ensure-suite name)))
  10.138+
  10.139+(declaim (inline test-opt-key-p test-opt-valid-p))
  10.140+(defun test-opt-key-p (k)
  10.141+  "Test if K is a `test-opt-key'."
  10.142+  (member k '(:profile :save :stream)))
  10.143+
  10.144+(defun test-opt-valid-p (f)
  10.145+  "Test if F is a valid `test-opt' form. If so, return F else nil."
  10.146+  (when (test-opt-key-p (car f))
  10.147+    f))
    11.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2+++ b/lisp/lib/rt/var.lisp	Sat Aug 31 22:34:56 2024 -0400
    11.3@@ -0,0 +1,27 @@
    11.4+;;; var.lisp --- Test Variables
    11.5+
    11.6+;; 
    11.7+
    11.8+;;; Code:
    11.9+(in-package :rt)
   11.10+
   11.11+;;; Vars
   11.12+(defvar *test-opts* '(optimize sb-c::instrument-consing))
   11.13+(defvar *compile-tests* nil
   11.14+  "When nil do not compile tests. With a value of t, tests are compiled
   11.15+with default optimizations else the value is used to configure
   11.16+compiler optimizations.")
   11.17+(defvar *catch-test-errors* t "When non-nil, cause errors in a test to be caught.")
   11.18+(defvar *test-suffix* "-TEST" "A suffix to append to every `test' defined with `deftest'.")
   11.19+(defvar *test-suite-list* nil "List of available `test-suite' objects.")
   11.20+(defvar *test-suite* nil "A 'test-suite-designator' which identifies the current `test-suite'.")
   11.21+(eval-when (:compile-toplevel :load-toplevel :execute)
   11.22+  (defvar *default-test-suite-name* "default"))
   11.23+(declaim (type (or stream boolean string) *test-input*))
   11.24+(defvar *test-input* nil "When non-nil, specifies an input stream or buffer for `*testing*'.")
   11.25+(defvar *testing* nil "Testing state var.")
   11.26+(defvar *default-tmp-directory* #P"/tmp/")
   11.27+(defvar *tmp* *default-tmp-directory*)
   11.28+;; TODO 2024-08-31:
   11.29+(defvar *test-on-definition* nil
   11.30+  "Special variable indicating whether to run tests as soon as they are defined.")
    12.1--- a/lisp/lib/skel/tests.lisp	Fri Aug 30 21:29:55 2024 -0400
    12.2+++ b/lisp/lib/skel/tests.lisp	Sat Aug 31 22:34:56 2024 -0400
    12.3@@ -1,6 +1,6 @@
    12.4 ;;; skel/tests.lisp --- skel tests
    12.5 (defpackage :skel/tests
    12.6-  (:use :cl :skel :rt :log :obj :dat/sxp)
    12.7+  (:use :cl :skel :rt :log :obj :dat/sxp :std/path)
    12.8   (:import-from :uiop :file-exists-p))
    12.9 
   12.10 (in-package :skel/tests)
   12.11@@ -8,13 +8,7 @@
   12.12 (defsuite :skel)
   12.13 (in-suite :skel)
   12.14 
   12.15-(defvar %tmp)
   12.16-(defun tmp-path (ext)
   12.17-  (setq %tmp (format nil "/tmp/~A.~A" (gensym) ext)))
   12.18-
   12.19-(defun do-tmp-path (file &rest body)
   12.20-  (prog1 body
   12.21-    (when (file-exists-p file) (delete-file file))))
   12.22+(defun tmp-path (ext) (make-pathname :name (namestring (tmpize-pathname (string (gensym "g")))) :type ext))
   12.23 
   12.24 (deftest header-comments ()
   12.25   "Make sure header comments are generated correctly. 
   12.26@@ -35,24 +29,21 @@
   12.27 (deftest skelfile ()
   12.28   "Ensure skelfiles are created and loaded correctly and that they signal
   12.29 the appropriate restarts."
   12.30-  (do-tmp-path (tmp-path "sk")
   12.31+  (with-tmp-file (f :type "sk")
   12.32     (is (sk-write-file
   12.33-         (make-instance 'sk-project :name "nada" :path "test" :vc :hg) :path %tmp :if-exists :supersede))
   12.34-    (ignore-errors (delete-file %tmp))
   12.35-    (setf %tmp (tmp-path "sk"))
   12.36-    (is (init-skelfile %tmp))
   12.37-    (is (load-skelfile %tmp))
   12.38-    (is (build-ast (sk-read-file (make-instance 'sk-project) %tmp)))))
   12.39+         (make-instance 'sk-project :name "nada" :path "test" :vc :hg) :path *tmp* :if-exists :supersede))
   12.40+    (is (load-skelfile *tmp*))
   12.41+    (is (build-ast (sk-read-file (make-instance 'sk-project) *tmp*)))))
   12.42 
   12.43 (deftest skelrc ()
   12.44   "Ensure skelrc files are created and loaded correctly."
   12.45-  (do-tmp-path (tmp-path "skrc")))
   12.46+  (with-tmp-file (f :name "" :type "skelrc")))
   12.47 
   12.48 (deftest makefile ()
   12.49   "Make sure makefiles are making out ok."
   12.50-    (do-tmp-path (tmp-path "mk")
   12.51+    (with-tmp-file (f :name "" :type "mk")
   12.52       (flet ((mk (&optional path) (make-instance 'makefile :name (gensym)
   12.53-							   :path (or path (pathname %tmp)) :description "barfood"))
   12.54+							   :path (or path (pathname *tmp*)) :description "barfood"))
   12.55 	     (src (path) (list path))
   12.56 	     (cmd (body) (make-instance 'sk-command :body body))
   12.57 	     (rule (tr sr) (make-sk-rule tr sr nil)))