;;;; Test suite for the Common Lisp condition system
;;;; Written by David Gadbois <gadbois@cs.utexas.edu> 30.11.1993

;;;
;;; Helpers
;;;

(defun check-superclasses (class expected)
  (let ((expected (list* class 't 'clos:standard-object 'condition expected))
        (super (mapcar #'clos:class-name (clos::class-precedence-list (clos:find-class class)))))
    (and (null (set-difference super expected))
         (null (set-difference expected super)))))
CHECK-SUPERCLASSES

;;;
;;; IGNORE-ERRORS
;;;
;;; If this does not work, none of the tests that check for getting an error
;;; will.

;;; IGNORE-ERRORS should work.
(multiple-value-bind (value condition)
    (ignore-errors (error "Foo"))
  (list value (type-of condition)))
(nil simple-error)

;;; IGNORE-ERRORS should not interfere with values in non-error situations.
(multiple-value-list
    (ignore-errors (values 23 42)))
(23 42)

;;;
;;; Predefined condition types.
;;;

(check-superclasses 'warning '()) T
#+dpANS (check-superclasses 'style-warning '(warning)) #+dpANS T
(check-superclasses 'serious-condition '()) T
(check-superclasses 'error '(serious-condition)) T
(check-superclasses 'cell-error '(error serious-condition)) T
#+dpANS (check-superclasses 'parse-error '(error serious-condition)) #+dpANS T
(check-superclasses 'storage-condition '(serious-condition)) T
(check-superclasses 'simple-error '(simple-condition error serious-condition)) T
(check-superclasses 'simple-condition '()) T
(check-superclasses 'simple-warning '(simple-condition warning)) T
(check-superclasses 'file-error '(error serious-condition)) T
(check-superclasses 'control-error '(error serious-condition)) T
(check-superclasses 'program-error '(error serious-condition)) T
(check-superclasses 'undefined-function '(cell-error error serious-condition)) T
(check-superclasses 'arithmetic-error '(error serious-condition)) T
(check-superclasses 'division-by-zero '(arithmetic-error error serious-condition)) T
#+dpANS (check-superclasses 'floating-point-invalid-operation '(arithmetic-error error serious-condition)) #+dpANS T
#+dpANS (check-superclasses 'floating-point-inexact '(arithmetic-error error serious-condition)) #+dpANS T
(check-superclasses 'floating-point-overflow '(arithmetic-error error serious-condition)) T
(check-superclasses 'floating-point-underflow '(arithmetic-error error serious-condition)) T
#+dpANS (check-superclasses 'unbound-slot '(cell-error error serious-condition)) #+dpANS T
(check-superclasses 'package-error '(error serious-condition)) T
#+dpANS (check-superclasses 'print-not-readable '(error serious-condition)) #+dpANS T
#+dpANS (check-superclasses 'reader-error '(parse-error stream-error error serious-condition)) #+dpANS T
(check-superclasses 'stream-error '(error serious-condition)) T
(check-superclasses 'end-of-file '(stream-error error serious-condition)) T
(check-superclasses 'unbound-variable '(cell-error error serious-condition)) T
(check-superclasses 'type-error '(error serious-condition)) T
(check-superclasses 'simple-type-error '(#-dpANS simple-error simple-condition type-error error serious-condition)) T

;;;
;;; Defining conditions.
;;;
(progn (define-condition test () ()) t)
T

(check-superclasses  'test '())
T

(progn (define-condition test2 (test) ()) t)
T

(check-superclasses 'test2 '(test))
T

(progn (define-condition test3 (test2 simple-condition) ()) t)
T

(check-superclasses 'test3 '(test2 test simple-condition))
T

;;;
;;; Making conditions
;;;
(progn (make-condition 'test) t)
T

(ignore-errors (progn (make-condition 'integer) t))
NIL

;;;
;;; :REPORT option to DEFINE-CONDITION
;;;
(progn (define-condition test4 (test3)
         ()
         (:report (lambda (condition stream)
                    (format stream "Yow! -- ~S" (type-of condition)))))
       t)
T

(with-output-to-string (s) (princ (make-condition 'test4) s))
"Yow! -- TEST4"

(progn (define-condition test5 (test4) ()) t)
T

(with-output-to-string (s) (princ (make-condition 'test5) s))
"Yow! -- TEST5"

(with-output-to-string (s)
  (princ (make-condition 'test3 :format-string "And How! -- ~S"
                                :format-arguments '(23)) s))
"And How! -- 23"

;;;
;;; Condition slots.
;;;
(progn (define-condition test6 (test4)
         ((foo :initarg :foo :initform 23 :accessor test6-foo))
         (:report (lambda (condition stream)
                    (format stream "~S -- ~S"
                            (type-of condition)
                            (test6-foo condition)))))
       t)
T

(test6-foo (make-condition 'test6))
23

(test6-foo (make-condition 'test6 :foo 42))
42

(setf (test6-foo (make-condition 'test6 :foo 42)) 17)
17

(with-output-to-string (s) (princ (make-condition 'test6 :foo 42) s))
"TEST6 -- 42"

;;;
;;; HANDLER-BIND
;;;

;;; You do not have to bind handlers.
(ignore-errors
 (handler-bind
     ()
   (error "Foo")))
nil

;;; Handlers should not interfere with values in non-error situations.
(multiple-value-list
    (block foo
      (handler-bind
          ((error #'(lambda (c)
                      (declare (ignore c))
                      (return-from foo 23))))
        (values 42 17))))
(42 17)

;;; Handlers should work.
(multiple-value-list
    (block foo
      (handler-bind 
          ((error #'(lambda (c)
                      (declare (ignore c))
                      (return-from foo (values 23 17)))))
        (error "Foo"))))
(23 17)

;;; Only the appropriate handlers should be called.
(ignore-errors
 (block foo
   (handler-bind 
       ((type-error #'(lambda (c)
                        (declare (ignore c))
                        (return-from foo 23))))
     (error "Foo"))))
nil

;;; Handlers can be specified type expressions.
(block foo
  (handler-bind 
      (((or type-error error)
        #'(lambda (c)
            (declare (ignore c))
            (return-from foo 23))))
    (error "Foo")))
23

;;; Handlers should be undone.
(ignore-errors
 (block foo
   (let ((first-time t))
     (handler-bind 
         ((error
           #'(lambda (c)
               (declare (ignore c))
               (if first-time
                   (progn
                     (setq first-time nil)
                     (error "Bar"))
                   (return-from foo 23)))))
       (error "Foo")))))
nil

;;; Handlers should be undone.
(block foo
  (let ((first-time t))
    (handler-bind
        ((error
          #'(lambda (c)
              (declare (ignore c))
              (return-from foo 23))))
      (handler-bind
          ((error 
            #'(lambda (c)
                (declare (ignore c))
                (if first-time
                    (progn
                      (setq first-time nil)
                      (error "Bar"))
                    (return-from foo 42)))))
        (error "Foo")))))
23

;;; Handlers in the same cluster should be accessible.
(ignore-errors
 (block foo
   (handler-bind 
       ((error
         #'(lambda (c) (declare (ignore c)) nil))
        (error
         #'(lambda (c)
             (declare (ignore c))
             (return-from foo 23))))
     (error "Foo"))))
#-dpANS nil #+dpANS 23

;;; Multiple handlers should work.
(block foo
  (handler-bind 
      ((type-error
        #'(lambda (c) 
            (declare (ignore c))
            (return-from foo 42)))
       (error
        #'(lambda (c)
            (declare (ignore c))
            (return-from foo 23))))
    (error "Foo")))
23

;;; Handlers should be undone.
(block foo
  (handler-bind
      ((error #'(lambda (c)
                  (declare (ignore c))
                  (return-from foo 23))))
    (block bar
      (handler-bind
          ((error #'(lambda (c)
                      (declare (ignore c))
                      (return-from foo 42))))
        (return-from bar)))
    (error "Foo")))
23

;;;
;;; HANDLER-CASE
;;;

;;; HANDLER-CASE should handle errors.
(multiple-value-list
    (handler-case 
        (error "Foo")
      (error (c) (when (typep c 'error) (values 23 42)))))
(23 42)

;;; Except those it doesn't handle.
(ignore-errors
 (handler-case
     (error "Foo")
   (type-error () 23)))
NIL

;;; You don't have to specify handlers.
(ignore-errors
 (handler-case
     (error "Foo")))
NIL

;;; HANDLER-CASE should not interfere with values in non-error situations.
(multiple-value-list
    (handler-case
        (values 42 17)
      (error () 23)))
(42 17)

;;; :NO-ERROR should return values.
(multiple-value-list
    (handler-case
        (values 23 42)
      (:no-error (a b)
        (values b a))))
(42 23)

;;; Except when there is an error.
(handler-case
    (error "Foo")
  (error () 23)
  (:no-error (&rest args) (declare (ignore args)) 42))
23

;;; Or if it is not the last clause.
(handler-case
    23
  (:no-error (v) (1+ v))
  (error () 42))
23

;;; Multiple handlers should be OK.
(handler-case
    (error "Foo")
  (type-error () 23)
  (error () 42))
42

;;; Handlers should get undone.
(ignore-errors
 (progn 
   (block foo
     (handler-case
         (return-from foo 23)
       (error () 42)))
   (error "Foo")))
NIL

;;; Ditto.
(ignore-errors
 (block foo
   (let ((first-time t))
     (handler-case
         (error "Foo")
       (error ()
         (if first-time
             (progn
               (setf first-time nil)
               (error "Bar"))
             (return-from foo 23)))))))
NIL
      


