test-hashtables.scm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. ;;; Copyright (C) 2024 David Thompson <dave@spritely.institute>
  2. ;;; Copyright (C) 2023, 2024 Robin Templeton
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; Hashtable tests.
  18. ;;;
  19. ;;; Code:
  20. (use-modules (srfi srfi-64)
  21. (test utils))
  22. (test-begin "test-hashtables")
  23. (with-additional-imports ((hoot hashtables))
  24. ;; Hashing numbers
  25. (test-call "6" (lambda () (hashq 42 37)))
  26. ;; Hashing pairs and lists.
  27. (test-call "228" (lambda () (hash '(a . b) 389)))
  28. (test-call "94" (lambda () (hash '(a b) 389)))
  29. ;; Deeply nested list.
  30. (test-call "69" (lambda () (hash '(a (b (c (d (e (f (g (h (i))))))))) 389)))
  31. ;; Circular list!
  32. (test-call "65"
  33. (lambda ()
  34. (let ((x (list 'a 'b 'c)))
  35. (set-cdr! (cdr (cdr x)) x)
  36. (hash x 389))))
  37. ;; Hash composition should not be commutative.
  38. (test-call "#f" (lambda () (= (hash '(a . b) 389) (hash '(b . a) 389))))
  39. ;; Hashing vectors of different length.
  40. (test-call "200" (lambda () (hash #() 389)))
  41. (test-call "222" (lambda () (hash #(1 2 3) 389)))
  42. ;; Hashing bytevectors of different length.
  43. (test-call "51" (lambda () (hash #vu8() 389)))
  44. (test-call "155" (lambda () (hash #vu8(1) 389)))
  45. (test-call "224" (lambda () (hash #vu8(1 2) 389)))
  46. (test-call "294" (lambda () (hash #vu8(1 2 3) 389)))
  47. (test-call "206" (lambda () (hash #vu8(1 2 3 4) 389)))
  48. ;; Hashing bitvectors of different length.
  49. (test-call "173" (lambda () (hash #* 389)))
  50. (test-call "195" (lambda () (hash #*1010 389)))
  51. (test-call "119" (lambda () (hash #*01010 389)))
  52. ;; Empty bytevector should have different hash than empty bitvector.
  53. (test-call "#f" (lambda () (= (hash #vu8() 389) (hash #* 389))))
  54. ;; Hashing records.
  55. (test-call "222"
  56. (lambda ()
  57. (define-record-type q (make-q a) q? (a q-a))
  58. (hash (make-q 42) 389)))
  59. (define-syntax-rule (test-hashtable-impl make-hashtable
  60. make-eq-hashtable
  61. make-eqv-hashtable
  62. hashtable?
  63. hashtable-hash
  64. hashtable-equiv
  65. hashtable-size
  66. hashtable-ref
  67. hashtable-set!
  68. hashtable-delete!
  69. hashtable-clear!
  70. hashtable-contains?
  71. hashtable-copy
  72. hashtable-keys
  73. hashtable-values
  74. hashtable-for-each
  75. hashtable-fold)
  76. (begin
  77. ;; Ref hit
  78. (test-call "b"
  79. (lambda ()
  80. (let ((ht (make-eq-hashtable)))
  81. (hashtable-set! ht 'a 'b)
  82. (hashtable-ref ht 'a))))
  83. ;; Ref miss
  84. (test-call "#f"
  85. (lambda ()
  86. (let ((ht (make-eq-hashtable)))
  87. (hashtable-set! ht 'x 'y)
  88. (hashtable-ref ht 'a))))
  89. ;; Ref miss with default
  90. (test-call "b"
  91. (lambda ()
  92. (let ((ht (make-eq-hashtable)))
  93. (hashtable-set! ht 'x 'y)
  94. (hashtable-ref ht 'a 'b))))
  95. ;; Key insertion increases size
  96. (test-call "1"
  97. (lambda ()
  98. (let ((ht (make-eq-hashtable)))
  99. (hashtable-set! ht 'a 'b)
  100. (hashtable-size ht))))
  101. ;; Key deletion
  102. (test-call "#f"
  103. (lambda ()
  104. (let ((ht (make-eq-hashtable)))
  105. (hashtable-set! ht 'a 'b)
  106. (hashtable-delete! ht 'a)
  107. (hashtable-contains? ht 'a))))
  108. ;; Key deletion decrements size
  109. (test-call "0"
  110. (lambda ()
  111. (let ((ht (make-eq-hashtable)))
  112. (hashtable-set! ht 'a 'b)
  113. (hashtable-delete! ht 'a)
  114. (hashtable-size ht))))
  115. ;; Key deletion miss does not decrement size
  116. (test-call "1"
  117. (lambda ()
  118. (let ((ht (make-eq-hashtable)))
  119. (hashtable-set! ht 'a 'b)
  120. (hashtable-delete! ht 'c)
  121. (hashtable-size ht))))
  122. ;; Check for existing key
  123. (test-call "#t"
  124. (lambda ()
  125. (let ((ht (make-eq-hashtable)))
  126. (hashtable-set! ht 'a 'b)
  127. (hashtable-contains? ht 'a))))
  128. ;; Overwrite value for key
  129. (test-call "c"
  130. (lambda ()
  131. (let ((ht (make-eq-hashtable)))
  132. (hashtable-set! ht 'a 'b)
  133. (hashtable-set! ht 'a 'c)
  134. (hashtable-ref ht 'a))))
  135. ;; Copy
  136. (test-call "(2 b d)"
  137. (lambda ()
  138. (let ((ht (make-eq-hashtable)))
  139. (hashtable-set! ht 'a 'b)
  140. (hashtable-set! ht 'c 'd)
  141. (let ((ht* (hashtable-copy ht)))
  142. (list (hashtable-size ht*)
  143. (hashtable-ref ht* 'a)
  144. (hashtable-ref ht* 'c))))))
  145. ;; Clear sets size to 0
  146. (test-call "0"
  147. (lambda ()
  148. (let ((ht (make-eq-hashtable)))
  149. (hashtable-set! ht 'a 'b)
  150. (hashtable-clear! ht)
  151. (hashtable-size ht))))
  152. ;; Clear removes all associations
  153. (test-call "#f"
  154. (lambda ()
  155. (let ((ht (make-eq-hashtable)))
  156. (hashtable-set! ht 'a 'b)
  157. (hashtable-clear! ht)
  158. (hashtable-contains? ht 'a))))
  159. ;; Keys of an empty table
  160. (test-call "()"
  161. (lambda ()
  162. (hashtable-keys (make-eq-hashtable))))
  163. ;; Keys of a populated table
  164. (test-call "(a)"
  165. (lambda ()
  166. (let ((ht (make-eq-hashtable)))
  167. (hashtable-set! ht 'a 'b)
  168. (hashtable-keys ht))))
  169. ;; Values of an empty table
  170. (test-call "()"
  171. (lambda ()
  172. (hashtable-values (make-eq-hashtable))))
  173. ;; Values of a populated table
  174. (test-call "(b)"
  175. (lambda ()
  176. (let ((ht (make-eq-hashtable)))
  177. (hashtable-set! ht 'a 'b)
  178. (hashtable-values ht))))
  179. ;; For each iteration
  180. (test-call "(a b)"
  181. (lambda ()
  182. (let ((ht (make-eq-hashtable))
  183. (result #f))
  184. (hashtable-set! ht 'a 'b)
  185. (hashtable-for-each (lambda (k v)
  186. (set! result (list k v)))
  187. ht)
  188. result)))
  189. ;; Fold (result order is technically unspecified but we know what it
  190. ;; will be)
  191. (test-call "((a . b) (c . d))"
  192. (lambda ()
  193. (let ((ht (make-eq-hashtable))
  194. (result #f))
  195. (hashtable-set! ht 'a 'b)
  196. (hashtable-set! ht 'c 'd)
  197. (hashtable-fold (lambda (k v prev)
  198. (cons (cons k v) prev))
  199. '()
  200. ht))))
  201. ;; Grow/shrink
  202. (test-call "100"
  203. (lambda ()
  204. (let ((ht (make-eq-hashtable)))
  205. (do ((i 0 (1+ i)))
  206. ((= i 100))
  207. (hashtable-set! ht i i))
  208. (do ((i 0 (1+ i)))
  209. ((= i 100))
  210. (hashtable-delete! ht i))
  211. (do ((i 0 (1+ i)))
  212. ((= i 100))
  213. (hashtable-set! ht i i))
  214. (hashtable-size ht))))))
  215. (test-hashtable-impl make-hashtable
  216. make-eq-hashtable
  217. make-eqv-hashtable
  218. hashtable?
  219. hashtable-hash
  220. hashtable-equiv
  221. hashtable-size
  222. hashtable-ref
  223. hashtable-set!
  224. hashtable-delete!
  225. hashtable-clear!
  226. hashtable-contains?
  227. hashtable-copy
  228. hashtable-keys
  229. hashtable-values
  230. hashtable-for-each
  231. hashtable-fold)
  232. ;; FIXME: These would need to be run in an async context in order
  233. ;; for any finalization to happen in the Hoot VM, but at least we
  234. ;; ensure that the main interface is working.
  235. (test-hashtable-impl make-weak-key-hashtable
  236. make-eq-weak-key-hashtable
  237. make-eqv-weak-key-hashtable
  238. weak-key-hashtable?
  239. weak-key-hashtable-hash
  240. weak-key-hashtable-equiv
  241. weak-key-hashtable-size
  242. weak-key-hashtable-ref
  243. weak-key-hashtable-set!
  244. weak-key-hashtable-delete!
  245. weak-key-hashtable-clear!
  246. weak-key-hashtable-contains?
  247. weak-key-hashtable-copy
  248. weak-key-hashtable-keys
  249. weak-key-hashtable-values
  250. weak-key-hashtable-for-each
  251. weak-key-hashtable-fold)
  252. (test-hashtable-impl make-weak-value-hashtable
  253. make-eq-weak-value-hashtable
  254. make-eqv-weak-value-hashtable
  255. weak-value-hashtable?
  256. weak-value-hashtable-hash
  257. weak-value-hashtable-equiv
  258. weak-value-hashtable-size
  259. weak-value-hashtable-ref
  260. weak-value-hashtable-set!
  261. weak-value-hashtable-delete!
  262. weak-value-hashtable-clear!
  263. weak-value-hashtable-contains?
  264. weak-value-hashtable-copy
  265. weak-value-hashtable-keys
  266. weak-value-hashtable-values
  267. weak-value-hashtable-for-each
  268. weak-value-hashtable-fold)
  269. (test-hashtable-impl make-doubly-weak-hashtable
  270. make-eq-doubly-weak-hashtable
  271. make-eqv-doubly-weak-hashtable
  272. doubly-weak-hashtable?
  273. doubly-weak-hashtable-hash
  274. doubly-weak-hashtable-equiv
  275. doubly-weak-hashtable-size
  276. doubly-weak-hashtable-ref
  277. doubly-weak-hashtable-set!
  278. doubly-weak-hashtable-delete!
  279. doubly-weak-hashtable-clear!
  280. doubly-weak-hashtable-contains?
  281. doubly-weak-hashtable-copy
  282. doubly-weak-hashtable-keys
  283. doubly-weak-hashtable-values
  284. doubly-weak-hashtable-for-each
  285. doubly-weak-hashtable-fold))
  286. ;; Guile legacy API
  287. (with-imports ((guile))
  288. (test-call "42"
  289. (lambda ()
  290. (let ((table (make-hash-table)))
  291. (hashq-set! table 'foo 42)
  292. (hashq-ref table 'foo))))
  293. (test-call "#f"
  294. (lambda ()
  295. (let ((table (make-hash-table)))
  296. (hash-set! table "foo" 42)
  297. (hash-remove! table "foo")
  298. (hash-ref table "foo"))))
  299. (test-call "42"
  300. (lambda ()
  301. (let ((table (make-weak-key-hash-table)))
  302. (hashq-set! table 'foo 42)
  303. (hashq-ref table 'foo))))
  304. (test-call "((baz . 3) (bar . 2) (foo . 1))"
  305. (lambda ()
  306. (let ((table (make-hash-table)))
  307. (hashq-set! table 'foo 1)
  308. (hashq-set! table 'bar 2)
  309. (hashq-set! table 'baz 3)
  310. (hash-map->list cons table))))
  311. (test-call "3"
  312. (lambda ()
  313. (let ((table (make-hash-table)))
  314. (hash-set! table "foo" 1)
  315. (hash-set! table "bar" 2)
  316. (hash-set! table "baz" 3)
  317. (hash-count (lambda (key val) #t) table))))
  318. ;; clear, fold, and for-each on an empty table should no-op because
  319. ;; we don't yet know the concrete table type.
  320. (test-call "#t"
  321. (lambda ()
  322. (let ((table (make-hash-table)))
  323. (hash-clear! table)
  324. #t)))
  325. (test-call "0"
  326. (lambda ()
  327. (let ((table (make-hash-table)))
  328. (hash-fold (lambda (key val sum)
  329. (+ sum val))
  330. 0 table))))
  331. (test-call "0"
  332. (lambda ()
  333. (let ((count 0)
  334. (table (make-hash-table)))
  335. (hash-for-each (lambda (key val)
  336. (set! count (1+ count)))
  337. table)
  338. count))))
  339. (test-end* "test-hashtables")