doctests.w 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/env bash
  2. # -*- wisp -*-
  3. guile -L $(dirname $(dirname $(realpath "$0"))) -c '(import (language wisp spec))'
  4. exec -a "$0" guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -x .w -e '(doctests)' -c '' "$@"
  5. ; !#
  6. ;;; doctests --- simple testing by adding procedure-properties with tests.
  7. ;;; Usage
  8. ;; Add a tests property to a procedure to have simple unit tests.
  9. ;; Simple tests:
  10. ;;
  11. ;; (define (A)
  12. ;; #((tests (test-eqv 'A (A))
  13. ;; (test-assert #t)))
  14. ;; 'A)
  15. ;;
  16. ;; Named tests:
  17. ;;
  18. ;; (define (A)
  19. ;; #((tests ('test1 (test-eqv 'A (A))
  20. ;; (test-assert #t))
  21. ;; ('test2 (test-assert #t))))
  22. ;; 'A)
  23. ;;
  24. ;; Allows for docstrings:
  25. ;;
  26. ;; (define (A)
  27. ;; "returns 'A"
  28. ;; #((tests (test-eqv 'A (A))
  29. ;; (test-assert #t)))
  30. ;; 'A)
  31. ;; For writing the test before the implementation, start with the test and #f:
  32. ;; (define (A)
  33. ;; #((tests (test-eqv 'A (A))))
  34. ;; #f)
  35. ;; With wisp, you currently need to use the literal #((tests (...)))
  36. ;; TODO: add array parsing to wisp following quoting with ':
  37. ;; # a b → #(a b) and # : a b c → #((a b))
  38. define-module : doctests
  39. . #:export : doctests-testmod main
  40. import : ice-9 optargs
  41. ice-9 rdelim
  42. ice-9 match
  43. ice-9 pretty-print
  44. oop goops
  45. texinfo reflection
  46. ; define basic dir
  47. define* (dir #:key (all? #f))
  48. if all?
  49. map (λ (x) (cons (module-name x)
  50. (module-map (λ (sym var) sym) (resolve-interface (module-name x)))))
  51. cons (current-module) : module-uses (current-module)
  52. module-map (λ (sym var) sym) (current-module)
  53. ; add support for giving the module as argument
  54. define-generic dir
  55. define-method (dir (all? <boolean>)) (dir #:all? all?)
  56. define-method (dir (m <list>)) (module-map (λ (sym var) sym) (resolve-interface m))
  57. ; add support for using modules directly (interfaces are also modules, so this catches both)
  58. define-method (dir (m <module>)) (module-map (λ (sym var) sym) m)
  59. define : string-index s fragment
  60. . "return the index of the first character of the FRAGMENT in string S."
  61. let loop : (s s) (i 0)
  62. if : = 0 : string-length s
  63. . #f
  64. if : string-prefix? fragment s
  65. . i
  66. loop (string-drop s 1) (+ i 1)
  67. define : doctests-extract-from-string s
  68. . "Extract all test calls from a given string."
  69. let lp
  70. : str s
  71. tests : list
  72. if : string-null? str
  73. reverse tests
  74. let : : idx : string-index str "(test"
  75. if : not idx
  76. reverse tests
  77. let : : sub : substring str idx
  78. lp ; recurse with the rest of the string
  79. with-input-from-string sub
  80. λ () (read) (read-string)
  81. cons
  82. with-input-from-string sub
  83. λ () : read
  84. . tests
  85. define : subtract a b
  86. . "Subtract B from A."
  87. ##
  88. tests : test-eqv 3 (subtract 5 2)
  89. - a b
  90. define : doctests-testmod mod
  91. . "Execute all doctests in the current module
  92. This procedure provides an example test:"
  93. ##
  94. tests
  95. 'mytest
  96. define v (make-vector 5 99)
  97. test-assert (vector? v)
  98. test-eqv 99 (vector-ref v 2)
  99. vector-set! v 2 7
  100. test-eqv 7 (vector-ref v 2)
  101. 'mytest2
  102. test-assert #t
  103. ;; thanks to Vítor De Araújo: https://lists.gnu.org/archive/html/guile-user/2017-08/msg00003.html
  104. let*
  105. : names : module-map (λ (sym var) sym) mod
  106. filename
  107. if (module-filename mod) (string-join (string-split (module-filename mod) #\/ ) "-")
  108. string-join (cons "._" (map symbol->string (module-name mod))) "-"
  109. doctests
  110. map (λ (x) (if (procedure? x) (procedure-property x 'tests)))
  111. map (λ (x) (module-ref mod x)) names
  112. let loop
  113. : names names
  114. doctests doctests
  115. ;; pretty-print doctests
  116. ;; newline
  117. when : pair? doctests
  118. let*
  119. : name : car names
  120. doctest : car doctests
  121. let loop-tests
  122. : doctest doctest
  123. when : and (pair? doctest) (car doctest) : pair? : car doctest
  124. ;; pretty-print : car doctest
  125. ;; newline
  126. let*
  127. :
  128. testid
  129. match doctest
  130. : (('quote id) tests ...) moretests ...
  131. string-join
  132. list filename
  133. string-join (string-split (symbol->string name) #\/) "--" ;; escape / in paths
  134. symbol->string id
  135. . "--"
  136. : tests ...
  137. string-join : list filename : string-join (string-split (symbol->string name) #\/) "--" ;; escape / in paths
  138. . "--"
  139. body
  140. match doctest
  141. : (('quote id) test tests ...) moretests ...
  142. cons test tests
  143. : tests ...
  144. . tests
  145. cleaned
  146. cons 'begin
  147. cons '(import (srfi srfi-64))
  148. cons
  149. list 'test-begin : or testid ""
  150. append
  151. . body
  152. list : list 'test-end : or testid ""
  153. ;; pretty-print testid
  154. ;; pretty-print body
  155. ;; pretty-print cleaned
  156. ;; newline
  157. when cleaned
  158. let :
  159. eval cleaned mod
  160. newline
  161. match doctest
  162. : (('quote id) tests ...) moretests ...
  163. loop-tests moretests
  164. : tests ...
  165. . #t
  166. loop (cdr names) (cdr doctests)
  167. define : hello who
  168. . "Say hello to WHO"
  169. ##
  170. tests
  171. test-equal "Hello World!\n"
  172. hello "World"
  173. format #f "Hello ~a!\n"
  174. . who
  175. define %this-module : current-module
  176. define : main args
  177. doctests-testmod %this-module