1 ;;;; Proposed new file in clsql-tests to enable abstracting datasets for reuse.
3 ;;;; The core is def-datset and with-dataset that respectively define a set,
4 ;;;; and enable one for a dynamic scope. Datasets will normally be setup and
5 ;;;; torn down in the scope of one test, which may impose a computation
6 ;;;; overhead, but enables simpler tests by not worrying about side-effects
9 ;;;; In general datasets should be database agnostic, but because the code
10 ;;;; is only run in the scope of a test, if a test is excluded for a backend
11 ;;;; or some other reason then it is never run hence doesn't cause problems.
13 (in-package #:clsql-tests)
15 (defparameter *dataset-debug-on-error* nil
16 "If we get an error while loading or cleaning up the dataset,
17 should we debug (T) or just print and quit.")
19 (defun generic-error (e)
20 (when (and *dataset-debug-on-error*
23 (fresh-line *error-output*)
24 (princ e *error-output*)
25 (throw 'quit-dataset e))
27 (defmacro def-dataset (name &body body)
29 ;;probably just shove this into a param, perhaps a marginal
30 ;; bit of processing first.
31 `(defparameter ,name ',body))
33 (defmacro with-dataset (name &body body)
34 "Use a dataset in a dynamic scope, e.g. a single test.
39 3. :cleanup always happens"
43 (restart-case (%dataset-init ,name)
44 (retry-dataset-init ()
45 :report ,(format nil "Retry dataset('~a) init: (with any dataset changes)"
47 (%dataset-init ,name))
49 :report "FAIL and run the next test"
50 (throw 'quit-dataset :data-set-failure)))
52 (%dataset-cleanup ,name))))
55 (defun %dataset-dispatch (arg)
56 "For use with def-dataset and with-dataset, tries to DWIM."
58 (string (clsql-sys:execute-command arg)) ;treat it as a sql command.
59 ((or function symbol) (funcall arg)) ;run functions
62 ((function lambda) (%dataset-dispatch (eval arg))) ;#' forms, lambdas
63 (progn (mapc #'%dataset-dispatch (rest arg))) ; (progn "asdf" "ff")
64 (ignore-errors (ignore-errors (mapc #'%dataset-dispatch (rest arg))))
65 (t (mapc #'%dataset-dispatch arg))) ;otherwise implicit progn
68 (defun %dataset-init (name)
69 "Run initialization code and fill database for given dataset."
71 ((error #'generic-error))
72 ;;find items that looks like '(:setup ...),
74 (let ((setup (rest (find :setup name :key #'first)))
75 (sqldata (rest (find :sqldata name :key #'first)))
76 (objdata (rest (find :objdata name :key #'first))))
78 (%dataset-dispatch setup))
80 ;;run raw sql insert statements
81 (destructuring-bind (table-name columns &rest values-list) sqldata
82 (dolist (v values-list)
83 (clsql-sys:execute-command
85 "INSERT INTO ~a (~a) VALUES (~a)"
86 table-name columns v)))))
88 ;;presumed to be view-class objects, force them to insert.
90 (setf (slot-value o 'clsql-sys::view-database) nil)
91 (clsql-sys:update-records-from-instance o))))))
93 (defun %dataset-cleanup (name)
94 "Run cleanup code associated with the given dataset."
96 (handler-bind ((error #'generic-error))
97 (let ((cleanup (rest (find :cleanup name :key #'first))))
99 (%dataset-dispatch cleanup))))
100 (retry-dataset-cleanup ()
101 :report "Retry dataset cleanup (with any dataset changes)"
102 (%dataset-cleanup name))
103 (skip-cleanup () nil)))
106 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
107 ;;; Example Test Code
109 ;;incomplete example taken from test-init
110 ;; (def-dataset *employees*
111 ;; (:setup "CREATE TABLE employee
113 ;; emplid integer NOT NULL,
114 ;; groupid integer NOT NULL,
115 ;; first_name character varying(30),
116 ;; last_name character varying(30),
117 ;; email character varying(100),
118 ;; ecompanyid integer,
119 ;; managerid integer,
122 ;; birthday timestamp without time zone,
124 ;; CONSTRAINT employeepk PRIMARY KEY (emplid, groupid),
125 ;; CONSTRAINT employee_emplid_key UNIQUE (emplid)
128 ;; ;;alternatively setup can still be done as
129 ;; ;;(:setup #'(lambda () (create-view-from-class ...)))
130 ;; (:sqldata "employees" "emplid,groupid,married,height,first_name,last_name"
131 ;; "1,1,false,1.5,'Napolean', 'Bonaparte'"
132 ;; (format nil "1,1,true,~a,'Vladimir','Lenin'" (1+ (random 1.00))))
133 ;; (:cleanup "DROP TABLE EMPLOYEES"))