;;;;
;;;; Initialisation utilities for running regression tests on CLSQL.
;;;;
+;;;; This file is part of CLSQL.
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
;;;; ======================================================================
(in-package #:clsql-tests)
+(defvar *report-stream* nil "Stream to send text report.")
+(defvar *sexp-report-stream* nil "Stream to send sexp report.")
(defvar *rt-connection*)
(defvar *rt-fddl*)
(defvar *rt-fdml*)
(defvar *rt-ooddl*)
(defvar *rt-oodml*)
(defvar *rt-syntax*)
+(defvar *rt-time*)
(defvar *test-database-type* nil)
+(defvar *test-database-underlying-type* nil)
(defvar *test-database-user* nil)
(defclass thing ()
((extraterrestrial :initform nil :initarg :extraterrestrial)))
(def-view-class person (thing)
- ((height :db-kind :base :accessor height :type float :nulls-ok t
+ ((height :db-kind :base :accessor height :type float
:initarg :height)
- (married :db-kind :base :accessor married :type boolean :nulls-ok t
+ (married :db-kind :base :accessor married :type boolean
:initarg :married)
- (birthday :nulls-ok t :type clsql-base:wall-time :initarg :birthday)
+ (birthday :type clsql-base:wall-time :initarg :birthday)
(hobby :db-kind :virtual :initarg :hobby :initform nil)))
(def-view-class employee (person)
((emplid
:db-kind :key
:db-constraints :not-null
- :nulls-ok nil
:type integer
:initarg :emplid)
(groupid
:db-kind :key
:db-constraints :not-null
- :nulls-ok nil
:type integer
:initarg :groupid)
(first-name
(email
:accessor employee-email
:type (string 100)
- :nulls-ok t
:initarg :email)
(companyid
:type integer)
:foreign-key companyid
:set nil))
(managerid
- :type integer
- :nulls-ok t)
+ :type integer)
(manager
:accessor employee-manager
:db-kind :join
(def-view-class company ()
((companyid
- :db-type :key
+ :db-kind :key
:db-constraints :not-null
:type integer
:initarg :companyid)
(groupid
- :db-type :key
+ :db-kind :key
:db-constraints :not-null
:type integer
:initarg :groupid)
:set t)))
(:base-table company))
-(defparameter company1 (make-instance 'company
- :companyid 1
- :groupid 1
- :name "Widgets Inc."))
-
-(defparameter employee1 (make-instance 'employee
- :emplid 1
- :groupid 1
- :married t
- :height (1+ (random 1.00))
- :birthday (clsql-base:get-time)
- :first-name "Vladamir"
- :last-name "Lenin"
- :email "lenin@soviet.org"))
-
-(defparameter employee2 (make-instance 'employee
- :emplid 2
- :groupid 1
- :height (1+ (random 1.00))
- :married t
- :birthday (clsql-base:get-time)
- :first-name "Josef"
- :last-name "Stalin"
- :email "stalin@soviet.org"))
-
-(defparameter employee3 (make-instance 'employee
- :emplid 3
- :groupid 1
- :height (1+ (random 1.00))
- :married t
- :birthday (clsql-base:get-time)
- :first-name "Leon"
- :last-name "Trotsky"
- :email "trotsky@soviet.org"))
-
-(defparameter employee4 (make-instance 'employee
- :emplid 4
- :groupid 1
- :height (1+ (random 1.00))
- :married nil
- :birthday (clsql-base:get-time)
- :first-name "Nikita"
- :last-name "Kruschev"
- :email "kruschev@soviet.org"))
-
-(defparameter employee5 (make-instance 'employee
- :emplid 5
- :groupid 1
- :married nil
- :height (1+ (random 1.00))
- :birthday (clsql-base:get-time)
- :first-name "Leonid"
- :last-name "Brezhnev"
- :email "brezhnev@soviet.org"))
-
-(defparameter employee6 (make-instance 'employee
- :emplid 6
- :groupid 1
- :married nil
- :height (1+ (random 1.00))
- :birthday (clsql-base:get-time)
- :first-name "Yuri"
- :last-name "Andropov"
- :email "andropov@soviet.org"))
-
-(defparameter employee7 (make-instance 'employee
- :emplid 7
- :groupid 1
- :height (1+ (random 1.00))
- :married nil
- :birthday (clsql-base:get-time)
- :first-name "Konstantin"
- :last-name "Chernenko"
- :email "chernenko@soviet.org"))
-
-(defparameter employee8 (make-instance 'employee
- :emplid 8
- :groupid 1
- :height (1+ (random 1.00))
- :married nil
- :birthday (clsql-base:get-time)
- :first-name "Mikhail"
- :last-name "Gorbachev"
- :email "gorbachev@soviet.org"))
-(defparameter employee9 (make-instance 'employee
- :emplid 9
- :groupid 1
- :married nil
- :height (1+ (random 1.00))
- :birthday (clsql-base:get-time)
- :first-name "Boris"
- :last-name "Yeltsin"
- :email "yeltsin@soviet.org"))
-
-(defparameter employee10 (make-instance 'employee
- :emplid 10
- :groupid 1
- :married nil
- :height (1+ (random 1.00))
- :birthday (clsql-base:get-time)
- :first-name "Vladamir"
- :last-name "Putin"
- :email "putin@soviet.org"))
+(def-view-class address ()
+ ((emplid
+ :db-kind :key
+ :db-constraints :not-null
+ :type integer
+ :initarg :emplid)
+ (street-number
+ :type integer
+ :initarg :street-number)
+ (street-name
+ :type (string 30)
+ :void-value ""
+ :initarg :street-name)
+ (city
+ :column "city_field"
+ :void-value "no city"
+ :type (string 30)
+ :initarg :city)
+ (postal-code
+ :column "zip"
+ :type integer
+ :void-value 0
+ :initarg :postal-code)))
-(defun test-connect-to-database (database-type spec)
- (setf *test-database-type* database-type)
+(defun test-connect-to-database (db-type spec)
+ (when (db-backend-has-create/destroy-db? db-type)
+ (ignore-errors (destroy-database spec :database-type db-type))
+ (ignore-errors (create-database spec :database-type db-type)))
+
+ (setf *test-database-type* db-type)
(when (>= (length spec) 3)
(setq *test-database-user* (third spec)))
-
+
;; Connect to the database
(clsql:connect spec
- :database-type database-type
- :make-default t
- :if-exists :old))
+ :database-type db-type
+ :make-default t
+ :if-exists :old)
+
+ ;; Ensure database is empty
+ (truncate-database :database *default-database*)
+
+ (setf *test-database-underlying-type*
+ (clsql:database-underlying-type *default-database*))
+
+ *default-database*)
-(defmacro with-ignore-errors (&rest forms)
- `(progn
- ,@(mapcar
- (lambda (x) (list 'ignore-errors x))
- forms)))
+(defparameter company1 nil)
+(defparameter employee1 nil)
+(defparameter employee2 nil)
+(defparameter employee3 nil)
+(defparameter employee4 nil)
+(defparameter employee5 nil)
+(defparameter employee6 nil)
+(defparameter employee7 nil)
+(defparameter employee8 nil)
+(defparameter employee9 nil)
+(defparameter employee10 nil)
+(defparameter address1 nil)
+(defparameter address2 nil)
(defun test-initialise-database ()
- ;; Delete the instance records
- (with-ignore-errors
- (clsql:delete-instance-records company1)
- (clsql:delete-instance-records employee1)
- (clsql:delete-instance-records employee2)
- (clsql:delete-instance-records employee3)
- (clsql:delete-instance-records employee4)
- (clsql:delete-instance-records employee5)
- (clsql:delete-instance-records employee6)
- (clsql:delete-instance-records employee7)
- (clsql:delete-instance-records employee8)
- (clsql:delete-instance-records employee9)
- (clsql:delete-instance-records employee10)
- ;; Drop the required tables if they exist
- (clsql:drop-view-from-class 'employee)
- (clsql:drop-view-from-class 'company))
- ;; Create the tables for our view classes
- (clsql:create-view-from-class 'employee)
- (clsql:create-view-from-class 'company)
+ (test-basic-initialize)
+
+ (let ((*backend-warning-behavior*
+ (if (member *test-database-type* '(:postgresql :postgresql-socket))
+ :ignore
+ :warn)))
+ (clsql:create-view-from-class 'employee)
+ (clsql:create-view-from-class 'company)
+ (clsql:create-view-from-class 'address))
+
+ (setf company1 (make-instance 'company
+ :companyid 1
+ :groupid 1
+ :name "Widgets Inc.")
+ employee1 (make-instance 'employee
+ :emplid 1
+ :groupid 1
+ :married t
+ :height (1+ (random 1.00))
+ :birthday (clsql-base:get-time)
+ :first-name "Vladamir"
+ :last-name "Lenin"
+ :email "lenin@soviet.org")
+ employee2 (make-instance 'employee
+ :emplid 2
+ :groupid 1
+ :height (1+ (random 1.00))
+ :married t
+ :birthday (clsql-base:get-time)
+ :first-name "Josef"
+ :last-name "Stalin"
+ :email "stalin@soviet.org")
+ employee3 (make-instance 'employee
+ :emplid 3
+ :groupid 1
+ :height (1+ (random 1.00))
+ :married t
+ :birthday (clsql-base:get-time)
+ :first-name "Leon"
+ :last-name "Trotsky"
+ :email "trotsky@soviet.org")
+ employee4 (make-instance 'employee
+ :emplid 4
+ :groupid 1
+ :height (1+ (random 1.00))
+ :married nil
+ :birthday (clsql-base:get-time)
+ :first-name "Nikita"
+ :last-name "Kruschev"
+ :email "kruschev@soviet.org")
+
+ employee5 (make-instance 'employee
+ :emplid 5
+ :groupid 1
+ :married nil
+ :height (1+ (random 1.00))
+ :birthday (clsql-base:get-time)
+ :first-name "Leonid"
+ :last-name "Brezhnev"
+ :email "brezhnev@soviet.org")
+
+ employee6 (make-instance 'employee
+ :emplid 6
+ :groupid 1
+ :married nil
+ :height (1+ (random 1.00))
+ :birthday (clsql-base:get-time)
+ :first-name "Yuri"
+ :last-name "Andropov"
+ :email "andropov@soviet.org")
+ employee7 (make-instance 'employee
+ :emplid 7
+ :groupid 1
+ :height (1+ (random 1.00))
+ :married nil
+ :birthday (clsql-base:get-time)
+ :first-name "Konstantin"
+ :last-name "Chernenko"
+ :email "chernenko@soviet.org")
+ employee8 (make-instance 'employee
+ :emplid 8
+ :groupid 1
+ :height (1+ (random 1.00))
+ :married nil
+ :birthday (clsql-base:get-time)
+ :first-name "Mikhail"
+ :last-name "Gorbachev"
+ :email "gorbachev@soviet.org")
+ employee9 (make-instance 'employee
+ :emplid 9
+ :groupid 1
+ :married nil
+ :height (1+ (random 1.00))
+ :birthday (clsql-base:get-time)
+ :first-name "Boris"
+ :last-name "Yeltsin"
+ :email "yeltsin@soviet.org")
+ employee10 (make-instance 'employee
+ :emplid 10
+ :groupid 1
+ :married nil
+ :height (1+ (random 1.00))
+ :birthday (clsql-base:get-time)
+ :first-name "Vladamir"
+ :last-name "Putin"
+ :email "putin@soviet.org")
+
+ address1 (make-instance 'address
+ :emplid 1
+ :street-number 10
+ :street-name "Park Place"
+ :city "Leningrad"
+ :postal-code 123)
+
+ address2 (make-instance 'address
+ :emplid 2))
+
+ ;; sleep to ensure birthdays are no longer at current time
+ (sleep 2)
+
;; Lenin manages everyone
(clsql:add-to-relation employee2 'manager employee1)
(clsql:add-to-relation employee3 'manager employee1)
(clsql:update-records-from-instance employee8)
(clsql:update-records-from-instance employee9)
(clsql:update-records-from-instance employee10)
- (clsql:update-records-from-instance company1))
+ (clsql:update-records-from-instance company1)
+ (clsql:update-records-from-instance address1)
+ (clsql:update-records-from-instance address2))
+
+(defvar *error-count* 0)
+(defvar *error-list* nil)
+
+(defun run-function-append-report-file (function report-file)
+ (let* ((report-path (etypecase report-file
+ (pathname report-file)
+ (string (parse-namestring report-file))))
+ (sexp-report-path (make-pathname :defaults report-path
+ :type "sexp")))
+ (with-open-file (rs report-path :direction :output
+ :if-exists :append
+ :if-does-not-exist :create)
+ (with-open-file (srs sexp-report-path :direction :output
+ :if-exists :append
+ :if-does-not-exist :create)
+ (funcall function :report-stream rs :sexp-report-stream srs)))))
-(defun run-tests ()
- (let ((specs (read-specs)))
+(defun run-tests-append-report-file (report-file)
+ (run-function-append-report-file 'run-tests report-file))
+
+
+(defun run-tests (&key (report-stream *standard-output*) (sexp-report-stream nil))
+ (let ((specs (read-specs))
+ (*report-stream* report-stream)
+ (*sexp-report-stream* sexp-report-stream)
+ (*error-count* 0)
+ (*error-list* nil))
(unless specs
(warn "Not running tests because test configuration file is missing")
(return-from run-tests :skipped))
(load-necessary-systems specs)
(dolist (db-type +all-db-types+)
- (let ((spec (db-type-spec db-type specs)))
- (when spec
- (do-tests-for-backend spec db-type))))))
+ (dolist (spec (db-type-spec db-type specs))
+ (do-tests-for-backend db-type spec))))
+ (zerop *error-count*))
(defun load-necessary-systems (specs)
(dolist (db-type +all-db-types+)
(when (db-type-spec db-type specs)
- (db-type-ensure-system db-type))))
+ (clsql:initialize-database-type :database-type db-type))))
-(defun do-tests-for-backend (spec db-type)
- (format t
+(defun write-report-banner (report-type db-type stream)
+ (format stream
"~&
-*******************************************************************
-*** Running CLSQL tests with ~A backend.
-*******************************************************************
-" db-type)
- (regression-test:rem-all-tests)
-
- ;; Tests of clsql-base
- (ignore-errors (destroy-database spec :database-type db-type))
- (ignore-errors (create-database spec :database-type db-type))
- (with-tests (:name "CLSQL")
- (test-basic spec db-type))
-
- (ignore-errors (destroy-database spec :database-type db-type))
- (ignore-errors (create-database spec :database-type db-type))
- (dolist (test (append *rt-connection* *rt-fddl* *rt-fdml*
- *rt-ooddl* *rt-oodml* *rt-syntax*))
- (eval test))
+******************************************************************************
+*** CLSQL ~A begun at ~A
+*** ~A
+*** ~A on ~A
+*** Database ~A backend~A.
+******************************************************************************
+"
+ report-type
+ (clsql-base:format-time
+ nil
+ (clsql-base:utime->time (get-universal-time)))
+ (lisp-implementation-type)
+ (lisp-implementation-version)
+ (machine-type)
+ db-type
+ (if (not (eq db-type *test-database-underlying-type*))
+ (format nil " with underlying type ~A"
+ *test-database-underlying-type*)
+ "")
+ ))
+
+(defun do-tests-for-backend (db-type spec)
(test-connect-to-database db-type spec)
- (test-initialise-database)
- (rtest:do-tests))
+
+ (unwind-protect
+ (multiple-value-bind (test-forms skip-tests)
+ (compute-tests-for-backend db-type *test-database-underlying-type*)
+
+ (write-report-banner "Test Suite" db-type *report-stream*)
+
+ (test-initialise-database)
+
+ (regression-test:rem-all-tests)
+ (dolist (test-form test-forms)
+ (eval test-form))
+
+ (let ((remaining (regression-test:do-tests *report-stream*)))
+ (when (regression-test:pending-tests)
+ (incf *error-count* (length remaining))))
+
+ (let ((sexp-error (list db-type
+ *test-database-underlying-type*
+ (get-universal-time)
+ (length test-forms)
+ (regression-test:pending-tests)
+ (lisp-implementation-type)
+ (lisp-implementation-version)
+ (machine-type))))
+ (when *sexp-report-stream*
+ (write sexp-error :stream *sexp-report-stream*))
+ (push sexp-error *error-list*))
+
+ (format *report-stream* "~&Tests skipped:")
+ (if skip-tests
+ (dolist (skipped skip-tests)
+ (format *report-stream*
+ "~& ~20A ~A~%" (car skipped) (cdr skipped)))
+ (format *report-stream* " None~%")))
+ (disconnect)))
+
+
+(defun compute-tests-for-backend (db-type db-underlying-type)
+ (declare (ignorable db-type))
+ (let ((test-forms '())
+ (skip-tests '()))
+ (dolist (test-form (append (test-basic-forms)
+ *rt-connection* *rt-fddl* *rt-fdml*
+ *rt-ooddl* *rt-oodml* *rt-syntax*))
+ (let ((test (second test-form)))
+ (cond
+ ((and (null (db-type-has-views? db-underlying-type))
+ (clsql-base::in test :fddl/view/1 :fddl/view/2 :fddl/view/3 :fddl/view/4))
+ (push (cons test "views not supported") skip-tests))
+ ((and (null (db-type-has-boolean-where? db-underlying-type))
+ (clsql-base::in test :fdml/select/11 :oodml/select/5))
+ (push (cons test "boolean where not supported") skip-tests))
+ ((and (null (db-type-has-subqueries? db-underlying-type))
+ (clsql-base::in test :fdml/select/5 :fdml/select/10))
+ (push (cons test "subqueries not supported") skip-tests))
+ ((and (null (db-type-transaction-capable? db-underlying-type
+ *default-database*))
+ (clsql-base::in test :fdml/transaction/1 :fdml/transaction/2 :fdml/transaction/3 :fdml/transaction/4))
+ (push (cons test "transactions not supported") skip-tests))
+ ((and (null (db-type-has-fancy-math? db-underlying-type))
+ (clsql-base::in test :fdml/select/1))
+ (push (cons test "fancy math not supported") skip-tests))
+ ((and (eql *test-database-type* :sqlite)
+ (clsql-base::in test :fddl/view/4 :fdml/select/10))
+ (push (cons test "not supported by sqlite") skip-tests))
+ (t
+ (push test-form test-forms)))))
+ (values (nreverse test-forms) (nreverse skip-tests))))