r9199: fold clsql-base and clsql-base-sys into clsql-base
[clsql.git] / tests / test-init.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; ======================================================================
3 ;;;; File:    test-init.lisp
4 ;;;; Authors: Marcus Pearce <m.t.pearce@city.ac.uk>, Kevin Rosenberg
5 ;;;; Created: 30/03/2004
6 ;;;; Updated: $Id$
7 ;;;;
8 ;;;; Initialisation utilities for running regression tests on CLSQL. 
9 ;;;;
10 ;;;; This file is part of CLSQL.
11 ;;;;
12 ;;;; CLSQL users are granted the rights to distribute and use this software
13 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
14 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
15 ;;;; ======================================================================
16
17 (in-package #:clsql-tests)
18
19 (defvar *report-stream* nil "Stream to send text report.")
20 (defvar *sexp-report-stream* nil "Stream to send sexp report.")
21 (defvar *rt-connection*)
22 (defvar *rt-fddl*)
23 (defvar *rt-fdml*)
24 (defvar *rt-ooddl*)
25 (defvar *rt-oodml*)
26 (defvar *rt-syntax*)
27 (defvar *rt-time*)
28
29 (defvar *test-database-type* nil)
30 (defvar *test-database-underlying-type* nil)
31 (defvar *test-database-user* nil)
32
33 (defclass thing ()
34   ((extraterrestrial :initform nil :initarg :extraterrestrial)))
35
36 (def-view-class person (thing)
37   ((height :db-kind :base :accessor height :type float
38            :initarg :height)
39    (married :db-kind :base :accessor married :type boolean
40             :initarg :married)
41    (birthday :type clsql-base:wall-time :initarg :birthday)
42    (hobby :db-kind :virtual :initarg :hobby :initform nil)))
43   
44 (def-view-class employee (person)
45   ((emplid
46     :db-kind :key
47     :db-constraints :not-null
48     :type integer
49     :initarg :emplid)
50    (groupid
51     :db-kind :key
52     :db-constraints :not-null
53     :type integer
54     :initarg :groupid)
55    (first-name
56     :accessor first-name
57     :type (string 30)
58     :initarg :first-name)
59    (last-name
60     :accessor last-name
61     :type (string 30)
62     :initarg :last-name)
63    (email
64     :accessor employee-email
65     :type (string 100)
66     :initarg :email)
67    (companyid
68     :type integer)
69    (company
70     :accessor employee-company
71     :db-kind :join
72     :db-info (:join-class company
73                           :home-key companyid
74                           :foreign-key companyid
75                           :set nil))
76    (managerid
77     :type integer)
78    (manager
79     :accessor employee-manager
80     :db-kind :join
81     :db-info (:join-class employee
82                           :home-key managerid
83                           :foreign-key emplid
84                           :set nil)))
85   (:base-table employee))
86
87 (def-view-class company ()
88   ((companyid
89     :db-kind :key
90     :db-constraints :not-null
91     :type integer
92     :initarg :companyid)
93    (groupid
94     :db-kind :key
95     :db-constraints :not-null
96     :type integer
97     :initarg :groupid)
98    (name
99     :type (string 100)
100     :initarg :name)
101    (presidentid
102     :type integer)
103    (president
104     :reader president
105     :db-kind :join
106     :db-info (:join-class employee
107                           :home-key presidentid
108                           :foreign-key emplid
109                           :set nil))
110    (employees
111     :reader company-employees
112     :db-kind :join
113     :db-info (:join-class employee
114                           :home-key (companyid groupid)
115                           :foreign-key (companyid groupid)
116                           :set t)))
117   (:base-table company))
118
119
120
121 (defun test-connect-to-database (db-type spec)
122   (when (db-backend-has-create/destroy-db? db-type)
123     (ignore-errors (destroy-database spec :database-type db-type))
124     (ignore-errors (create-database spec :database-type db-type)))
125   
126   (setf *test-database-type* db-type)
127   (when (>= (length spec) 3)
128     (setq *test-database-user* (third spec)))
129   
130   ;; Connect to the database
131   (clsql:connect spec
132                  :database-type db-type
133                  :make-default t
134                  :if-exists :old)
135   
136   ;; Ensure database is empty
137   (truncate-database :database *default-database*)
138   
139   (setf *test-database-underlying-type*
140         (clsql-sys:database-underlying-type *default-database*))
141   
142   *default-database*)
143
144 (defparameter company1 nil)
145 (defparameter employee1 nil)
146 (defparameter employee2 nil)
147 (defparameter employee3 nil)
148 (defparameter employee4 nil)
149 (defparameter employee5 nil)
150 (defparameter employee6 nil)
151 (defparameter employee7 nil)
152 (defparameter employee8 nil)
153 (defparameter employee9 nil)
154 (defparameter employee10 nil)
155
156 (defun test-initialise-database ()
157   (test-basic-initialize)
158   
159   (clsql:create-view-from-class 'employee)
160   (clsql:create-view-from-class 'company)
161
162   (setf company1 (make-instance 'company
163                    :companyid 1
164                    :groupid 1
165                    :name "Widgets Inc.")
166         employee1 (make-instance 'employee
167                     :emplid 1
168                     :groupid 1
169                     :married t 
170                     :height (1+ (random 1.00))
171                     :birthday (clsql-base:get-time)
172                     :first-name "Vladamir"
173                     :last-name "Lenin"
174                     :email "lenin@soviet.org")
175         employee2 (make-instance 'employee
176                     :emplid 2
177                     :groupid 1
178                     :height (1+ (random 1.00))
179                     :married t 
180                     :birthday (clsql-base:get-time)
181                     :first-name "Josef"
182                     :last-name "Stalin"
183                     :email "stalin@soviet.org")
184         employee3 (make-instance 'employee
185                     :emplid 3
186                     :groupid 1
187                     :height (1+ (random 1.00))
188                     :married t 
189                     :birthday (clsql-base:get-time)
190                     :first-name "Leon"
191                     :last-name "Trotsky"
192                     :email "trotsky@soviet.org")
193         employee4 (make-instance 'employee
194                     :emplid 4
195                     :groupid 1
196                     :height (1+ (random 1.00))
197                     :married nil
198                     :birthday (clsql-base:get-time)
199                     :first-name "Nikita"
200                     :last-name "Kruschev"
201                     :email "kruschev@soviet.org")
202         
203         employee5 (make-instance 'employee
204                     :emplid 5
205                     :groupid 1
206                     :married nil
207                     :height (1+ (random 1.00))
208                     :birthday (clsql-base:get-time)
209                     :first-name "Leonid"
210                     :last-name "Brezhnev"
211                     :email "brezhnev@soviet.org")
212
213         employee6 (make-instance 'employee
214                     :emplid 6
215                     :groupid 1
216                     :married nil
217                     :height (1+ (random 1.00))
218                     :birthday (clsql-base:get-time)
219                     :first-name "Yuri"
220                     :last-name "Andropov"
221                     :email "andropov@soviet.org")
222         employee7 (make-instance 'employee
223                     :emplid 7
224                     :groupid 1
225                     :height (1+ (random 1.00))
226                     :married nil
227                     :birthday (clsql-base:get-time)
228                     :first-name "Konstantin"
229                     :last-name "Chernenko"
230                     :email "chernenko@soviet.org")
231         employee8 (make-instance 'employee
232                     :emplid 8
233                     :groupid 1
234                     :height (1+ (random 1.00))
235                     :married nil
236                     :birthday (clsql-base:get-time)
237                     :first-name "Mikhail"
238                     :last-name "Gorbachev"
239                     :email "gorbachev@soviet.org")
240         employee9 (make-instance 'employee
241                     :emplid 9
242                     :groupid 1 
243                     :married nil
244                     :height (1+ (random 1.00))
245                     :birthday (clsql-base:get-time)
246                     :first-name "Boris"
247                     :last-name "Yeltsin"
248                     :email "yeltsin@soviet.org")
249         employee10 (make-instance 'employee
250                      :emplid 10
251                      :groupid 1
252                      :married nil
253                      :height (1+ (random 1.00))
254                      :birthday (clsql-base:get-time)
255                      :first-name "Vladamir"
256                      :last-name "Putin"
257                      :email "putin@soviet.org"))
258
259   ;; sleep to ensure birthdays are no longer at current time
260   (sleep 2) 
261
262   ;; Lenin manages everyone
263   (clsql:add-to-relation employee2 'manager employee1)
264   (clsql:add-to-relation employee3 'manager employee1)
265   (clsql:add-to-relation employee4 'manager employee1)
266   (clsql:add-to-relation employee5 'manager employee1)
267   (clsql:add-to-relation employee6 'manager employee1)
268   (clsql:add-to-relation employee7 'manager employee1)
269   (clsql:add-to-relation employee8 'manager employee1)
270   (clsql:add-to-relation employee9 'manager employee1)
271   (clsql:add-to-relation employee10 'manager employee1)
272   ;; Everyone works for Widgets Inc.
273   (clsql:add-to-relation company1 'employees employee1)
274   (clsql:add-to-relation company1 'employees employee2)
275   (clsql:add-to-relation company1 'employees employee3)
276   (clsql:add-to-relation company1 'employees employee4)
277   (clsql:add-to-relation company1 'employees employee5)
278   (clsql:add-to-relation company1 'employees employee6)
279   (clsql:add-to-relation company1 'employees employee7)
280   (clsql:add-to-relation company1 'employees employee8)
281   (clsql:add-to-relation company1 'employees employee9)
282   (clsql:add-to-relation company1 'employees employee10)
283   ;; Lenin is president of Widgets Inc.
284   (clsql:add-to-relation company1 'president employee1)
285   ;; store these instances 
286   (clsql:update-records-from-instance employee1)
287   (clsql:update-records-from-instance employee2)
288   (clsql:update-records-from-instance employee3)
289   (clsql:update-records-from-instance employee4)
290   (clsql:update-records-from-instance employee5)
291   (clsql:update-records-from-instance employee6)
292   (clsql:update-records-from-instance employee7)
293   (clsql:update-records-from-instance employee8)
294   (clsql:update-records-from-instance employee9)
295   (clsql:update-records-from-instance employee10)
296   (clsql:update-records-from-instance company1))
297
298 (defvar *error-count* 0)
299 (defvar *error-list* nil)
300
301 (defun run-tests-append-report-file (report-file)
302   (let* ((report-path (etypecase report-file
303                         (pathname report-file)
304                         (string (parse-namestring report-file))))
305          (sexp-report-path (make-pathname :defaults report-path
306                                           :type "sexp")))
307   (with-open-file (rs report-path :direction :output
308                       :if-exists :append
309                       :if-does-not-exist :create)
310     (with-open-file (srs sexp-report-path :direction :output
311                          :if-exists :append
312                          :if-does-not-exist :create)
313       (run-tests :report-stream rs :sexp-report-stream srs)))))
314     
315 (defun run-tests (&key (report-stream *standard-output*) (sexp-report-stream nil))
316   (let ((specs (read-specs))
317         (*report-stream* report-stream)
318         (*sexp-report-stream* sexp-report-stream)
319         (*error-count* 0)
320         (*error-list* nil))
321     (unless specs
322       (warn "Not running tests because test configuration file is missing")
323       (return-from run-tests :skipped))
324     (load-necessary-systems specs)
325     (dolist (db-type +all-db-types+)
326       (dolist (spec (db-type-spec db-type specs))
327         (do-tests-for-backend db-type spec))))
328   (zerop *error-count*))
329
330 (defun load-necessary-systems (specs)
331   (dolist (db-type +all-db-types+)
332     (when (db-type-spec db-type specs)
333       (clsql:initialize-database-type :database-type db-type))))
334
335 (defun do-tests-for-backend (db-type spec)
336   (test-connect-to-database db-type spec)
337
338   (unwind-protect
339       (multiple-value-bind (test-forms skip-tests)
340           (compute-tests-for-backend db-type *test-database-underlying-type*)
341         
342         (format *report-stream* 
343                 "~&
344 ******************************************************************************
345 ***     CLSQL Test Suite begun at ~A
346 ***     ~A
347 ***     ~A on ~A
348 ***     Database ~A backend~A.
349 ******************************************************************************
350
351                 (clsql-base:format-time 
352                  nil 
353                  (clsql-base:utime->time (get-universal-time)))
354                 (lisp-implementation-type)
355                 (lisp-implementation-version)
356                 (machine-type)
357                 db-type
358                 (if (not (eq db-type *test-database-underlying-type*))
359                     (format nil " with underlying type ~A" 
360                             *test-database-underlying-type*)
361                   "")
362                 )
363         
364         (test-initialise-database)
365         
366         (regression-test:rem-all-tests)
367         (dolist (test-form test-forms)
368           (eval test-form))
369         
370         (let ((remaining (regression-test:do-tests *report-stream*)))
371           (when (regression-test:pending-tests)
372             (incf *error-count* (length remaining))))
373         
374         (let ((sexp-error (list db-type 
375                                 *test-database-underlying-type* 
376                                 (get-universal-time)
377                                 (length test-forms)
378                                 (regression-test:pending-tests)
379                                 (lisp-implementation-type) 
380                                 (lisp-implementation-version)
381                                 (machine-type))))
382           (when *sexp-report-stream*
383             (write sexp-error :stream *sexp-report-stream*)) 
384           (push sexp-error *error-list*))
385         
386         (format *report-stream* "~&Tests skipped:")
387         (if skip-tests
388             (dolist (skipped skip-tests)
389               (format *report-stream*
390                       "~&   ~20A ~A~%" (car skipped) (cdr skipped)))
391           (format *report-stream* " None~%")))
392     (disconnect)))
393
394
395 (defun compute-tests-for-backend (db-type db-underlying-type)
396   (let ((test-forms '())
397         (skip-tests '()))
398     (dolist (test-form (append (test-basic-forms)
399                                *rt-connection* *rt-fddl* *rt-fdml*
400                                *rt-ooddl* *rt-oodml* *rt-syntax*))
401       (let ((test (second test-form)))
402         (cond
403           ((and (null (db-type-has-views? db-underlying-type))
404                 (clsql-base::in test :fddl/view/1 :fddl/view/2 :fddl/view/3 :fddl/view/4))
405            (push (cons test "views not supported") skip-tests))
406           ((and (null (db-type-has-boolean-where? db-underlying-type))
407                 (clsql-base::in test :fdml/select/11 :oodml/select/5))
408            (push (cons test "boolean where not supported") skip-tests))
409           ((and (null (db-type-has-subqueries? db-underlying-type))
410                 (clsql-base::in test :fdml/select/5 :fdml/select/10))
411            (push (cons test "subqueries not supported") skip-tests))
412           ((and (null (db-type-transaction-capable? db-underlying-type
413                                                     *default-database*))
414                 (clsql-base::in test :fdml/transaction/1 :fdml/transaction/2 :fdml/transaction/3 :fdml/transaction/4))
415            (push (cons test "transactions not supported") skip-tests))
416           ((and (null (db-type-has-fancy-math? db-underlying-type))
417                 (clsql-base::in test :fdml/select/1))
418            (push (cons test "fancy math not supported") skip-tests))
419           ((and (eql *test-database-type* :sqlite)
420                 (clsql-base::in test :fddl/view/4 :fdml/select/10))
421            (push (cons test "not supported by sqlite") skip-tests))
422           (t
423            (push test-form test-forms)))))
424     (values (nreverse test-forms) (nreverse skip-tests))))
425