Skip to content

Commit d61a0f8

Browse files
mattiuusitalolread
andauthored
Fix multiline expectations (#35)
* Allows using leading comments in expected output for editor style * Updated expected generated tests * Fix typo in docstring * Add unit test for removing leading comments * Update expected generated tests * Allow multiple leading comment characters * Add test cases * Add integration test case for leading semicolon support for adoc * Add test cases based on more involved mixing of eval and stdout * docs: examples: add section to explain multiline Create a dedicated section to explain multiline output for example.md and example.adoc. These both explain to the user and act as tests for test-doc-blocks. * Multiline eval expectations complete when parsable We now know that an eval expectation is complete when its form parses successfully. Added the lovely nubank matcher combinators dep to help with test assertions. Now that things are a bit more complicated, algorithm and naming in code. It might be a bit easier to follow now? Hope so. * review feedback: fix typo * doc: examples: update single semicolon advice We now would only ever need the single semicolon distinguisher for `=stdout=>` and `=stderr=>` blocks. * lint: add nubank/matcher-combinators config * doc: changelog Closes #33 --------- Co-authored-by: lread <[email protected]>
1 parent 6654b4a commit d61a0f8

23 files changed

+523
-142
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{:linters
2+
{:unresolved-symbol
3+
{:exclude [(cljs.test/is [match? thrown-match?])
4+
(clojure.test/is [match? thrown-match?])]}}}

CHANGELOG.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ A release with known breaking changes is marked with:
2121

2222
* Other changes
2323
** Test-doc-blocks now correctly specifies `true` for ClojureScript `include-macros`
24+
https://github.com/lread/test-doc-blocks/issues/29[#29]
2425
(thanks for raising https://github.com/NoahTheDuke[@NoahTheDuke]! thanks for fixing https://github.com/mattiuusitalo[@mattiuusitalo]!)
26+
** Editor-style multiline expectations can be fully commented
27+
https://github.com/lread/test-doc-blocks/issues/33[#33]
28+
(thanks https://github.com/mattiuusitalo[@mattiuusitalo]!)
2529
** Bumped deps (used during test generation only)
2630

2731
== v1.1.20 - 2024-05-06 [[v1.1.20]]

deps.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
;;
6363

6464
;; for kaocha see also tests.edn
65-
:kaocha {:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
65+
:kaocha {:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}
66+
nubank/matcher-combinators {:mvn/version "3.9.1"}}
6667
:extra-paths ["test"]
6768
:main-opts ["-m" "kaocha.runner"]}
6869

doc/example.adoc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ user=> (+ 1 2 3)
4343
(+ 1 2 3 4)
4444
;; => 10
4545
46+
;; the expected value can span multiple lines
47+
(assoc {} :foo :bar :quu :baz)
48+
;; => {:foo :bar
49+
;; :quu :baz}
50+
4651
;; it understands that Clojure and ClojureScript can evaluate differently
4752
\C
4853
;; =clj=> \C
@@ -98,6 +103,54 @@ It is sometimes just nice to know that your code snippet will run without except
98103
(apply str))
99104
----
100105

106+
== Multiline Expectations
107+
108+
It is normal for `=stdout=>` and `=stderr=>` output to span multiple lines.
109+
You can span other expected output over multiple lines for readability.
110+
111+
//#:test-doc-blocks {:platform :clj :test-ns example-adoc-out-test}
112+
[source,clj]
113+
----
114+
;; A snippit of Clojure where we check result, stderr and stdout
115+
(do
116+
(println "Hi ho\nHi ho\nTo out I will go")
117+
(binding [*out* *err*] (println "To err is human\nTo forgive\nIs devine"))
118+
(range 1 16))
119+
;; => ( 1 2 3 4 5
120+
;; 6 7 8 9 10
121+
;; 11 12 13 14 15)
122+
;; =stdout=> Hi ho
123+
;; Hi ho
124+
;; To out I will go
125+
;; =stderr=>
126+
;; To err is human
127+
;; To forgive
128+
;; Is devine
129+
----
130+
131+
If your expected stdout or stderr contains subsequent lines starting with test-doc-block `+=*>+` markers, you can distinguish by using a single semicolon prefix for subsequent lines:
132+
133+
//#:test-doc-blocks {:platform :clj :test-ns example-adoc-out-test}
134+
[source,clj]
135+
----
136+
;; A snippit of Clojure where we check result, stderr and stdout
137+
(do
138+
(println "Hi ho\nHi ho\nTo out I will go\n=stderr=> still part of stdout")
139+
(binding [*out* *err*] (println "To err is human\nTo forgive\nIs devine\n=> part of stderr"))
140+
[:bip :bop '=stdout> :bap :borp])
141+
;; => [:bip :bop
142+
;; =stdout> :bap
143+
;; :borp]
144+
;; =stdout=> Hi ho
145+
; Hi ho
146+
; To out I will go
147+
; =stderr=> still part of stdout
148+
;; =stderr=>
149+
; To err is human
150+
; To forgive
151+
; Is devine
152+
; => part of stderr
153+
----
101154

102155
== Inline Options
103156
You can, to some limited extent, communicate your intent to test-doc-blocks.

doc/example.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ Within your Clojure code blocks, test-doc-blocks automatically generates asserti
3737
(+ 1 2 3 4)
3838
;; => 10
3939

40+
;; the expected value can span multiple lines
41+
(assoc {} :foo :bar :quu :baz)
42+
;; => {:foo :bar
43+
;; :quu :baz}
44+
4045
;; it understands that Clojure and ClojureScript can evaluate differently
4146
\C
4247
;; =clj=> \C
@@ -52,7 +57,6 @@ Within your Clojure code blocks, test-doc-blocks automatically generates asserti
5257
; is this right?
5358
; or not?
5459
```
55-
5660
Sometimes we might care about evaluated result, stderr and stdout.
5761
<!-- #:test-doc-blocks {:platform :clj :test-ns example-md-out-test} -->
5862
```clj
@@ -65,7 +69,6 @@ Within your Clojure code blocks, test-doc-blocks automatically generates asserti
6569
;; =stderr=> To err is human
6670
;; =stdout=> To out I go
6771
```
68-
6972
<!-- #:test-doc-blocks {:platform :cljs :test-ns example-md-out-test} -->
7073
```cljs
7174
;; And the same idea for ClojureScript
@@ -87,6 +90,51 @@ It is sometimes just nice to know that your code snippet will run without except
8790
reverse
8891
(apply str))
8992
```
93+
## Multiline Expectations
94+
95+
It is normal for `=stdout=>` and `=stderr=>` output to span multiple lines.
96+
You can span other expected output over multiple lines for readability.
97+
98+
<!-- #:test-doc-blocks {:platform :clj :test-ns example-md-out-test} -->
99+
```clj
100+
;; A snippit of Clojure where we check result, stderr and stdout
101+
(do
102+
(println "Hi ho\nHi ho\nTo out I will go")
103+
(binding [*out* *err*] (println "To err is human\nTo forgive\nIs devine"))
104+
(range 1 16))
105+
;; => ( 1 2 3 4 5
106+
;; 6 7 8 9 10
107+
;; 11 12 13 14 15)
108+
;; =stdout=> Hi ho
109+
;; Hi ho
110+
;; To out I will go
111+
;; =stderr=>
112+
;; To err is human
113+
;; To forgive
114+
;; Is devine
115+
```
116+
If your expected stdout or stderr contains subsequent lines starting with test-doc-block `+=*>+` markers, you can distinguish by using a single semicolon prefix for subsequent lines:
117+
118+
<!-- #:test-doc-blocks {:platform :clj :test-ns example-md-out-test} -->
119+
```clj
120+
;; A snippit of Clojure where we check result, stderr and stdout
121+
(do
122+
(println "Hi ho\nHi ho\nTo out I will go\n=stderr=> still part of stdout")
123+
(binding [*out* *err*] (println "To err is human\nTo forgive\nIs devine\n=> part of stderr"))
124+
[:bip :bop '=stdout> :bap :borp])
125+
;; => [:bip :bop
126+
;; =stdout> :bap
127+
;; :borp]
128+
;; =stdout=> Hi ho
129+
; Hi ho
130+
; To out I will go
131+
; =stderr=> still part of stdout
132+
;; =stderr=>
133+
; To err is human
134+
; To forgive
135+
; Is devine
136+
; => part of stderr
137+
```
90138

91139
## Inline Options
92140
You can, to some limited extent, communicate your intent to test-doc-blocks.
Lines changed: 118 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
(ns ^:no-doc lread.test-doc-blocks.impl.body-prep
22
"Prep a doc block to be converted into a test."
3-
(:require [clojure.string :as string]))
3+
(:require [clojure.string :as string]
4+
[rewrite-clj.zip :as z]))
5+
6+
(defn- parseable? [s]
7+
(try
8+
(let [zloc (z/of-string s)]
9+
(not= :forms (z/tag zloc)))
10+
(catch Exception _ex
11+
false)))
412

513
(defn ^:no-doc prep-block-for-conversion-to-test
614
"Convert doc block to Clojure that can be more easily parsed.
@@ -29,62 +37,128 @@
2937
"
3038
[block-text]
3139
(let [re-editor-style-out-expected #"^\s*(?:;;\s*){0,1}(=stdout=>|=stderr=>)(?:\s*$| {0,1}(.*))"
32-
re-out-continue #"^\s*;(?:\s*$| {0,1}(.*))"
33-
re-editor-style-expected #"^\s*(?:;;\s*){0,1}(=clj=>|=cljs=>|=>)\s*(.*$)"]
34-
(-> (loop [acc {:body ""}
40+
re-line-continue #"^\s*;+(?:\s*$| {0,1}(.*))"
41+
re-editor-style-expected #"^\s*(?:;;\s*){0,1}(=clj=>|=cljs=>|=>)\s*(.*$)"]
42+
(-> (loop [{:keys [body parsing assert-token expectation eo-assert?] :as state} {:body ""}
3543
[line :as lines] (string/split-lines block-text)]
3644
(let [line (and line (string/trimr line))
37-
[_ assert-token payload] (when line
38-
(or (re-matches re-editor-style-expected line)
39-
(re-matches re-editor-style-out-expected line)))]
45+
[_ line-assert-token line-payload] (when line
46+
(or (re-matches re-editor-style-expected line)
47+
(re-matches re-editor-style-out-expected line)))]
4048
(cond
41-
;; out expectation ends
42-
(and (:out acc) (or (not line)
43-
assert-token
44-
(not (re-matches re-out-continue line))
45-
(re-matches re-editor-style-expected line)))
46-
(recur (-> acc
47-
(update :body str (str (:out-token acc) " "
48-
(conj (:out acc)) "\n"))
49-
(dissoc :out :out-token))
50-
lines)
49+
(= :eval-expectation parsing)
50+
(cond
51+
;; end of eval expectation?
52+
(or (not line) eo-assert?)
53+
(recur {:body (str body
54+
assert-token " "
55+
(string/join "\n" expectation)
56+
"\n")}
57+
lines)
58+
59+
:else
60+
(let [[_ line-continue] (re-matches re-line-continue line)
61+
eval-line (or line-continue line)]
62+
(recur (let [{:keys [expectation] :as state} (update state :expectation conj (or eval-line ""))]
63+
(if (parseable? (string/join "\n" expectation))
64+
(assoc state :eo-assert? true)
65+
state))
66+
(rest lines))))
67+
68+
(= :out-expectation parsing)
69+
(cond
70+
;; end of expecation?
71+
(or (not line)
72+
line-assert-token
73+
eo-assert?
74+
(not (re-matches re-line-continue line)))
75+
(recur {:body (str body
76+
assert-token " "
77+
(conj expectation)
78+
"\n")}
79+
lines)
80+
81+
(re-matches re-line-continue line)
82+
(let [[_ line-continue] (re-matches re-line-continue line)]
83+
(recur (update state :expectation conj (or line-continue ""))
84+
(rest lines)))
85+
86+
:else
87+
(recur (assoc state :eo-assert? true) lines))
5188

5289
;; all done?
5390
(not line)
54-
acc
55-
56-
;; collecting stdout/stderr expectation
57-
(and (:out acc)
58-
(not assert-token)
59-
(re-matches re-out-continue line))
60-
(let [[_ out-line] (re-matches re-out-continue line)]
61-
(recur (update acc :out conj (or out-line ""))
62-
(rest lines)))
63-
64-
;; out expectation starts
65-
(or (= "=stdout=>" assert-token)
66-
(= "=stderr=>" assert-token))
67-
(recur (assoc acc
68-
:out (if payload [payload] [])
69-
:out-token assert-token)
91+
state
92+
93+
;; start of out expection
94+
(or (= "=stdout=>" line-assert-token)
95+
(= "=stderr=>" line-assert-token))
96+
(recur (assoc state
97+
:parsing :out-expectation
98+
:expectation (if line-payload [line-payload] [])
99+
:assert-token line-assert-token)
70100
(rest lines))
71101

72-
;; editor style evaluation expectation:
73-
;; actual
74-
;; ;;=> expected
75-
assert-token
76-
(recur (update acc :body str (str assert-token " " payload "\n"))
102+
;; editor style expectation
103+
line-assert-token
104+
(recur (let [{:keys [expectation] :as state} (assoc state
105+
:parsing :eval-expectation
106+
:expectation (if line-payload [line-payload] [])
107+
:assert-token line-assert-token)]
108+
(if (parseable? (string/join "\n" expectation))
109+
(assoc state :eo-assert? true)
110+
state))
77111
(rest lines))
78112

79-
;; other lines
80113
:else
81-
(recur (update acc :body str (str line "\n"))
114+
(recur (update state :body str (str line "\n"))
82115
(rest lines)))))
83116
:body)))
84117

85118

86119
(comment
87-
(prep-block-for-conversion-to-test ";; =stdout=> line1")
88-
89-
90-
)
120+
(parseable? " , ")
121+
122+
(prep-block-for-conversion-to-test ";; =stdout=> line1
123+
; line2")
124+
;; => "=stdout=> [\"line1\" \"line2\"]\n"
125+
126+
(prep-block-for-conversion-to-test
127+
"(assoc {} :foo :bar :baz :kikka)
128+
;; => {:foo :bar
129+
; :baz :kikka}"
130+
)
131+
;; => "(assoc {} :foo :bar :baz :kikka)\n=> {:foo :bar\n :baz :kikka}\n"
132+
133+
(prep-block-for-conversion-to-test
134+
"(assoc {} :foo :bar :baz :kikka)
135+
;; => ^{:foo :bar
136+
;; :baz :kikka} []
137+
;; some other comment
138+
;; more comments
139+
;; =stdout=> foo
140+
"
141+
)
142+
;; => "(assoc {} :foo :bar :baz :kikka)\n=> ^{:foo :bar\n :baz :kikka} []\n;; some other comment\n;; more comments\n=stdout=> [\"foo\"]\n"
143+
144+
145+
(parseable? " ")
146+
;; => false
147+
(parseable? " 32 ")
148+
;; => true
149+
150+
;; only :a will be significant
151+
(parseable? ":a 2 3")
152+
;; => true
153+
154+
(parseable? "[:a 2\n;;some foo\n 3]")
155+
;; => true
156+
157+
(parseable? ";; foo")
158+
;; => false
159+
160+
;; for now don't skip discards, user should get error for this eventualy
161+
(parseable? "#_ 3")
162+
;; => true
163+
164+
:eoc)

test-resources/expected/test-doc-blocks/test/example_adoc_cljs_test.cljs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
(:import goog.events.EventType))
77

88
(clojure.test/deftest block-0001
9-
(clojure.test/testing "doc/example.adoc - line 252 - Specifying The Platform - :platform"
9+
(clojure.test/testing "doc/example.adoc - line 305 - Specifying The Platform - :platform"
1010
;; this code block will generate a test under example-adoc-cljs-test ns to a .cljs file
1111

1212
nil

test-resources/expected/test-doc-blocks/test/example_adoc_inline_ns_test.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
(:import #?@(:clj [java.util.List java.util.Queue java.util.Set] :cljs [goog.math.Long goog.math.Vec2 goog.math.Vec3])))
1111

1212
(clojure.test/deftest block-0001
13-
(clojure.test/testing "doc/example.adoc - line 472 - Inline requires and imports"
13+
(clojure.test/testing "doc/example.adoc - line 525 - Inline requires and imports"
1414
;; Stick the basics for requires, shorthand notation isn't supported
1515

1616
;; Some examples:

test-resources/expected/test-doc-blocks/test/example_adoc_inline_refer_clojure_test.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[clojure.edn :refer [read-string]]))
88

99
(clojure.test/deftest block-0001
10-
(clojure.test/testing "doc/example.adoc - line 503 - Inline refer-clojure"
10+
(clojure.test/testing "doc/example.adoc - line 556 - Inline refer-clojure"
1111
;; a contrived example that uses uses clojure.edn/read-string in place
1212
;; of clojure.core/read-string and excludes clojure.core/for
1313
nil

test-resources/expected/test-doc-blocks/test/example_adoc_new_ns/ns1_test.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[clojure.string :as string]))
77

88
(clojure.test/deftest block-0001
9-
(clojure.test/testing "doc/example.adoc - line 220 - Specifying Test Namespace - :test-ns"
9+
(clojure.test/testing "doc/example.adoc - line 273 - Specifying Test Namespace - :test-ns"
1010
;; this code block will generate tests under example-adoc-new-ns.ns1-test
1111

1212
nil

0 commit comments

Comments
 (0)