Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
256 lines
8.1 KiB
Plaintext
256 lines
8.1 KiB
Plaintext
;; Smalltalk runtime tests — class table, type→class mapping, instances.
|
|
;;
|
|
;; Reuses helpers (st-test, st-deep=?) from tokenize.sx. Counters reset
|
|
;; here so this file's summary covers runtime tests only.
|
|
|
|
(set! st-test-pass 0)
|
|
(set! st-test-fail 0)
|
|
(set! st-test-fails (list))
|
|
|
|
;; Fresh hierarchy for every test file.
|
|
(st-bootstrap-classes!)
|
|
|
|
;; ── 1. Bootstrap installed expected classes ──
|
|
(st-test "Object exists" (st-class-exists? "Object") true)
|
|
(st-test "Behavior exists" (st-class-exists? "Behavior") true)
|
|
(st-test "Metaclass exists" (st-class-exists? "Metaclass") true)
|
|
(st-test "True/False/UndefinedObject"
|
|
(and
|
|
(st-class-exists? "True")
|
|
(st-class-exists? "False")
|
|
(st-class-exists? "UndefinedObject"))
|
|
true)
|
|
(st-test "SmallInteger / Float / Symbol exist"
|
|
(and
|
|
(st-class-exists? "SmallInteger")
|
|
(st-class-exists? "Float")
|
|
(st-class-exists? "Symbol"))
|
|
true)
|
|
(st-test "BlockClosure exists" (st-class-exists? "BlockClosure") true)
|
|
|
|
;; ── 2. Superclass chain ──
|
|
(st-test "Object has no superclass" (st-class-superclass "Object") nil)
|
|
(st-test "Behavior super = Object" (st-class-superclass "Behavior") "Object")
|
|
(st-test "True super = Boolean" (st-class-superclass "True") "Boolean")
|
|
(st-test "Symbol super = String" (st-class-superclass "Symbol") "String")
|
|
(st-test
|
|
"String chain"
|
|
(st-class-chain "String")
|
|
(list "String" "ArrayedCollection" "SequenceableCollection" "Collection" "Object"))
|
|
(st-test
|
|
"SmallInteger chain"
|
|
(st-class-chain "SmallInteger")
|
|
(list "SmallInteger" "Integer" "Number" "Magnitude" "Object"))
|
|
|
|
;; ── 3. inherits-from? ──
|
|
(st-test "True inherits from Boolean" (st-class-inherits-from? "True" "Boolean") true)
|
|
(st-test "True inherits from Object" (st-class-inherits-from? "True" "Object") true)
|
|
(st-test "True inherits from True" (st-class-inherits-from? "True" "True") true)
|
|
(st-test
|
|
"True does not inherit from Number"
|
|
(st-class-inherits-from? "True" "Number")
|
|
false)
|
|
(st-test
|
|
"Object does not inherit from Number"
|
|
(st-class-inherits-from? "Object" "Number")
|
|
false)
|
|
|
|
;; ── 4. type→class mapping ──
|
|
(st-test "class-of nil" (st-class-of nil) "UndefinedObject")
|
|
(st-test "class-of true" (st-class-of true) "True")
|
|
(st-test "class-of false" (st-class-of false) "False")
|
|
(st-test "class-of int" (st-class-of 42) "SmallInteger")
|
|
(st-test "class-of zero" (st-class-of 0) "SmallInteger")
|
|
(st-test "class-of negative int" (st-class-of -3) "SmallInteger")
|
|
(st-test "class-of float" (st-class-of 3.14) "Float")
|
|
(st-test "class-of string" (st-class-of "hi") "String")
|
|
(st-test "class-of symbol" (st-class-of (quote foo)) "Symbol")
|
|
(st-test "class-of list" (st-class-of (list 1 2)) "Array")
|
|
(st-test "class-of empty list" (st-class-of (list)) "Array")
|
|
(st-test "class-of lambda" (st-class-of (fn (x) x)) "BlockClosure")
|
|
(st-test "class-of dict" (st-class-of {:a 1}) "Dictionary")
|
|
|
|
;; ── 5. User class definition ──
|
|
(st-class-define! "Account" "Object" (list "balance" "owner"))
|
|
(st-class-define! "SavingsAccount" "Account" (list "rate"))
|
|
|
|
(st-test "Account exists" (st-class-exists? "Account") true)
|
|
(st-test "Account super = Object" (st-class-superclass "Account") "Object")
|
|
(st-test
|
|
"SavingsAccount chain"
|
|
(st-class-chain "SavingsAccount")
|
|
(list "SavingsAccount" "Account" "Object"))
|
|
(st-test
|
|
"SavingsAccount own ivars"
|
|
(get (st-class-get "SavingsAccount") :ivars)
|
|
(list "rate"))
|
|
(st-test
|
|
"SavingsAccount inherited+own ivars"
|
|
(st-class-all-ivars "SavingsAccount")
|
|
(list "balance" "owner" "rate"))
|
|
|
|
;; ── 6. Instance construction ──
|
|
(define a1 (st-make-instance "Account"))
|
|
(st-test "instance is st-instance" (st-instance? a1) true)
|
|
(st-test "instance class" (get a1 :class) "Account")
|
|
(st-test "instance ivars start nil" (st-iv-get a1 "balance") nil)
|
|
(st-test
|
|
"instance has all expected ivars"
|
|
(sort (keys (get a1 :ivars)))
|
|
(sort (list "balance" "owner")))
|
|
(define a2 (st-iv-set! a1 "balance" 100))
|
|
(st-test "iv-set! returns updated copy" (st-iv-get a2 "balance") 100)
|
|
(st-test "iv-set! does not mutate original" (st-iv-get a1 "balance") nil)
|
|
(st-test "class-of instance" (st-class-of a1) "Account")
|
|
|
|
(define s1 (st-make-instance "SavingsAccount"))
|
|
(st-test
|
|
"subclass instance has all inherited ivars"
|
|
(sort (keys (get s1 :ivars)))
|
|
(sort (list "balance" "owner" "rate")))
|
|
|
|
;; ── 7. Method install + lookup ──
|
|
(st-class-add-method!
|
|
"Account"
|
|
"balance"
|
|
(st-parse-method "balance ^ balance"))
|
|
(st-class-add-method!
|
|
"Account"
|
|
"deposit:"
|
|
(st-parse-method "deposit: amount balance := balance + amount. ^ self"))
|
|
|
|
(st-test
|
|
"method registered"
|
|
(has-key? (get (st-class-get "Account") :methods) "balance")
|
|
true)
|
|
|
|
(st-test
|
|
"method lookup direct"
|
|
(= (st-method-lookup "Account" "balance" false) nil)
|
|
false)
|
|
|
|
(st-test
|
|
"method lookup walks superclass"
|
|
(= (st-method-lookup "SavingsAccount" "deposit:" false) nil)
|
|
false)
|
|
|
|
(st-test
|
|
"method lookup unknown selector"
|
|
(st-method-lookup "Account" "frobnicate" false)
|
|
nil)
|
|
|
|
(st-test
|
|
"method lookup records defining class"
|
|
(get (st-method-lookup "SavingsAccount" "balance" false) :defining-class)
|
|
"Account")
|
|
|
|
;; SavingsAccount overrides deposit:
|
|
(st-class-add-method!
|
|
"SavingsAccount"
|
|
"deposit:"
|
|
(st-parse-method "deposit: amount ^ super deposit: amount + 1"))
|
|
|
|
(st-test
|
|
"subclass override picked first"
|
|
(get (st-method-lookup "SavingsAccount" "deposit:" false) :defining-class)
|
|
"SavingsAccount")
|
|
|
|
(st-test
|
|
"Account still finds its own deposit:"
|
|
(get (st-method-lookup "Account" "deposit:" false) :defining-class)
|
|
"Account")
|
|
|
|
;; ── 8. Class-side methods ──
|
|
(st-class-add-class-method!
|
|
"Account"
|
|
"new"
|
|
(st-parse-method "new ^ super new"))
|
|
(st-test
|
|
"class-side lookup"
|
|
(= (st-method-lookup "Account" "new" true) nil)
|
|
false)
|
|
(st-test
|
|
"instance-side does not find class method"
|
|
(st-method-lookup "Account" "new" false)
|
|
nil)
|
|
|
|
;; ── 9. Re-bootstrap resets table ──
|
|
(st-bootstrap-classes!)
|
|
(st-test "after re-bootstrap Account gone" (st-class-exists? "Account") false)
|
|
(st-test "after re-bootstrap Object stays" (st-class-exists? "Object") true)
|
|
|
|
;; ── 10. Method-lookup cache ──
|
|
(st-bootstrap-classes!)
|
|
(st-class-define! "Foo" "Object" (list))
|
|
(st-class-define! "Bar" "Foo" (list))
|
|
(st-class-add-method! "Foo" "greet" (st-parse-method "greet ^ 1"))
|
|
|
|
;; Bootstrap clears cache; record stats from now.
|
|
(st-method-cache-reset-stats!)
|
|
|
|
;; First lookup is a miss; second is a hit.
|
|
(st-method-lookup "Bar" "greet" false)
|
|
(st-test
|
|
"first lookup recorded as miss"
|
|
(get (st-method-cache-stats) :misses)
|
|
1)
|
|
(st-test
|
|
"first lookup recorded as hit count zero"
|
|
(get (st-method-cache-stats) :hits)
|
|
0)
|
|
|
|
(st-method-lookup "Bar" "greet" false)
|
|
(st-test
|
|
"second lookup hits cache"
|
|
(get (st-method-cache-stats) :hits)
|
|
1)
|
|
|
|
;; Misses are also cached as :not-found.
|
|
(st-method-lookup "Bar" "frobnicate" false)
|
|
(st-method-lookup "Bar" "frobnicate" false)
|
|
(st-test
|
|
"negative-result caches"
|
|
(get (st-method-cache-stats) :hits)
|
|
2)
|
|
|
|
;; Adding a new method invalidates the cache.
|
|
(st-class-add-method! "Bar" "greet" (st-parse-method "greet ^ 2"))
|
|
(st-test
|
|
"cache cleared on method add"
|
|
(get (st-method-cache-stats) :size)
|
|
0)
|
|
(st-test
|
|
"after invalidation lookup picks up override"
|
|
(get (st-method-lookup "Bar" "greet" false) :defining-class)
|
|
"Bar")
|
|
|
|
;; Removing a method also invalidates and exposes the inherited one.
|
|
(st-class-remove-method! "Bar" "greet")
|
|
(st-test
|
|
"after remove lookup falls through to Foo"
|
|
(get (st-method-lookup "Bar" "greet" false) :defining-class)
|
|
"Foo")
|
|
|
|
;; Cache survives across unrelated class-table mutations? No — define! clears.
|
|
(st-method-lookup "Foo" "greet" false) ; warm cache
|
|
(st-class-define! "Baz" "Object" (list))
|
|
(st-test
|
|
"class-define clears cache"
|
|
(get (st-method-cache-stats) :size)
|
|
0)
|
|
|
|
;; Class-side and instance-side cache entries are separate keys.
|
|
(st-class-add-class-method! "Foo" "make" (st-parse-method "make ^ self new"))
|
|
(st-method-lookup "Foo" "make" true)
|
|
(st-method-lookup "Foo" "make" false)
|
|
(st-test
|
|
"class-side hit found, instance-side stored as not-found"
|
|
(= (st-method-lookup "Foo" "make" true) nil)
|
|
false)
|
|
(st-test
|
|
"instance-side same selector returns nil"
|
|
(st-method-lookup "Foo" "make" false)
|
|
nil)
|
|
|
|
(list st-test-pass st-test-fail)
|