config-value-parser.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. ;; This file is part of scheme-GNUnet.
  2. ;; Copyright (C) 2021 Maxime Devos
  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: AGPL3.0-or-later
  18. (use-modules (gnu gnunet config value-parser)
  19. (srfi srfi-26)
  20. (srfi srfi-43)
  21. (quickcheck)
  22. (quickcheck generator)
  23. (quickcheck property)
  24. (quickcheck arbitrary)
  25. ((rnrs conditions) #:select (&assertion))
  26. ((rnrs base) #:select (assert mod)))
  27. ;; (Incomplete) recollection of bugs found with these tests:
  28. ;; * [A] some exception types were not exported
  29. ;; * [A] off-by-one in value->choice
  30. ;; * [A] float-regex is too permissive, leading to crashes
  31. ;; * [A] incorrect detection of leading 0 in value->natural
  32. ;; * [A] some imports are missing
  33. ;; * [A] missing arguments to string-skip in convert-with-table
  34. ;; * [A] missing detection of empty number string in convert-with-table
  35. ;; * [A] incorrect detection of empty number string or missing unit
  36. ;; in convert-with-table, leading to crashes
  37. ;; * [A] comparison of character with number
  38. ;; * [A] variable naming errors in convert-with-table
  39. ;; * [A] value->natural allows too much syntax
  40. ;; * [A] size-values is missing an entry
  41. ;; * [A] missing argument to make-value-parse/size-error
  42. ;;
  43. ;; Tally: 14 [A]
  44. ;;
  45. ;; [A]: bug caught before patch was merged
  46. ;; Fresh object that is not eq? to anything else.
  47. (define *object* (cons '#f '#f))
  48. (define-syntax-rule (test-x-error value->x x msg text arg ...)
  49. (test-equal msg
  50. `(x ,text)
  51. (with-exception-handler
  52. (lambda (e)
  53. `(x ,(value-parse-error-text e)))
  54. (lambda ()
  55. (cons *object* (value->x text arg ...)))
  56. #:unwind? #t
  57. #:unwind-for-type x)))
  58. (define-syntax-rule (define-test-x-error test-y-error value->y y)
  59. (define-syntax test-y-error
  60. (syntax-rules ::: ()
  61. ((test-y-error msg text arg :::)
  62. (test-x-error value->y y msg text arg :::)))))
  63. (define-test-x-error test-natural-error
  64. value->natural &value-parse/natural-error)
  65. (define-test-x-error test-float-error
  66. value->float &value-parse/float-error)
  67. (define-test-x-error test-boolean-error
  68. value->boolean &value-parse/boolean-error)
  69. (define-test-x-error test-size-error
  70. value->size &value-parse/size-error)
  71. (define-test-x-error test-choice-error
  72. value->choice &value-parse/choice-error)
  73. (test-begin "value-parser")
  74. (test-equal "value->natural, valid"
  75. (iota 23)
  76. (map (compose value->natural number->string) (iota 23)))
  77. (test-equal "value->natural, valid (2)"
  78. #xdeadbeef (value->natural (number->string #xdeadbeef)))
  79. (test-natural-error "value->natural, multiple leading zeros" "00")
  80. (test-natural-error "value->natural, multiple leading zeros (2)" "001")
  81. (test-natural-error "value->natural, leading zero" "01")
  82. (test-natural-error "value->natural, empty string" "")
  83. (test-natural-error "value->natural, leading space" " 1")
  84. (test-natural-error "value->natural, trailing space" "1 ")
  85. (test-natural-error "value->natural, spaces" " ")
  86. (test-natural-error "value->natural, hexadecimal" "#xdeadbeef")
  87. ;; IEEE 754 makes a distinction between positive zero
  88. ;; and negative zero, with (/ 1 +0.0) = +inf.0 and
  89. ;; (/ 1 -0.0) = -inf.0
  90. ;;
  91. ;; In Guile 3.?, 0.0 and -0.0 are = but not eqv?.
  92. (test-skip (if (eqv? 0.0 -0.0) 1 0))
  93. (test-eqv "value->float, positive 0 (a)"
  94. 0.0
  95. (value->float "0.0"))
  96. (test-eqv "value->float, positive 0 (b)"
  97. 0.0
  98. (value->float "0."))
  99. (test-eqv "value->float, positive 0 (c)"
  100. 0.0
  101. (value->float ".0"))
  102. (test-eqv "value->float, positive 0 (d)"
  103. 0.0
  104. (value->float "0"))
  105. (test-equal "value->float, nothing before dot"
  106. (list 0.1 0.3 0.19 0.22)
  107. (map value->float '(".1" ".3" ".19" ".22")))
  108. (test-float-error "value->float, multiple 0" "00")
  109. (test-float-error "value->float, leading 0" "01")
  110. (test-equal "value->float, 0 and dot"
  111. 0.1
  112. (value->float "0.1"))
  113. (test-equal "value->float, leading 0 after dot"
  114. 1.001
  115. (value->float "1.001"))
  116. (test-equal "value->float, multiple 0 after dot"
  117. 1.0
  118. (value->float "1.000"))
  119. (test-float-error "value->float, hexadecimal" "#xdeadbeef")
  120. (test-equal "value->float, exact->inexact naturals"
  121. (map exact->inexact (iota 20))
  122. (map (compose value->float number->string) (iota 20)))
  123. ;; Powers of two are exactly representable in IEEE 754
  124. ;; (if exponent is not too large). Even then, (value->float "0.5")
  125. ;; should return a flonum and not the exact rational 1/2.
  126. (test-skip (if (equal? (map (compose inexact->exact exact->inexact
  127. (cut expt 2 <>))
  128. (iota 10 -5))
  129. (map (cut expt 2 <>) (iota 10 -5)))
  130. 0 1))
  131. (test-equal "value->float, exact->inexact fractionals"
  132. (map (compose exact->inexact (cut expt 2 <>))
  133. (iota 10 -5))
  134. (map (compose value->float number->string exact->inexact
  135. (cut expt 2 <>))
  136. (iota 10 -5)))
  137. ;; Whitespace is not allowed!
  138. (test-float-error "value->float, no leading spaces" " 1.0")
  139. (test-float-error "value->float, no trailing spaces" "1.0 ")
  140. (test-float-error "value->float, not empty!" "")
  141. (test-float-error "value->float, not only space!" " ")
  142. (test-float-error "value->float, not a single .!" ".")
  143. ;; TODO: should exponential notation 2e-3 = (* 2 (expt 10 -3))
  144. ;; be accepted?
  145. (test-equal "value->boolean, YES"
  146. #t
  147. (value->boolean "YES"))
  148. (test-equal "value->boolean, NO"
  149. #f
  150. (value->boolean "NO"))
  151. (define-syntax-rule (test-bool-error text extra)
  152. (test-boolean-error (string-append "value->boolean, " text extra)
  153. text))
  154. ;; We're not simply looking at the first or second
  155. ;; character or the length of the string.
  156. (test-bool-error "Y" " (invalid)")
  157. (test-bool-error "YE" " (invalid)")
  158. (test-bool-error "NOS" " (invalid)")
  159. (test-bool-error "NOSE" " (invalid)")
  160. (test-bool-error "N" " (invalid)")
  161. (test-bool-error "YES! " " (invalid)")
  162. ;; Case sensitive!
  163. (test-bool-error "yes" " (invalid case, 0)")
  164. (test-bool-error "Yes" " (invalid case, 1)")
  165. (test-bool-error "yEs" " (invalid case, 2)")
  166. (test-bool-error "yeS" " (invalid case, 3)")
  167. (test-bool-error "no" " (invalid case, 0)")
  168. (test-bool-error "No" " (invalid case, 1)")
  169. (test-bool-error "nO" " (invalid case, 2)")
  170. ;; Space are not allowed!
  171. (test-bool-error " YES" " (leading space)")
  172. (test-bool-error " NO" " (leading space)")
  173. (test-bool-error "YES " " (trailing space)")
  174. (test-bool-error "NO " " (trailing space)")
  175. (test-bool-error "" " (empty string)")
  176. (test-bool-error " " " (only space)")
  177. (define-syntax-rule (test-size-equal msg text val)
  178. (test-equal (string-append "value->size, " msg) val
  179. (value->size text)))
  180. (define-syntax-rule (test-binary-unit unit value exponent)
  181. (begin
  182. (assert (= value (expt 1024 exponent)))
  183. (test-size-equal (string-append "unit " unit)
  184. (string-append "1 " unit)
  185. (expt 1024 exponent))))
  186. ;; XXX not actually decimal
  187. (define-syntax-rule (test-decimal-unit unit value exponent)
  188. (begin
  189. (assert (= value (expt 1000 exponent)))
  190. (test-size-equal (string-append "unit " unit)
  191. (string-append "1 " unit)
  192. (expt 1000 exponent))))
  193. (define-syntax-rule (test-binary-units (unit value exponent) ...)
  194. (begin (test-binary-unit unit value exponent) ...))
  195. (define-syntax-rule (test-decimal-units (unit value exponent) ...)
  196. (begin (test-decimal-unit unit value exponent) ...))
  197. ;; Verify the unit table and some parsing code.
  198. ;; Sizes are copied from (coreutils)Block size
  199. (test-binary-units
  200. ("B" 1 0) ("KiB" 1024 1) ("MiB" 1048576 2) ("GiB" 1073741824 3)
  201. ("TiB" 1099511627776 4) ("PiB" 1125899906842624 5)
  202. ("EiB" 1152921504606846976 6))
  203. (test-decimal-units
  204. ("kB" 1000 1) ("MB" 1000000 2) ("GB" 1000000000 3)
  205. ("TB" 1000000000000 4) ("PB" 1000000000000000 5)
  206. ("EB" 1000000000000000000 6))
  207. (test-size-equal "value->size, multiple space in-between" "1 B" 1)
  208. (test-size-error "value->size, only space" " ")
  209. (test-size-error "value->size, empty string" "")
  210. (test-size-error "value->size, leading space" " 1 B")
  211. (test-size-error "value->size, trailing space" "1 B ")
  212. (test-size-error "value->size, negative" "-1 B")
  213. (test-size-error "value->size, fraction" "3/2 B")
  214. (test-size-error "value->size, flonum, 1" "1.5 B")
  215. (test-size-error "value->size, flonum, 2" "1. B")
  216. (test-size-error "value->size, flonum, 3" ".1 B")
  217. (test-size-error "value->size, leading zero" "01 B")
  218. (define (factorial n)
  219. (assert (and (integer? n)
  220. (exact? n)
  221. (>= n 0)))
  222. (let loop ((acc 1)
  223. (n n))
  224. (if (> n 1)
  225. (loop (* acc n) (- n 1))
  226. acc)))
  227. (assert (= (factorial 0) 1))
  228. (assert (= (factorial 1) 1))
  229. (assert (= (factorial 2) 2))
  230. (assert (= (factorial 3) 6))
  231. (assert (= (factorial 4) 24))
  232. (define (choose-permutation size)
  233. (choose-integer 0 (- (factorial size) 1)))
  234. ;; The Fisher-Yates shuffle, as described on Wikipedia,
  235. ;; but with random numbers extracted from PERMUTATION.
  236. (define (shuffle-vector vector permutation)
  237. (assert (and (integer? permutation)
  238. (exact? permutation)
  239. (>= permutation 0)))
  240. (let ((v (make-vector (vector-length vector))))
  241. (let loop ((i 0)
  242. (permutation permutation))
  243. (if (< i (vector-length v))
  244. (let ((j (mod permutation (+ i 1)))
  245. (rest (floor/ permutation (+ i 1))))
  246. ;; Except this assignment is unconditional.
  247. ;; (On Wikipedia "if j != i" is added.)
  248. (vector-set! v i (vector-ref v j))
  249. (vector-set! v j (vector-ref vector i))
  250. (loop (+ i 1) rest))
  251. (begin
  252. (assert (= permutation 0))
  253. v)))))
  254. (define choose-unit
  255. (choose-one (map generator-return '("KiB" "MiB" "GiB" "B" "kB" "MB"))))
  256. (define choose-value choose-byte) ; large enough
  257. (define choose-required-space-count (choose-integer 1 2))
  258. (define choose-optional-space-count (choose-integer 0 2))
  259. (define (choose-part-vector n)
  260. (choose-vector
  261. (generator-lift
  262. vector choose-required-space-count choose-value
  263. choose-optional-space-count choose-unit)
  264. (+ 1 n)))
  265. (define (parts->string part-vector)
  266. (call-with-output-string
  267. (lambda (out)
  268. (vector-for-each
  269. (lambda (i val)
  270. (apply (lambda (spaces-before value spaces-between unit)
  271. (unless (= i 0)
  272. (for-each (lambda _ (display " " out))
  273. (iota spaces-before)))
  274. (display value out)
  275. (for-each (lambda _ (display " " out))
  276. (iota spaces-between))
  277. (display unit out))
  278. (vector->list val)))
  279. part-vector))))
  280. (test-assert "value->size, morphism: (string-append, +)"
  281. (quickcheck
  282. (property ((parts (arbitrary
  283. (gen (sized-generator choose-part-vector))
  284. (xform #f))))
  285. (= (value->size (parts->string parts))
  286. (apply + (vector->list
  287. (vector-map
  288. (lambda (_ e)
  289. ((compose value->size parts->string vector) e))
  290. parts)))))))
  291. (test-assert "value->size, invariant under permutation"
  292. (quickcheck
  293. (property ((parts+property
  294. (arbitrary
  295. (gen (sized-generator
  296. (lambda (size)
  297. (generator-lift cons
  298. (choose-permutation size)
  299. (choose-part-vector size)))))
  300. (xform #f))))
  301. (= (value->size (parts->string (cdr parts+property)))
  302. (value->size (parts->string
  303. (shuffle-vector (cdr parts+property)
  304. (car parts+property))))))))
  305. (test-eq "value->choice, direct match"
  306. 'x
  307. (value->choice "x" #("x" x)))
  308. (test-eq "value->choice, match later"
  309. 'y
  310. (value->choice "y" #("x" x "y" y)))
  311. (test-eq "value->choice, match early"
  312. 'x
  313. (value->choice "x" #("x" x "y" y)))
  314. (test-choice-error "value->choice, empty vector"
  315. "x" #())
  316. (test-error "value->choice, bad text"
  317. &assertion
  318. (value->choice 0 #("x" x)))
  319. (test-error "value->choice, bad choices"
  320. &assertion
  321. (value->choice "x" '(("x" x))))
  322. (test-eq "value->choice, whitespace (left) left intact"
  323. 'y
  324. (value->choice " y" #("y" x " y" y)))
  325. (test-eq "value->choice, whitespace (right) left intact"
  326. 'y
  327. (value->choice " y" #("y" x " y" y)))
  328. (test-eq "value->choice, case sensitive (1)"
  329. 'upper
  330. (value->choice "X" #("x" lower "X" upper)))
  331. (test-eq "value->choice, case sensitive (2)"
  332. 'mixed
  333. (value->choice "Xy" #("XY" upper "xy" lower "Xy" mixed)))
  334. (test-eq "value->choice, case sensitive (3)"
  335. 'lower
  336. (value->choice "xy" #("xy" lower)))
  337. (test-assert "value->file-name, no-op"
  338. (quickcheck
  339. (property ((text ($string $char)))
  340. (string=? (value->file-name text) text))))
  341. (test-error "value->file-name, text must be a string"
  342. &assertion
  343. (value->file-name 'bad))
  344. (test-end "value-parser")