#!/usr/bin/env sh
# -*- wisp -*-
guile -L $(dirname $(dirname $(realpath "$0"))) -c '(import (language wisp spec))'
exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples doctests) main)' -s "$0" "$@"
; !#
;;; doctests --- simple testing by adding procedure-properties with tests.
;;; Usage
;; Add a tests property to a procedure to have simple unit tests.
;; Simple tests:
;;
;; (define (A)
;; #((tests (test-eqv 'A (A))
;; (test-assert #t)))
;; 'A)
;;
;; Named tests:
;;
;; (define (A)
;; #((tests ('test1 (test-eqv 'A (A))
;; (test-assert #t))
;; ('test2 (test-assert #t))))
;; 'A)
;;
;; Allows for docstrings:
;;
;; (define (A)
;; "returns 'A"
;; #((tests (test-eqv 'A (A))
;; (test-assert #t)))
;; 'A)
;; For writing the test before the implementation, start with the test and #f:
;; (define (A)
;; #((tests (test-eqv 'A (A))))
;; #f)
;; With wisp, you currently need to use the literal #((tests (...)))
;; TODO: add array parsing to wisp following quoting with ':
;; # a b → #(a b) and # : a b c → #((a b))
define-module : examples doctests
. #:export : doctests-testmod
import : ice-9 optargs
ice-9 rdelim
ice-9 match
ice-9 pretty-print
oop goops
texinfo reflection
; define basic dir
define* (dir #:key (all? #f))
if all?
map (λ (x) (cons (module-name x)
(module-map (λ (sym var) sym) (resolve-interface (module-name x)))))
cons (current-module) : module-uses (current-module)
module-map (λ (sym var) sym) (current-module)
; add support for giving the module as argument
define-generic dir
define-method (dir (all? <boolean>)) (dir #:all? all?)
define-method (dir (m <list>)) (module-map (λ (sym var) sym) (resolve-interface m))
; add support for using modules directly (interfaces are also modules, so this catches both)
define-method (dir (m <module>)) (module-map (λ (sym var) sym) m)
define : string-index s fragment
. "return the index of the first character of the FRAGMENT in string S."
let loop : (s s) (i 0)
if : = 0 : string-length s
. #f
if : string-prefix? fragment s
. i
loop (string-drop s 1) (+ i 1)
define : doctests-extract-from-string s
. "Extract all test calls from a given string."
let lp
: str s
tests : list
if : string-null? str
reverse tests
let : : idx : string-index str "(test"
if : not idx
reverse tests
let : : sub : substring str idx
lp ; recurse with the rest of the string
with-input-from-string sub
λ () (read) (read-string)
cons
with-input-from-string sub
λ () : read
. tests
define : subtract a b
. "Subtract B from A."
##
tests : test-eqv 3 (subtract 5 2)
- a b
define : doctests-testmod mod
. "Execute all doctests in the current module
This procedure provides an example test:"
##
tests
'mytest
define v (make-vector 5 99)
test-assert (vector? v)
test-eqv 99 (vector-ref v 2)
vector-set! v 2 7
test-eqv 7 (vector-ref v 2)
'mytest2
test-assert #t
;; thanks to Vítor De Araújo: https://lists.gnu.org/archive/html/guile-user/2017-08/msg00003.html
let*
: names : module-map (λ (sym var) sym) mod
filename
if (module-filename mod) (string-join (string-split (module-filename mod) #\/ ) "-")
string-join (cons "._" (map symbol->string (module-name mod))) "-"
doctests
map (λ (x) (if (procedure? x) (procedure-property x 'tests)))
map (λ (x) (module-ref mod x)) names
let loop
: names names
doctests doctests
;; pretty-print doctests
;; newline
when : pair? doctests
let*
: name : car names
doctest : car doctests
let loop-tests
: doctest doctest
when : and (pair? doctest) (car doctest) : pair? : car doctest
;; pretty-print : car doctest
;; newline
let*
:
testid
match doctest
: (('quote id) tests ...) moretests ...
string-join : list filename (symbol->string name) : symbol->string id
. "--"
: tests ...
string-join : list filename (symbol->string name)
. "--"
body
match doctest
: (('quote id) test tests ...) moretests ...
cons test tests
: tests ...
. tests
cleaned
cons 'begin
cons '(import (srfi srfi-64))
cons
list 'test-begin : or testid ""
append
. body
list : list 'test-end : or testid ""
;; pretty-print testid
;; pretty-print body
;; pretty-print cleaned
;; newline
when cleaned
let :
eval cleaned mod
newline
match doctest
: (('quote id) tests ...) moretests ...
loop-tests moretests
: tests ...
. #t
loop (cdr names) (cdr doctests)
define %this-module : current-module
define : main args
doctests-testmod %this-module