~ chicken-core (chicken-5) /tests/weak-pointer-test.scm


  1;; weak-pointer-test.scm
  2
  3(import (chicken gc) (chicken port) (chicken locative))
  4
  5(include "test.scm")
  6
  7;; Ensure weakly held items are not just equal to other references to it, but *identical*
  8(current-test-comparator eq?)
  9
 10(test-group "Testing basic pair accessors work on weak pairs, too"
 11  (let ((my-proper-weak-list (weak-cons 1 (weak-cons 2 '())))
 12	(my-proper-list (cons 1 (cons 2 '())))
 13	(my-improper-weak-list (weak-cons 1 (weak-cons 2 3)))
 14	(my-improper-list (cons 1 (cons 2 3))))
 15
 16    (test-assert "proper weak lists are pairs" (pair? my-proper-weak-list))
 17    (test-assert "improper weak lists are pairs" (pair? my-improper-weak-list))
 18
 19    (test-assert "regular proper lists are not weak pairs" (not (weak-pair? my-proper-list)))
 20    (test-assert "regular improper lists are not weak pairs" (not (weak-pair? my-improper-list)))
 21
 22    (test-assert "proper weak lists are lists" (list? my-proper-weak-list))
 23    (test-assert "improper weak lists are *not* lists" (not (list? my-improper-weak-list)))
 24
 25    (test-equal "an weak proper list is equal to the same regular proper list" my-proper-weak-list my-proper-list equal?)
 26    (test-equal "an weak proper list is not *identical* to the same regular proper list" my-proper-weak-list my-proper-list (complement eq?))
 27
 28    (test-equal "car of weak list returns the first item" (car my-proper-weak-list) 1)
 29    (test-equal "cdr of weak list returns the cdr" (cdr my-proper-weak-list) (cdr my-proper-list) equal?)
 30    (test-equal "cadr of weak list returns the second item" (cadr my-proper-weak-list) 2)
 31    (test-equal "cddr of weak list returns the cdr of the cdr" (cddr my-proper-weak-list) '())
 32
 33    (test-equal "length of weak proper list returns the length" 2 (length my-proper-weak-list))
 34    (test-error "length of weak improper list raises an error" (length my-improper-weak-list))
 35
 36    (let* ((written-proper-weak-list (with-output-to-string (lambda () (write my-proper-weak-list))))
 37	   (written-improper-weak-list (with-output-to-string (lambda () (write my-improper-weak-list))))
 38	   (reread-proper-weak-list (with-input-from-string written-proper-weak-list read))
 39	   (reread-improper-weak-list (with-input-from-string written-improper-weak-list read)))
 40      (test-equal "a proper weak list is written as a regular proper list" "(1 2)" written-proper-weak-list string=?)
 41      (test-equal "a proper weak list is read back as regular proper list" my-proper-list reread-proper-weak-list equal?)
 42      (test-equal "an improper weak list is written as a regular improper list" "(1 2 . 3)" written-improper-weak-list string=?)
 43      (test-equal "an improper weak list is read back as regular improper list" my-improper-list reread-improper-weak-list equal?))))
 44
 45(test-group "Testing that basic weak pairs get their car reclaimed"
 46  (gc #t) ; Improve chances we don't get a minor GC in between
 47  (let* ((not-held-onto-value (vector 42))
 48	 (held-onto-vector (vector 'this-one-stays))
 49
 50	 (weak-list (weak-cons not-held-onto-value
 51			       (weak-cons (vector 'ohai)
 52					  (weak-cons held-onto-vector '()))))
 53	 (weak-immediate-pair (weak-cons 1 2)))
 54
 55    ;; break other references to the values
 56    (set! not-held-onto-value #f)
 57
 58    (gc)
 59
 60    ;; First item is reclaimed
 61    (test-assert "first item of weak list is reclaimed" (not (vector? (car weak-list))))
 62    (test-assert "first item of weak list is set to the broken-weak-pointer object" (bwp-object? (car weak-list)))
 63
 64    ;; Second item is reclaimed
 65    (test-assert "second item of weak list is reclaimed" (not (vector? (cadr weak-list))))
 66    (test-assert "second item of weak list is set to the broken-weak-pointer object" (bwp-object? (cadr weak-list)))
 67
 68    ;; Third item stays
 69    (test-assert "third item of weak list is kept around due to other references existing" (vector? (caddr weak-list)))
 70    (test-equal "third item of weak list is identical to the other reference" (caddr weak-list) held-onto-vector)
 71    (test-assert "third item of weak list is not set to the broken-weak-pointer object" (not (bwp-object? (caddr weak-list))))
 72
 73    (test-equal "weak car is kept around when value is an immediate" (car weak-immediate-pair) 1)
 74    (test-equal "weak cdr is kept around when value is an immediate" (cdr weak-immediate-pair) 2)))
 75
 76
 77(test-group "Testing that weak pairs do not get broken when holding permanent symbols"
 78  (gc #t) ; Improve chances we don't get a minor GC in between
 79
 80  ;; NOTE: When we don't use string-append here, the strings somehow get interned as (permanent) symbols?!
 81  ;; Perhaps this is somehow caused by the reader.
 82  (let* ((sym1 (string->symbol (string-append "something" "1234")))
 83	 (sym2 (string->symbol (string-append "another" "1234")))
 84	 (weak-permanent-symbol-pair (weak-cons 'scheme#car 'scheme#cdr))
 85	 (weak-impermanent-symbol-pair (weak-cons sym1 sym2)))
 86
 87    (set! sym1 #f)
 88    (set! sym2 #f)
 89
 90    (gc)
 91
 92    (test-equal "weak car is kept around when value is a \"permanent\" symbol" (car weak-permanent-symbol-pair) 'scheme#car)
 93    (test-equal "weak cdr is kept around when value is a \"permanent\" symbol" (cdr weak-permanent-symbol-pair) 'scheme#cdr)
 94
 95    (test-assert "weak car is reclaimed when value is an \"impermanent\" symbol" (not (symbol? (car weak-impermanent-symbol-pair))))
 96    (test-assert "weak car is reclaimed when value is an \"impermanent\" symbol" (bwp-object? (car weak-impermanent-symbol-pair)))
 97    (test-equal "weak cdr is kept around when value is a \"impermanent\" symbol" (cdr weak-impermanent-symbol-pair) (string->symbol (string-append "an" "other1234")))))
 98
 99
100(test-group "Testing cars of weak pairs referenced by their cdr do not get collected"
101  (gc #t) ; Improve chances we don't get a minor GC in between
102  (let* ((obj-a (vector 42))
103	 (ref-a (weak-cons obj-a obj-a))
104	 (obj-b (vector 'ohai))
105	 (ref-b (weak-cons obj-b obj-b))
106	 (held-onto-vector (vector 'this-one-stays)) ; should be held onto regardless of this, but here for consistency
107	 (ref-c (weak-cons held-onto-vector held-onto-vector)))
108
109    ;; break other references to the values
110    (set! obj-a #f)
111    (set! obj-b #f)
112
113    (gc)
114
115    (test-assert "object in first weak cons is still kept around in car" (vector? (car ref-a)))
116    (test-assert "object in first weak cons is still kept around in cdr" (vector? (cdr ref-a)))
117    (test-equal "object in first weak cons' car is identical to its cdr" (car ref-a) (cdr ref-a))
118    (test-assert "car of first weak cons is not a broken weak pair" (not (bwp-object? (car ref-a))))
119    (test-assert "cdr of first weak cons is not a broken weak pair" (not (bwp-object? (cdr ref-a))))
120
121    (test-assert "object in second weak cons is still kept around in car" (vector? (car ref-b)))
122    (test-assert "object in second weak cons is still kept around in cdr" (vector? (cdr ref-b)))
123    (test-equal "object in second weak cons' car is identical to its cdr" (car ref-b) (cdr ref-b))
124    (test-assert "car of second weak cons is not a broken weak pair" (not (bwp-object? (car ref-b))))
125    (test-assert "cdr of second weak cons is not a broken weak pair" (not (bwp-object? (cdr ref-b))))
126
127    (test-assert "object in third weak cons is still kept around in car" (vector? (car ref-c)))
128    (test-assert "object in third weak cons is still kept around in cdr" (vector? (cdr ref-c)))
129    (test-equal "object in third weak cons' car is identical to its cdr" (car ref-c) (cdr ref-c))
130    (test-equal "object in third weak cons' car is identical to the other reference" (car ref-c) held-onto-vector)
131    (test-assert "car of third weak cons is not a broken weak pair" (not (bwp-object? (car ref-c))))
132    (test-assert "cdr of third weak cons is not a broken weak pair" (not (bwp-object? (cdr ref-c))))))
133
134
135(test-group "Testing that strong locatives get their object updated"
136  (gc #t) ; Improve chances we don't get a minor GC in between
137  (let* ((not-held-onto-value (vector 42))
138	 (held-onto-vector (vector 'this-one-stays))
139	 (vec-0 (vector 0))
140	 (vec-1 (vector 1))
141	 (vec-2 (vector 2))
142
143	 (nested-not-held-onto-value (vector vec-0 vec-1 vec-2))
144	 (nested-held-onto-value (vector (vector 'x) (vector 'y) (vector 'z)))
145	 (vec-ohai (vector 'ohai))
146	 (vec-fubar (vector 'fubar))
147
148	 (loc1 (make-locative not-held-onto-value 0))
149	 (loc2 (make-locative (vector 'ohai 'fubar) 1))
150	 (loc3 (make-locative held-onto-vector 0))
151
152	 (loc4 (make-locative nested-not-held-onto-value 1))
153	 (loc5 (make-locative (vector vec-ohai vec-fubar) 1))
154	 (loc6 (make-locative nested-held-onto-value 1)))
155
156    ;; break other references to the values
157    (set! not-held-onto-value #f)
158    (set! nested-not-held-onto-value #f)
159
160    (gc)
161
162    (test-equal "First locative is updated" (locative-ref loc1) 42)
163    (test-equal "Second locative is updated" (locative-ref loc2) 'fubar)
164    (test-equal "Third locative is updated" (locative-ref loc3) 'this-one-stays)
165
166    (test-equal "Fourth locative is updated" (locative-ref loc4) vec-1)
167    (test-equal "Fifth locative is updated" (locative-ref loc5) vec-fubar)
168    (test-equal "Sixth locative is updated" (locative-ref loc6) (vector-ref nested-held-onto-value 1))))
169
170
171(test-group "Testing that weak locatives get their object reclaimed"
172  (gc #t) ; Improve chances we don't get a minor GC in between
173  (let* ((not-held-onto-value (vector 42))
174	 (held-onto-vector (vector 'this-one-stays))
175	 (vec-0 (vector 0))
176	 (vec-1 (vector 1))
177	 (vec-2 (vector 2))
178
179	 (nested-not-held-onto-value (vector vec-0 vec-1 vec-2))
180	 (nested-held-onto-value (vector (vector 'x) (vector 'y) (vector 'z)))
181	 (vec-ohai (vector 'ohai))
182	 (vec-fubar (vector 'fubar))
183
184	 (loc1 (make-weak-locative not-held-onto-value 0))
185	 (loc2 (make-weak-locative (vector 'ohai 'fubar) 1))
186	 (loc3 (make-weak-locative held-onto-vector 0))
187
188	 (loc4 (make-weak-locative nested-not-held-onto-value 1))
189	 (loc5 (make-weak-locative (vector vec-ohai vec-fubar) 1))
190	 (loc6 (make-weak-locative nested-held-onto-value 1)))
191
192    ;; break other references to the values
193    (set! not-held-onto-value #f)
194    (set! nested-not-held-onto-value #f)
195
196    (gc)
197
198    (test-error "First locative is reclaimed" (locative-ref loc1))
199    (test-error "Second locative is reclaimed" (locative-ref loc2))
200    ;; NOTE: It seems we have to go "through" the original vector to ensure reference is kept
201    (test-equal "Third locative is NOT reclaimed" (locative-ref loc3) (vector-ref held-onto-vector 0))
202
203    (test-error "Fourth locative is reclaimed" (locative-ref loc4))
204    (test-error "Fifth locative is reclaimed" (locative-ref loc5))
205    (test-equal "Sixth locative is NOT reclaimed" (locative-ref loc6) (vector-ref nested-held-onto-value 1))))
206
207
208(test-group "Testing that weak pairs get invalidated before finalizing would-be garbage"
209  (gc #t) ; Improve chances we don't get a minor GC in between
210  (let* ((not-held-onto-value (vector 42))
211	 (held-onto-value (vector 99))
212	 (garbage-a (vector (weak-cons not-held-onto-value '()) (weak-cons held-onto-value '()) #f))
213	 (garbage-b (vector (weak-cons not-held-onto-value '()) (weak-cons held-onto-value '()) #f))
214
215	 (garbage-a-weak-ref (weak-cons garbage-a '()))
216	 (garbage-b-weak-ref (weak-cons garbage-b '()))
217
218	 (observed-garbage-a-0 #f)
219	 (observed-garbage-a-1 #f)
220	 (observed-garbage-a-2 #f)
221	 (observed-garbage-b-0 #f)
222	 (observed-garbage-b-1 #f)
223	 (observed-garbage-b-2 #f))
224
225    ;; Garbage weakly references eachother
226    (vector-set! garbage-a 2 (weak-cons garbage-b '()))
227    (vector-set! garbage-b 2 (weak-cons garbage-a '()))
228
229    (set-finalizer! garbage-a (lambda (vec)
230				(set! observed-garbage-a-0 (car (vector-ref vec 0)))
231				(set! observed-garbage-a-1 (car (vector-ref vec 1)))
232				(set! observed-garbage-a-2 (car (vector-ref vec 2)))))
233    (set-finalizer! garbage-b (lambda (vec)
234				(set! observed-garbage-b-0 (car (vector-ref vec 0)))
235				(set! observed-garbage-b-1 (car (vector-ref vec 1)))
236				(set! observed-garbage-b-2 (car (vector-ref vec 2)))))
237
238    (set! not-held-onto-value #f)
239    (set! garbage-a #f)
240    (set! garbage-b #f)
241
242    ;; Must be a major collection, finalizers don't get queued on minor GC
243    ;; (gc #t)
244    ;; NOTE: The above won't work because it triggers *another* GC after running finalizers,
245    ;; which would invalidate all weak pairs anyway.  So instead, we create garbage until
246    ;; the finalizers have run.  This is more like what happens in a regular program.
247    (let lp ()
248      (unless (and observed-garbage-a-0 observed-garbage-b-0)
249	(make-vector 1000)
250	(lp)))
251
252    (test-assert "Weak pair's car which pointed to first garbage contains broken weak pointer" (bwp-object? (car garbage-a-weak-ref)))
253    (test-assert "Weak pair's car in first garbage which pointed to collected object contains broken weak pointer" (bwp-object? observed-garbage-a-0))
254    (test-equal "Weak pair's car in first garbage which pointed to retained object contains the original object" observed-garbage-a-1 held-onto-value)
255    (test-assert "Weak pair's car in first garbage which pointed to second garbage contains broken weak pointer" (bwp-object? observed-garbage-a-2))
256    (test-assert "Weak pair's car which pointed to second garbage contains broken weak pointer" (bwp-object? (car garbage-b-weak-ref)))
257    (test-assert "Weak pair's car in second garbage which pointed to collected object contains broken weak pointer" (bwp-object? observed-garbage-b-0))
258    (test-equal "Weak pair's car in second garbage which pointed to retained object contains the original object" observed-garbage-b-1 held-onto-value)
259    (test-assert "Weak pair's car in second garbage which pointed to first garbage contains broken weak pointer" (bwp-object? observed-garbage-b-2))))
260
261
262;; Safe version of locative-ref, returns !#bwp instead of raising an exception
263(define (weak-locative-ref loc)
264  (condition-case (locative-ref loc)
265    ((exn type) #!bwp)))
266
267(test-group "Testing that weak locatives get invalidated before finalizing would-be garbage"
268  (gc #t) ; Improve chances we don't get a minor GC in between
269  (let* ((not-held-onto-value (vector (vector 42)))
270	 (held-onto-value (vector (vector 99)))
271	 (garbage-a (vector (make-weak-locative not-held-onto-value 0) (make-weak-locative held-onto-value 0) #f))
272	 (garbage-b (vector (make-weak-locative not-held-onto-value 0) (make-weak-locative held-onto-value 0) #f))
273
274	 (garbage-a-weak-ref (make-weak-locative garbage-a 0))
275	 (garbage-b-weak-ref (make-weak-locative garbage-b 0))
276
277	 (observed-garbage-a-0 #f)
278	 (observed-garbage-a-1 #f)
279	 (observed-garbage-a-2 #f)
280	 (observed-garbage-b-0 #f)
281	 (observed-garbage-b-1 #f)
282	 (observed-garbage-b-2 #f))
283
284    ;; Garbage weakly references eachother
285    (vector-set! garbage-a 2 (make-weak-locative garbage-b 0))
286    (vector-set! garbage-b 2 (make-weak-locative garbage-a 0))
287
288    (set-finalizer! garbage-a (lambda (vec)
289				(set! observed-garbage-a-0 (weak-locative-ref (vector-ref vec 0)))
290				(set! observed-garbage-a-1 (weak-locative-ref (vector-ref vec 1)))
291				(set! observed-garbage-a-2 (weak-locative-ref (vector-ref vec 2)))))
292    (set-finalizer! garbage-b (lambda (vec)
293				(set! observed-garbage-b-0 (weak-locative-ref (vector-ref vec 0)))
294				(set! observed-garbage-b-1 (weak-locative-ref (vector-ref vec 1)))
295				(set! observed-garbage-b-2 (weak-locative-ref (vector-ref vec 2)))))
296
297    (set! not-held-onto-value #f)
298    (set! garbage-a #f)
299    (set! garbage-b #f)
300
301    ;; Must be a major collection, finalizers don't get queued on minor GC
302    ;; (gc #t)
303    ;; NOTE: The above won't work because it triggers *another* GC after running finalizers,
304    ;; which would invalidate all weak pairs anyway.  So instead, we create garbage until
305    ;; the finalizers have run.  This is more like what happens in a regular program.
306    (let lp ()
307      (unless (and observed-garbage-a-0 observed-garbage-b-0)
308	(make-vector 1000)
309	(lp)))
310
311    (test-assert "Weak locative which pointed to first garbage contains broken weak pointer" (bwp-object? (weak-locative-ref garbage-a-weak-ref)))
312    (test-assert "Weak locative in first garbage which pointed to collected object contains broken weak pointer" (bwp-object? observed-garbage-a-0))
313    (test-equal "Weak locative in first garbage which pointed to retained object contains the original object" observed-garbage-a-1 (vector-ref held-onto-value 0))
314    (test-assert "Weak locative in first garbage which pointed to second garbage contains broken weak pointer" (bwp-object? observed-garbage-a-2))
315    (test-assert "Weak locative which pointed to second garbage contains broken weak pointer" (bwp-object? (weak-locative-ref garbage-b-weak-ref)))
316    (test-assert "Weak locative in second garbage which pointed to collected object contains broken weak pointer" (bwp-object? observed-garbage-b-0))
317    (test-equal "Weak locative in second garbage which pointed to retained object contains the original object" observed-garbage-b-1 (vector-ref held-onto-value 0))
318    (test-assert "Weak locative in second garbage which pointed to first garbage contains broken weak pointer" (bwp-object? observed-garbage-b-2))))
319
320(test-exit)
Trap