config-db.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. ;; This file is part of scheme-GNUnet.
  2. ;; Copyright (C) 2021 GNUnet e.V.
  3. ;;
  4. ;; scheme-GNUnet is free software: you can redistribute it and/or modify it
  5. ;; under the terms of the GNU Affero General Public License as published
  6. ;; by the Free Software Foundation, either version 3 of the License,
  7. ;; or (at your option) any later version.
  8. ;;
  9. ;; scheme-GNUnet is distributed in the hope that it will be useful, but
  10. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Affero General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Affero General Public License
  15. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. ;;
  17. ;; SPDX-License-Identifier: AGPL-3.0-or-later
  18. ;; Bugs found with these tests:
  19. ;; * [I] missing arguments to %make
  20. ;; * [I] forgot to export &unwritable-key-error and friends
  21. ;; * [I] forgot to export undefine-key!
  22. ;; * [I] missing arguments for default-set-value!/raw
  23. ;; * [I] undefine-key! on configurations backed by a hash table
  24. ;; did not produce an exception
  25. (use-modules (gnu gnunet config db)
  26. (rnrs hashtables)
  27. (srfi srfi-8)
  28. ((rnrs base) #:select (assert))
  29. (ice-9 control))
  30. ;; Convert the exception into a S-expression
  31. ;; to be able to compare results with @code{equal?}.
  32. (define (call-with-return-exceptions fun . args)
  33. (with-exception-handler
  34. (lambda (e)
  35. (list
  36. (cond ((undefined-key-error? e) 'not-found)
  37. ((unwritable-key-error? e) 'unwritable)
  38. ((unundefinable-key-error? e) 'unundefinable))
  39. (config-error-section e)
  40. (config-error-key e)))
  41. (lambda () (apply fun args))
  42. #:unwind? #t
  43. #:unwind-for-type &config-error))
  44. (define (read-value/scatch config section key)
  45. (call-with-return-exceptions
  46. (lambda ()
  47. `(found . ,(read-value identity config section key)))))
  48. (define (set-value!/s config section key value)
  49. (set-value! identity config section key value))
  50. (define (set-value!/scatch config section key value)
  51. (call-with-return-exceptions
  52. (lambda () (set-value!/s config section key value))))
  53. (define (undefine-key!/catch config section key)
  54. (call-with-return-exceptions
  55. (lambda () (undefine-key! config section key) 'ok)))
  56. (define (alist->hash alist)
  57. (let ((h (make-hashtable hash-key key=?)))
  58. (for-each (lambda (key+value)
  59. (hashtable-set! h (car key+value) (cdr key+value)))
  60. alist)
  61. h))
  62. (test-equal "make-configuration return types"
  63. '(#t #t #t #t)
  64. (receive (c set-read-value/raw! set-set-value!/raw! set-undefine-key!!)
  65. (make-configuration)
  66. (list (configuration? c)
  67. (procedure? set-read-value/raw!)
  68. (procedure? set-set-value!/raw!)
  69. (procedure? set-undefine-key!!))))
  70. (define-syntax-rule (test-eqnh desc . rest)
  71. (test-equal (string-append "hash->configuration, " desc) . rest))
  72. (define-syntax-rule (test-newhash-read desc expected alist section key)
  73. (test-equal (string-append "hash->configuration, read-value, " desc)
  74. expected
  75. (read-value/scatch
  76. (hash->configuration (alist->hash alist))
  77. section key)))
  78. (test-newhash-read "match" '(found . "value") '((("section" . "x") . "value"))
  79. "section" "x")
  80. (test-newhash-read "section does not match"
  81. '(not-found "sect" "x")
  82. '((("section" . "x") . "value"))
  83. "sect" "x")
  84. (test-newhash-read "key does not match"
  85. '(not-found "section" "y")
  86. '((("section" . "x") . "value"))
  87. "section" "y")
  88. (define-syntax-rule (test-reflect desc alist
  89. (h c . rest)
  90. (section key expected)
  91. (section* key* expected*)
  92. mutate)
  93. (test-eqnh desc
  94. '(expected expected*)
  95. (let ((h (alist->hash alist)))
  96. (receive (c . rest) (hash->configuration h)
  97. (let ((old (read-value/scatch c section key)))
  98. mutate
  99. (list old (read-value/scatch c section* key*)))))))
  100. ;; In the docstring, it is specified the hash table is used
  101. ;; -- not a *copy* of the hash table.
  102. (test-reflect "read-value reflects hash (modified value)"
  103. '((("section" . "x") . "value"))
  104. (h c . _)
  105. ("section" "x" (found . "value"))
  106. ("section" "x" (found . "value2"))
  107. (hashtable-set! h '("section" . "x") "value2"))
  108. (test-reflect "read-value reflects hash (deleted value)"
  109. '((("section" . "x") . "value"))
  110. (h c . _)
  111. ("section" "x" (found . "value"))
  112. ("section" "x" (not-found "section" "x"))
  113. (hashtable-delete! h '("section" . "x")))
  114. (test-reflect "read-value reflects hash (new value)"
  115. '()
  116. (h c . _)
  117. ("section" "x" (not-found "section" "x"))
  118. ("section" "x" (found . "value"))
  119. (hashtable-set! h '("section" . "x") "value"))
  120. ;; The hash table is modified, not copied.
  121. ;; Also, new values are visible from read-value.
  122. (test-reflect "set-value! & read-value, in-place (new)"
  123. '()
  124. (h c . _)
  125. ("section" "x" (not-found "section" "x"))
  126. ("section" "x" (found . "value"))
  127. (begin
  128. (set-value!/s c "section" "x" "value")
  129. (assert (hashtable-contains? h `(,"section" . ,"x")))))
  130. ;; Make sure all callentries are adjusted to use the new hash.
  131. (test-reflect "read-value reflects new hash (modified value)"
  132. '((("section" . "x") . "value"))
  133. (h c set-hash!)
  134. ("section" "x" (found . "value"))
  135. ("section" "x" (found . "value2"))
  136. (set-hash! (alist->hash '((("section" . "x") . "value2")))))
  137. (test-reflect "read-value reflects new hash (deleted value)"
  138. '((("section" . "x") . "value"))
  139. (h c set-hash!)
  140. ("section" "x" (found . "value"))
  141. ("section" "x" (not-found "section" "x"))
  142. (set-hash! (alist->hash '())))
  143. (test-reflect "read-value reflects new hash (new value)"
  144. '()
  145. (h c set-hash!)
  146. ("section" "x" (not-found "section" "x"))
  147. ("section" "x" (found . "value"))
  148. (set-hash! (alist->hash '((("section" . "x") . "value")))))
  149. ;; Changing from a mutable to immutable hash (set-value!).
  150. ;;
  151. ;; set-hash! might have forgotten to change the set-value!
  152. ;; callentry correctly, in which case:
  153. ;; (a) the callentry uses the new (immutable) hash,
  154. ;; and tries to modify it. In that case, (rnrs hashtables)
  155. ;; would raise an exception, which will not be &unwritable-key-error.
  156. ;; --> FAIL.
  157. ;; (b) the callentry is unchanged, and uses the old hash. In that case,
  158. ;; no exception would be raised.
  159. ;; --> FAIL
  160. (test-eqnh "set-value! fails gracefully (mutable -> immutable hash)"
  161. '(unwritable "the-section" "the-key")
  162. (receive (c set-hash!)
  163. (hash->configuration (alist->hash '()))
  164. (set-hash! (hashtable-copy (alist->hash '()) #f))
  165. (set-value!/scatch c "the-section" "the-key" "the-value")))
  166. ;; Changing from an immutable to mutable hash (set-value!).
  167. ;;
  168. ;; set-hash! might have forgotten to change the set-value!
  169. ;; callentry correctly, in which case:
  170. ;; (a) the callentry uses the new (mutable) hash, but believes it to be
  171. ;; immutable, resulting in an &unwritable-key-error.
  172. ;; --> FAIL.
  173. ;; (b) the callentry is unchanged, and uses the old hash, resulting in
  174. ;; an &unwritable-key-error
  175. ;; --> FAIL.
  176. (test-eqnh "set-value! + read-value succeeds (immutable -> mutable hash)"
  177. '(found . "the-value")
  178. (receive (c set-hash!)
  179. (hash->configuration (hashtable-copy (alist->hash '()) #f))
  180. (set-hash! (alist->hash '()))
  181. (set-value!/s c "the-section" "the-key" "the-value")
  182. (read-value/scatch c "the-section" "the-key")))
  183. ;; Changing from a mutable to immutable hash (undefine-key!).
  184. ;;
  185. ;; set-hash! might have forgotten to change the undefine-key!
  186. ;; callentry, in which case:
  187. ;; (a) the callentry uses the new (immutable) hash, but believes it to
  188. ;; be mutable, resulting in an exception from (rnrs hashtables)
  189. ;; instead of an &unundefinable-key-error.
  190. ;; --> FAIL
  191. ;; (b) the callentry uses the old (mutable) hash, in which case no
  192. ;; &unundefinable-key-error is raised.
  193. ;; --> FAIL
  194. (test-eqnh "undefine-key! fails (mutable -> immutable, key exists)"
  195. '(unundefinable "a-section" "a-key")
  196. (receive (c set-hash!)
  197. (hash->configuration
  198. (alist->hash '((("a-section" . "a-key") "a-value"))))
  199. (set-hash!
  200. (hashtable-copy (alist->hash '((("a-section" . "a-key") "a-value")))
  201. #f))
  202. (undefine-key!/catch c "a-section" "a-key")))
  203. ;; undefine-key! should fail because there is no such key to undefine.
  204. (test-eqnh "undefine-key! fails (mutable -> immutable, key does not exists)"
  205. '(unundefinable "a-section" "a-key")
  206. (receive (c set-hash!)
  207. (hash->configuration (alist->hash '()))
  208. (set-hash! (hashtable-copy (alist->hash '()) #f))
  209. (undefine-key!/catch c "a-section" "a-key")))
  210. (test-eqnh "undefine-key! fails (mutable -> immutable, key disappears)"
  211. '(unundefinable "a-section" "a-key")
  212. (receive (c set-hash!)
  213. (hash->configuration
  214. (alist->hash '((("a-section" . "a-key") "a-value"))))
  215. (set-hash! (hashtable-copy (alist->hash '()) #f))
  216. (undefine-key!/catch c "a-section" "a-key")))
  217. (test-eqnh "undefine-key! fails (mutable -> immutable, key appears)"
  218. '(unundefinable "a-section" "a-key")
  219. (receive (c set-hash!)
  220. (hash->configuration (alist->hash '()))
  221. (set-hash!
  222. (hashtable-copy (alist->hash '((("a-section" . "a-key") . "a-value")))
  223. #f))
  224. (undefine-key!/catch c "a-section" "a-key")))
  225. ;; Changing from a mutable to immutable hash (undefine-key!).
  226. ;;
  227. ;; set-hash! might have forgotten to change the undefine-key!
  228. ;; callentry, in which case:
  229. ;; (a) the undefine-key! callentry believes the hash table
  230. ;; is still immutable, leading to an &unundefinable-key-error
  231. ;; (b) the undefine-key! callentry uses the new hash table,
  232. ;; but believes it is immutable, leading to an &unundefinable-key-error
  233. (test-eqnh "undefine-key! succeeds correctly (immutable -> mutable, key exists)"
  234. '(ok . #f)
  235. (receive (c set-hash!)
  236. (hash->configuration
  237. (hashtable-copy (alist->hash '((("b-section" . "b-key") . "b-value")))
  238. #f))
  239. (let ((new (hashtable-copy
  240. (alist->hash '((("b-section" . "b-key") . "b-value")))
  241. #t)))
  242. (set-hash! new)
  243. (let ((u (undefine-key!/catch c "b-section" "b-key")))
  244. (cons u (hashtable-contains? new '("b-section" . "b-key")))))))
  245. (test-eqnh "undefine-key! succeeds correctly (immutable -> mutable, key appears)"
  246. '(ok . #f)
  247. (receive (c set-hash!)
  248. (hash->configuration
  249. (hashtable-copy (alist->hash '()) #f))
  250. (let ((new (alist->hash '((("b-section" . "b-key") . "b-value")))))
  251. (set-hash! new)
  252. (let ((u (undefine-key!/catch c "b-section" "b-key")))
  253. (cons u (hashtable-contains? new '("b-section" . "b-key")))))))
  254. (test-eqnh "undefine-key! fails correctly (immutable -> mutable, key does not exist)"
  255. '((unundefinable "b-section" "b-key") . #f)
  256. (receive (c set-hash!)
  257. (hash->configuration
  258. (hashtable-copy (alist->hash '()) #f))
  259. (let ((new (alist->hash '())))
  260. (set-hash! new)
  261. (let ((u (undefine-key!/catch c "b-section" "b-key")))
  262. (cons u (hashtable-contains? new '("b-section" . "b-key")))))))
  263. (test-eqnh "undefine-key! fails correctly (immutable -> mutable, key disappears)"
  264. '((unundefinable "c-section" "c-key") . #f)
  265. (receive (c set-hash!)
  266. (hash->configuration
  267. (hashtable-copy (alist->hash '((("c-section" . "c-key") . "c-value")))
  268. #f))
  269. (let ((new (alist->hash '())))
  270. (set-hash! new)
  271. (let ((u (undefine-key!/catch c "c-section" "c-key")))
  272. (cons u (hashtable-contains? new '("c-section" . "c-key")))))))
  273. (test-eqnh "undefine-key! is not simply hashtable-clear!"
  274. '(found . "w")
  275. (receive (c _)
  276. (hash->configuration
  277. (alist->hash '((("x" . "y") . "z") (("u" . "v") . "w"))))
  278. (undefine-key! c "x" "y")
  279. (read-value/scatch c "u" "v")))
  280. ;; We've neglected the object->value an value->object arguments
  281. ;; in the previous tests.
  282. (test-equal "read-value, string->number"
  283. #x12
  284. (read-value string->number (hash->configuration
  285. (alist->hash '((("x" . "y") . "#x12")))) "x" "y"))
  286. (define (calls-in-tail-position? proc)
  287. (= 1 (stack-length (make-stack (let ((t (make-prompt-tag 'tail-position?)))
  288. (call-with-prompt t
  289. (lambda () (proc
  290. (lambda () (abort-to-prompt t))))
  291. identity))))))
  292. (test-assert "read-value, object->value in tail position"
  293. (calls-in-tail-position?
  294. (let ((c (hash->configuration (alist->hash '((("x" . "y") . "#x12"))))))
  295. (lambda (thunk)
  296. (read-value (lambda (x) (thunk)) c "x" "y")))))
  297. (test-equal "set-value!, object->value has correct argument"
  298. 'value
  299. (let/ec ec
  300. (set-value! ec
  301. (hash->configuration (alist->hash '()))
  302. "section" "key"
  303. 'value)
  304. 'what))
  305. ;; TODO: verify
  306. ;; Replacing the hash table is not an atomic operation;
  307. ;; while the hash table is being replaced, either the new or the old hash
  308. ;; table will be used by the callentries.
  309. ;; Check the defaults callentries.
  310. (test-equal "read-value, default callentry"
  311. '(not-found "x" "y")
  312. (read-value/scatch (make-configuration) "x" "y"))
  313. (test-equal "set-value!, default callentry"
  314. '(unwritable "x" "y")
  315. (set-value!/scatch (make-configuration) "x" "y" "z"))
  316. (test-equal "undefine-key!, default callentry"
  317. '(unundefinable "x" "y")
  318. (undefine-key!/catch (make-configuration) "x" "y"))