#!/usr/bin/env sh exec guile -L $(dirname $(dirname $(realpath "$0"))) -e '(@@ (examples doctests) main)' -s "$0" "$@" ; !# (define-module (examples doctests) #:export (doctests-testmod)) (import (ice-9 optargs) (ice-9 rdelim) (ice-9 match) (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. This is an example test: (test '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))) (test 'mytest2 (test-assert #t)) " (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 (doctests-testmod mod) "Execute all doctests in the current module" (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))) "-"))) (docstrings (map (λ (x) (if (procedure? x) (procedure-documentation x))) (map (λ (x) (module-ref mod x)) names)))) (let loop ((names names) (docstrings docstrings)) (when (not (null? docstrings)) (when (string? (car docstrings)) (let* ((name (car names)) (docstring (car docstrings))) (let loop-tests ((doctests (doctests-extract-from-string (car docstrings)))) (when (and (list? doctests) (not (null? doctests))) (let* ((doctest (car doctests)) (testid (if (not (list? doctest)) #f (string-join (list filename (symbol->string name) (symbol->string (primitive-eval (car (cdr doctest))))) "--"))) (cleaned (if (not (list? doctest)) '#f (append (cons 'begin (cons '(import (srfi srfi-64)) (cons (list 'test-begin testid) (cdr (cdr doctest))))) (list (list 'test-end testid)))))) (when cleaned (let () (eval cleaned mod)) (newline)) (loop-tests (cdr doctests))))))) (loop (cdr names) (cdr docstrings)))))) (define %this-module (current-module)) (define (main args) (doctests-testmod %this-module))