123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- ;; This file is part of scheme-GNUnet.
- ;; Copyright (C) 2021 Maxime Devos
- ;;
- ;; scheme-GNUnet is free software: you can redistribute it and/or modify it
- ;; under the terms of the GNU Affero General Public License as published
- ;; by the Free Software Foundation, either version 3 of the License,
- ;; or (at your option) any later version.
- ;;
- ;; scheme-GNUnet is distributed in the hope that it will be useful, but
- ;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;; Affero General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU Affero General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
- ;;
- ;; SPDX-License-Identifier: AGPL3.0-or-later
- (use-modules (gnu gnunet config value-parser)
- (srfi srfi-26)
- (srfi srfi-43)
- (quickcheck)
- (quickcheck generator)
- (quickcheck property)
- (quickcheck arbitrary)
- ((rnrs conditions) #:select (&assertion))
- ((rnrs base) #:select (assert mod)))
- ;; (Incomplete) recollection of bugs found with these tests:
- ;; * [A] some exception types were not exported
- ;; * [A] off-by-one in value->choice
- ;; * [A] float-regex is too permissive, leading to crashes
- ;; * [A] incorrect detection of leading 0 in value->natural
- ;; * [A] some imports are missing
- ;; * [A] missing arguments to string-skip in convert-with-table
- ;; * [A] missing detection of empty number string in convert-with-table
- ;; * [A] incorrect detection of empty number string or missing unit
- ;; in convert-with-table, leading to crashes
- ;; * [A] comparison of character with number
- ;; * [A] variable naming errors in convert-with-table
- ;; * [A] value->natural allows too much syntax
- ;; * [A] size-values is missing an entry
- ;; * [A] missing argument to make-value-parse/size-error
- ;;
- ;; Tally: 14 [A]
- ;;
- ;; [A]: bug caught before patch was merged
- ;; Fresh object that is not eq? to anything else.
- (define *object* (cons '#f '#f))
- (define-syntax-rule (test-x-error value->x x msg text arg ...)
- (test-equal msg
- `(x ,text)
- (with-exception-handler
- (lambda (e)
- `(x ,(value-parse-error-text e)))
- (lambda ()
- (cons *object* (value->x text arg ...)))
- #:unwind? #t
- #:unwind-for-type x)))
- (define-syntax-rule (define-test-x-error test-y-error value->y y)
- (define-syntax test-y-error
- (syntax-rules ::: ()
- ((test-y-error msg text arg :::)
- (test-x-error value->y y msg text arg :::)))))
- (define-test-x-error test-natural-error
- value->natural &value-parse/natural-error)
- (define-test-x-error test-float-error
- value->float &value-parse/float-error)
- (define-test-x-error test-boolean-error
- value->boolean &value-parse/boolean-error)
- (define-test-x-error test-size-error
- value->size &value-parse/size-error)
- (define-test-x-error test-choice-error
- value->choice &value-parse/choice-error)
- (test-begin "value-parser")
- (test-equal "value->natural, valid"
- (iota 23)
- (map (compose value->natural number->string) (iota 23)))
- (test-equal "value->natural, valid (2)"
- #xdeadbeef (value->natural (number->string #xdeadbeef)))
- (test-natural-error "value->natural, multiple leading zeros" "00")
- (test-natural-error "value->natural, multiple leading zeros (2)" "001")
- (test-natural-error "value->natural, leading zero" "01")
- (test-natural-error "value->natural, empty string" "")
- (test-natural-error "value->natural, leading space" " 1")
- (test-natural-error "value->natural, trailing space" "1 ")
- (test-natural-error "value->natural, spaces" " ")
- (test-natural-error "value->natural, hexadecimal" "#xdeadbeef")
- ;; IEEE 754 makes a distinction between positive zero
- ;; and negative zero, with (/ 1 +0.0) = +inf.0 and
- ;; (/ 1 -0.0) = -inf.0
- ;;
- ;; In Guile 3.?, 0.0 and -0.0 are = but not eqv?.
- (test-skip (if (eqv? 0.0 -0.0) 1 0))
- (test-eqv "value->float, positive 0 (a)"
- 0.0
- (value->float "0.0"))
- (test-eqv "value->float, positive 0 (b)"
- 0.0
- (value->float "0."))
- (test-eqv "value->float, positive 0 (c)"
- 0.0
- (value->float ".0"))
- (test-eqv "value->float, positive 0 (d)"
- 0.0
- (value->float "0"))
- (test-equal "value->float, nothing before dot"
- (list 0.1 0.3 0.19 0.22)
- (map value->float '(".1" ".3" ".19" ".22")))
- (test-float-error "value->float, multiple 0" "00")
- (test-float-error "value->float, leading 0" "01")
- (test-equal "value->float, 0 and dot"
- 0.1
- (value->float "0.1"))
- (test-equal "value->float, leading 0 after dot"
- 1.001
- (value->float "1.001"))
- (test-equal "value->float, multiple 0 after dot"
- 1.0
- (value->float "1.000"))
- (test-float-error "value->float, hexadecimal" "#xdeadbeef")
- (test-equal "value->float, exact->inexact naturals"
- (map exact->inexact (iota 20))
- (map (compose value->float number->string) (iota 20)))
- ;; Powers of two are exactly representable in IEEE 754
- ;; (if exponent is not too large). Even then, (value->float "0.5")
- ;; should return a flonum and not the exact rational 1/2.
- (test-skip (if (equal? (map (compose inexact->exact exact->inexact
- (cut expt 2 <>))
- (iota 10 -5))
- (map (cut expt 2 <>) (iota 10 -5)))
- 0 1))
- (test-equal "value->float, exact->inexact fractionals"
- (map (compose exact->inexact (cut expt 2 <>))
- (iota 10 -5))
- (map (compose value->float number->string exact->inexact
- (cut expt 2 <>))
- (iota 10 -5)))
- ;; Whitespace is not allowed!
- (test-float-error "value->float, no leading spaces" " 1.0")
- (test-float-error "value->float, no trailing spaces" "1.0 ")
- (test-float-error "value->float, not empty!" "")
- (test-float-error "value->float, not only space!" " ")
- (test-float-error "value->float, not a single .!" ".")
- ;; TODO: should exponential notation 2e-3 = (* 2 (expt 10 -3))
- ;; be accepted?
- (test-equal "value->boolean, YES"
- #t
- (value->boolean "YES"))
- (test-equal "value->boolean, NO"
- #f
- (value->boolean "NO"))
- (define-syntax-rule (test-bool-error text extra)
- (test-boolean-error (string-append "value->boolean, " text extra)
- text))
- ;; We're not simply looking at the first or second
- ;; character or the length of the string.
- (test-bool-error "Y" " (invalid)")
- (test-bool-error "YE" " (invalid)")
- (test-bool-error "NOS" " (invalid)")
- (test-bool-error "NOSE" " (invalid)")
- (test-bool-error "N" " (invalid)")
- (test-bool-error "YES! " " (invalid)")
- ;; Case sensitive!
- (test-bool-error "yes" " (invalid case, 0)")
- (test-bool-error "Yes" " (invalid case, 1)")
- (test-bool-error "yEs" " (invalid case, 2)")
- (test-bool-error "yeS" " (invalid case, 3)")
- (test-bool-error "no" " (invalid case, 0)")
- (test-bool-error "No" " (invalid case, 1)")
- (test-bool-error "nO" " (invalid case, 2)")
- ;; Space are not allowed!
- (test-bool-error " YES" " (leading space)")
- (test-bool-error " NO" " (leading space)")
- (test-bool-error "YES " " (trailing space)")
- (test-bool-error "NO " " (trailing space)")
- (test-bool-error "" " (empty string)")
- (test-bool-error " " " (only space)")
- (define-syntax-rule (test-size-equal msg text val)
- (test-equal (string-append "value->size, " msg) val
- (value->size text)))
- (define-syntax-rule (test-binary-unit unit value exponent)
- (begin
- (assert (= value (expt 1024 exponent)))
- (test-size-equal (string-append "unit " unit)
- (string-append "1 " unit)
- (expt 1024 exponent))))
- ;; XXX not actually decimal
- (define-syntax-rule (test-decimal-unit unit value exponent)
- (begin
- (assert (= value (expt 1000 exponent)))
- (test-size-equal (string-append "unit " unit)
- (string-append "1 " unit)
- (expt 1000 exponent))))
- (define-syntax-rule (test-binary-units (unit value exponent) ...)
- (begin (test-binary-unit unit value exponent) ...))
- (define-syntax-rule (test-decimal-units (unit value exponent) ...)
- (begin (test-decimal-unit unit value exponent) ...))
- ;; Verify the unit table and some parsing code.
- ;; Sizes are copied from (coreutils)Block size
- (test-binary-units
- ("B" 1 0) ("KiB" 1024 1) ("MiB" 1048576 2) ("GiB" 1073741824 3)
- ("TiB" 1099511627776 4) ("PiB" 1125899906842624 5)
- ("EiB" 1152921504606846976 6))
- (test-decimal-units
- ("kB" 1000 1) ("MB" 1000000 2) ("GB" 1000000000 3)
- ("TB" 1000000000000 4) ("PB" 1000000000000000 5)
- ("EB" 1000000000000000000 6))
- (test-size-equal "value->size, multiple space in-between" "1 B" 1)
- (test-size-error "value->size, only space" " ")
- (test-size-error "value->size, empty string" "")
- (test-size-error "value->size, leading space" " 1 B")
- (test-size-error "value->size, trailing space" "1 B ")
- (test-size-error "value->size, negative" "-1 B")
- (test-size-error "value->size, fraction" "3/2 B")
- (test-size-error "value->size, flonum, 1" "1.5 B")
- (test-size-error "value->size, flonum, 2" "1. B")
- (test-size-error "value->size, flonum, 3" ".1 B")
- (test-size-error "value->size, leading zero" "01 B")
- (define (factorial n)
- (assert (and (integer? n)
- (exact? n)
- (>= n 0)))
- (let loop ((acc 1)
- (n n))
- (if (> n 1)
- (loop (* acc n) (- n 1))
- acc)))
- (assert (= (factorial 0) 1))
- (assert (= (factorial 1) 1))
- (assert (= (factorial 2) 2))
- (assert (= (factorial 3) 6))
- (assert (= (factorial 4) 24))
- (define (choose-permutation size)
- (choose-integer 0 (- (factorial size) 1)))
- ;; The Fisher-Yates shuffle, as described on Wikipedia,
- ;; but with random numbers extracted from PERMUTATION.
- (define (shuffle-vector vector permutation)
- (assert (and (integer? permutation)
- (exact? permutation)
- (>= permutation 0)))
- (let ((v (make-vector (vector-length vector))))
- (let loop ((i 0)
- (permutation permutation))
- (if (< i (vector-length v))
- (let ((j (mod permutation (+ i 1)))
- (rest (floor/ permutation (+ i 1))))
- ;; Except this assignment is unconditional.
- ;; (On Wikipedia "if j != i" is added.)
- (vector-set! v i (vector-ref v j))
- (vector-set! v j (vector-ref vector i))
- (loop (+ i 1) rest))
- (begin
- (assert (= permutation 0))
- v)))))
- (define choose-unit
- (choose-one (map generator-return '("KiB" "MiB" "GiB" "B" "kB" "MB"))))
- (define choose-value choose-byte) ; large enough
- (define choose-required-space-count (choose-integer 1 2))
- (define choose-optional-space-count (choose-integer 0 2))
- (define (choose-part-vector n)
- (choose-vector
- (generator-lift
- vector choose-required-space-count choose-value
- choose-optional-space-count choose-unit)
- (+ 1 n)))
- (define (parts->string part-vector)
- (call-with-output-string
- (lambda (out)
- (vector-for-each
- (lambda (i val)
- (apply (lambda (spaces-before value spaces-between unit)
- (unless (= i 0)
- (for-each (lambda _ (display " " out))
- (iota spaces-before)))
- (display value out)
- (for-each (lambda _ (display " " out))
- (iota spaces-between))
- (display unit out))
- (vector->list val)))
- part-vector))))
- (test-assert "value->size, morphism: (string-append, +)"
- (quickcheck
- (property ((parts (arbitrary
- (gen (sized-generator choose-part-vector))
- (xform #f))))
- (= (value->size (parts->string parts))
- (apply + (vector->list
- (vector-map
- (lambda (_ e)
- ((compose value->size parts->string vector) e))
- parts)))))))
- (test-assert "value->size, invariant under permutation"
- (quickcheck
- (property ((parts+property
- (arbitrary
- (gen (sized-generator
- (lambda (size)
- (generator-lift cons
- (choose-permutation size)
- (choose-part-vector size)))))
- (xform #f))))
- (= (value->size (parts->string (cdr parts+property)))
- (value->size (parts->string
- (shuffle-vector (cdr parts+property)
- (car parts+property))))))))
- (test-eq "value->choice, direct match"
- 'x
- (value->choice "x" #("x" x)))
- (test-eq "value->choice, match later"
- 'y
- (value->choice "y" #("x" x "y" y)))
- (test-eq "value->choice, match early"
- 'x
- (value->choice "x" #("x" x "y" y)))
- (test-choice-error "value->choice, empty vector"
- "x" #())
- (test-error "value->choice, bad text"
- &assertion
- (value->choice 0 #("x" x)))
- (test-error "value->choice, bad choices"
- &assertion
- (value->choice "x" '(("x" x))))
- (test-eq "value->choice, whitespace (left) left intact"
- 'y
- (value->choice " y" #("y" x " y" y)))
- (test-eq "value->choice, whitespace (right) left intact"
- 'y
- (value->choice " y" #("y" x " y" y)))
- (test-eq "value->choice, case sensitive (1)"
- 'upper
- (value->choice "X" #("x" lower "X" upper)))
- (test-eq "value->choice, case sensitive (2)"
- 'mixed
- (value->choice "Xy" #("XY" upper "xy" lower "Xy" mixed)))
- (test-eq "value->choice, case sensitive (3)"
- 'lower
- (value->choice "xy" #("xy" lower)))
- (test-assert "value->file-name, no-op"
- (quickcheck
- (property ((text ($string $char)))
- (string=? (value->file-name text) text))))
- (test-error "value->file-name, text must be a string"
- &assertion
- (value->file-name 'bad))
- (test-end "value-parser")
|