;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
;;;; *************************************************************************
;;;; FILE IDENTIFICATION
;;;;
;;;; ID:      $Id$
;;;; Purpose: Suite functions for XLUnit
;;;;
;;;; *************************************************************************

(in-package #:xlunit)

(defclass test-suite (test)
  ((name :initform "" :initarg :name :reader test-suite-name)
   (tests :initarg :tests :accessor tests :initform nil)
   (description :initarg :description :reader description
                :initform "No description.")))

(defmacro get-suite (class-name)
  `(suite (make-instance ',class-name)))


(defmethod add-test ((ob test-suite) (new-test test))
  (remove-test new-test ob)
  (setf (tests ob) (append (tests ob) (list new-test))))


(defmethod run-on-test-results ((ob test-suite) (result test-results)
                                &key (handle-errors t))
  (mapc #'(lambda (composite)  ;;test-case or suite
            (run-on-test-results composite result
                                :handle-errors handle-errors))
        (tests ob)))

(defmethod named-test (name (suite test-suite))
  (some (lambda (test-or-suite)
          (when (and (typep test-or-suite 'test-case)
                     (equal name (name test-or-suite)))
            test-or-suite))
        (tests suite)))

(defmethod remove-test ((test test) (suite test-suite))
  (setf (tests suite)
    (delete-if #'(lambda (existing-tests-or-suite)
                   (cond ((typep existing-tests-or-suite 'test-suite)
                          (eq existing-tests-or-suite test))
                         ((typep existing-tests-or-suite 'test-case)
                          (eql (name existing-tests-or-suite)
                               (name test)))))
               (tests suite))))

;; Dynamic test suite

(defun find-test-generic-functions (instance)
  "Return a list of symbols for generic functions specialized on the
class of an instance and whose name begins with the string 'test-'.
This is used to dynamically generate a list of tests for a fixture."
  (let ((res)
        (package (symbol-package (class-name (class-of instance)))))
    (do-symbols (s package)
      (when (and (> (length (symbol-name s)) 5)
                 (string-equal "test-" (subseq (symbol-name s) 0 5))
                 (fboundp s)
                 (typep (symbol-function s) 'generic-function)
                 (ignore-errors
                   (plusp (length (compute-applicable-methods
                                   (ensure-generic-function s)
                                   (list instance))))))
        (push s res)))
    (nreverse res)))


(defmacro def-test-method (method-name ((instance-name class-name)
                                        &key (run t))
                           &body method-body)
  `(let ((,instance-name
          (make-instance ',class-name
            :name ',method-name)))
     (setf (method-body ,instance-name)
           #'(lambda() ,@method-body))
     (add-test (suite ,instance-name) ,instance-name)
     (when ,run
       (textui-test-run ,instance-name))))
