;; lib/common-lisp/tests/clos.sx — CLOS test suite ;; ;; Loaded after: spec/stdlib.sx, lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx (define passed 0) (define failed 0) (define failures (list)) (define assert-equal (fn (label got expected) (if (= got expected) (set! passed (+ passed 1)) (begin (set! failed (+ failed 1)) (set! failures (append failures (list (str "FAIL [" label "]: got=" (inspect got) " expected=" (inspect expected))))))))) (define assert-true (fn (label got) (if got (set! passed (+ passed 1)) (begin (set! failed (+ failed 1)) (set! failures (append failures (list (str "FAIL [" label "]: expected true, got " (inspect got))))))))) (define assert-nil (fn (label got) (if (nil? got) (set! passed (+ passed 1)) (begin (set! failed (+ failed 1)) (set! failures (append failures (list (str "FAIL [" label "]: expected nil, got " (inspect got))))))))) ;; ── 1. class-of for built-in types ──────────────────────────────────────── (assert-equal "class-of integer" (clos-class-of 42) "integer") (assert-equal "class-of float" (clos-class-of 3.14) "float") (assert-equal "class-of string" (clos-class-of "hi") "string") (assert-equal "class-of nil" (clos-class-of nil) "null") (assert-equal "class-of list" (clos-class-of (list 1)) "cons") (assert-equal "class-of empty" (clos-class-of (list)) "null") ;; ── 2. subclass-of? ─────────────────────────────────────────────────────── (assert-true "integer subclass-of t" (clos-subclass-of? "integer" "t")) (assert-true "float subclass-of t" (clos-subclass-of? "float" "t")) (assert-true "t subclass-of t" (clos-subclass-of? "t" "t")) (assert-equal "integer not subclass-of float" (clos-subclass-of? "integer" "float") false) ;; ── 3. defclass + make-instance ─────────────────────────────────────────── (clos-defclass "point" (list "t") (list {:initform 0 :initarg ":x" :reader nil :writer nil :accessor "point-x" :name "x"} {:initform 0 :initarg ":y" :reader nil :writer nil :accessor "point-y" :name "y"})) (let ((p (clos-make-instance "point" ":x" 3 ":y" 4))) (begin (assert-equal "make-instance slot x" (clos-slot-value p "x") 3) (assert-equal "make-instance slot y" (clos-slot-value p "y") 4) (assert-equal "class-of instance" (clos-class-of p) "point") (assert-true "instance-of? point" (clos-instance-of? p "point")) (assert-true "instance-of? t" (clos-instance-of? p "t")) (assert-equal "instance-of? string" (clos-instance-of? p "string") false))) ;; initform defaults (let ((p0 (clos-make-instance "point"))) (begin (assert-equal "initform default x=0" (clos-slot-value p0 "x") 0) (assert-equal "initform default y=0" (clos-slot-value p0 "y") 0))) ;; ── 4. slot-value / set-slot-value! ────────────────────────────────────── (let ((p (clos-make-instance "point" ":x" 10 ":y" 20))) (begin (clos-set-slot-value! p "x" 99) (assert-equal "set-slot-value! x" (clos-slot-value p "x") 99) (assert-equal "slot-value y unchanged" (clos-slot-value p "y") 20))) ;; ── 5. slot-boundp ──────────────────────────────────────────────────────── (let ((p (clos-make-instance "point" ":x" 5))) (begin (assert-true "slot-boundp x" (clos-slot-boundp p "x")) (assert-true "slot-boundp y (initform 0)" (clos-slot-boundp p "y")))) ;; ── 6. find-class ───────────────────────────────────────────────────────── (assert-equal "find-class point" (get (clos-find-class "point") "name") "point") (assert-nil "find-class missing" (clos-find-class "no-such-class")) ;; ── 7. inheritance ──────────────────────────────────────────────────────── (clos-defclass "colored-point" (list "point") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"})) (let ((cp (clos-make-instance "colored-point" ":x" 1 ":y" 2 ":color" "red"))) (begin (assert-equal "inherited slot x" (clos-slot-value cp "x") 1) (assert-equal "inherited slot y" (clos-slot-value cp "y") 2) (assert-equal "own slot color" (clos-slot-value cp "color") "red") (assert-true "instance-of? colored-point" (clos-instance-of? cp "colored-point")) (assert-true "instance-of? point (parent)" (clos-instance-of? cp "point")) (assert-true "instance-of? t (root)" (clos-instance-of? cp "t")))) ;; ── 8. defgeneric + primary method ─────────────────────────────────────── (clos-defgeneric "describe-obj" {}) (clos-defmethod "describe-obj" (list) (list "point") (fn (args next-fn) (let ((p (first args))) (str "(" (clos-slot-value p "x") "," (clos-slot-value p "y") ")")))) (clos-defmethod "describe-obj" (list) (list "t") (fn (args next-fn) (str "object:" (inspect (first args))))) (let ((p (clos-make-instance "point" ":x" 3 ":y" 4))) (begin (assert-equal "primary method for point" (clos-call-generic "describe-obj" (list p)) "(3,4)") (assert-equal "fallback t method" (clos-call-generic "describe-obj" (list 42)) "object:42"))) ;; ── 9. method inheritance + specificity ─────────────────────────────────── (clos-defmethod "describe-obj" (list) (list "colored-point") (fn (args next-fn) (let ((cp (first args))) (str (clos-slot-value cp "color") "@(" (clos-slot-value cp "x") "," (clos-slot-value cp "y") ")")))) (let ((cp (clos-make-instance "colored-point" ":x" 5 ":y" 6 ":color" "blue"))) (assert-equal "most specific method wins" (clos-call-generic "describe-obj" (list cp)) "blue@(5,6)")) ;; ── 10. :before / :after / :around qualifiers ───────────────────────────── (clos-defgeneric "logged-action" {}) (clos-defmethod "logged-action" (list "before") (list "t") (fn (args next-fn) (set! action-log (append action-log (list "before"))))) (clos-defmethod "logged-action" (list) (list "t") (fn (args next-fn) (set! action-log (append action-log (list "primary"))) "result")) (clos-defmethod "logged-action" (list "after") (list "t") (fn (args next-fn) (set! action-log (append action-log (list "after"))))) (define action-log (list)) (clos-call-generic "logged-action" (list 1)) (assert-equal ":before/:after order" action-log (list "before" "primary" "after")) ;; :around (define around-log (list)) (clos-defgeneric "wrapped-action" {}) (clos-defmethod "wrapped-action" (list "around") (list "t") (fn (args next-fn) (set! around-log (append around-log (list "around-enter"))) (let ((r (next-fn))) (set! around-log (append around-log (list "around-exit"))) r))) (clos-defmethod "wrapped-action" (list) (list "t") (fn (args next-fn) (set! around-log (append around-log (list "primary"))) 42)) (let ((r (clos-call-generic "wrapped-action" (list nil)))) (begin (assert-equal ":around result" r 42) (assert-equal ":around log" around-log (list "around-enter" "primary" "around-exit")))) ;; ── 11. call-next-method ───────────────────────────────────────────────── (clos-defgeneric "chain-test" {}) (clos-defmethod "chain-test" (list) (list "colored-point") (fn (args next-fn) (str "colored:" (clos-call-next-method next-fn)))) (clos-defmethod "chain-test" (list) (list "point") (fn (args next-fn) "point-base")) (let ((cp (clos-make-instance "colored-point" ":x" 0 ":y" 0 ":color" "green"))) (assert-equal "call-next-method chains" (clos-call-generic "chain-test" (list cp)) "colored:point-base")) ;; ── 12. accessor methods ────────────────────────────────────────────────── (let ((p (clos-make-instance "point" ":x" 7 ":y" 8))) (begin (assert-equal "accessor point-x" (clos-call-generic "point-x" (list p)) 7) (assert-equal "accessor point-y" (clos-call-generic "point-y" (list p)) 8))) ;; ── 13. with-slots ──────────────────────────────────────────────────────── (let ((p (clos-make-instance "point" ":x" 3 ":y" 4))) (assert-equal "with-slots" (clos-with-slots p (list "x" "y") (fn (x y) (* x y))) 12)) ;; ── 14. change-class ───────────────────────────────────────────────────── (clos-defclass "special-point" (list "point") (list {:initform "" :initarg ":label" :reader nil :writer nil :accessor nil :name "label"})) (let ((p (clos-make-instance "point" ":x" 1 ":y" 2))) (begin (clos-change-class! p "special-point") (assert-equal "change-class updates class" (clos-class-of p) "special-point"))) ;; ── summary ──────────────────────────────────────────────────────────────── (if (= failed 0) (print (str "ok " passed "/" (+ passed failed) " CLOS tests passed")) (begin (for-each (fn (f) (print f)) failures) (print (str "FAIL " passed "/" (+ passed failed) " passed, " failed " failed"))))