94 lines
2.3 KiB
Clojure
94 lines
2.3 KiB
Clojure
|
|
(ns doric.test.doctest
|
||
|
|
(:use [clojure.java.io :only [file]]
|
||
|
|
[clojure.test])
|
||
|
|
(:import (java.io PushbackReader StringReader)))
|
||
|
|
|
||
|
|
(defn fenced-blocks
|
||
|
|
"detect and extract github-style fenced blocks in a file"
|
||
|
|
[s]
|
||
|
|
(map second
|
||
|
|
(re-seq #"(?m)(?s)^```clojure\n(.*?)\n^```" s)))
|
||
|
|
|
||
|
|
(def prompt
|
||
|
|
;; regex for finding 'foo.bar>' repl prompts
|
||
|
|
"(?m)\n*^\\S*>\\s*")
|
||
|
|
|
||
|
|
(defn skip?
|
||
|
|
"is a result skippable?"
|
||
|
|
;; if it's a comment, the answer is yes
|
||
|
|
[s]
|
||
|
|
(.startsWith s ";"))
|
||
|
|
|
||
|
|
(defn reps
|
||
|
|
"given a string of read-eval-print sequences, separate the different
|
||
|
|
'r-e-p's from each other"
|
||
|
|
[prompt s]
|
||
|
|
(rest (.split s prompt)))
|
||
|
|
|
||
|
|
(defn markdown-tests
|
||
|
|
"extract all the tests from a markdown file"
|
||
|
|
[f]
|
||
|
|
(->> f
|
||
|
|
slurp
|
||
|
|
fenced-blocks
|
||
|
|
(mapcat (partial reps prompt))))
|
||
|
|
|
||
|
|
(defn repl-tests
|
||
|
|
"extract all the tests from a repl-session-like file"
|
||
|
|
[f]
|
||
|
|
(->> f
|
||
|
|
slurp
|
||
|
|
(reps prompt)))
|
||
|
|
|
||
|
|
(defn temp-ns
|
||
|
|
"create a temporary ns, and return its name"
|
||
|
|
[]
|
||
|
|
(binding [*ns* *ns*]
|
||
|
|
(in-ns (gensym))
|
||
|
|
(use 'clojure.core)
|
||
|
|
;; BB-TEST-PATCH: bb can't .getName on ns
|
||
|
|
(str *ns*)))
|
||
|
|
|
||
|
|
(defn eval-in-ns
|
||
|
|
"evaluate a form inside the given ns-name"
|
||
|
|
[ns form]
|
||
|
|
(binding [*ns* *ns*]
|
||
|
|
(in-ns ns)
|
||
|
|
(eval form)))
|
||
|
|
|
||
|
|
(defn run-doctest
|
||
|
|
"run a single doctest, reporting success or failure"
|
||
|
|
[file idx ns test]
|
||
|
|
(let [r (PushbackReader. (StringReader. test))
|
||
|
|
form (read r)
|
||
|
|
expected (.trim (slurp r))
|
||
|
|
actual (when-not (skip? expected)
|
||
|
|
(.trim (try
|
||
|
|
(with-out-str
|
||
|
|
(pr (eval-in-ns ns form))
|
||
|
|
(flush))
|
||
|
|
(catch Exception _
|
||
|
|
(println _)
|
||
|
|
(.toString (gensym))))))]
|
||
|
|
(if (or (skip? expected)
|
||
|
|
(= actual expected))
|
||
|
|
(report {:type :pass})
|
||
|
|
(report {:type :fail
|
||
|
|
:file file :line idx
|
||
|
|
:expected expected :actual actual}))))
|
||
|
|
|
||
|
|
(defn run-doctests
|
||
|
|
"use text-extract-fn to get all the tests out of file, and run them
|
||
|
|
all, reporting success or failure"
|
||
|
|
[test-extract-fn file]
|
||
|
|
(let [ns (temp-ns)]
|
||
|
|
(doseq [[idx t] (map-indexed vector (test-extract-fn file))]
|
||
|
|
(run-doctest file idx ns t))
|
||
|
|
(remove-ns ns)))
|
||
|
|
|
||
|
|
|
||
|
|
(comment
|
||
|
|
;; example usage
|
||
|
|
(deftest bar-repl
|
||
|
|
(run-doctests repl-tests "test/bar.repl")))
|