Skip to content

Commit

Permalink
** Tabular facts once again print their substitutions on failure
Browse files Browse the repository at this point in the history
  • Loading branch information
marick committed Mar 20, 2013
1 parent 6df4535 commit 80d0c8f
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 75 deletions.
23 changes: 14 additions & 9 deletions src/midje/parsing/0_to_fact_form/tabular.clj
@@ -1,16 +1,18 @@
(ns ^{:doc "A way to create multiple facts with the same template, but different data points."}
midje.parsing.0-to-fact-form.tabular
(:use [midje.parsing.util.zip]
(:use midje.clojure.core
midje.parsing.util.zip
[midje.parsing.util.file-position :only [form-with-copied-line-numbers]]
[midje.emission.deprecation :only [deprecate]]
[midje.parsing.util.zip :only [skip-to-rightmost-leaf]]
[midje.parsing.1-to-explicit-form.facts :only [working-on-nested-facts unparse-edited-fact]]
[midje.data.metaconstant :only [metaconstant-symbol?]]
[utilize.map :only [ordered-zipmap]])
(:require [clojure.string :as str]
[clojure.zip :as zip]
[midje.util.unify :as unify]
[midje.parsing.util.overrides :as override]
[midje.parsing.util.recognizing :as recognize]
[midje.parsing.lexical-maps :as maps]
[midje.parsing.1-to-explicit-form.metadata :as metadata]
[midje.parsing.util.error-handling :as error]))

Expand Down Expand Up @@ -48,7 +50,7 @@
;; (creation-time-check
;; (letfn [...] <letfn-body>
;;
;; It is the <letfn-body> that must be searched for expect forms,
;; It is the <letfn-body> that must be searched for the construction of checkable maps,
;; which then have annotations added to them.
(letfn [(headed-by? [form string]
(and (sequential? form)
Expand All @@ -68,14 +70,17 @@
(second possible-letfn))]
(-> definite-letfn second first rest second)))

(translate-letfn-body [expect-containing-form]
(translate-zipper expect-containing-form
recognize/expect? one-binding-note))
(translate-letfn-body [checkable-containing-form]
;; TODO: Nested facts lead to nested letfns, and that stops processing,
;; so nested facts don't get binding annotations.
(translate-zipper checkable-containing-form
(comp maps/checkable-map? zip/node)
one-binding-note))

(one-binding-note [loc]
(skip-to-rightmost-leaf
(override/above-arrow-sequence__add-key-value__at-arrow
:binding-note (format-binding-map ordered-binding-map) loc)))]
(zip/replace loc
(assoc (clojure.zip/node loc)
:binding-note (format-binding-map ordered-binding-map))))]

(if (acceptable-body?)
(let [letfn-body (target-body)]
Expand Down
8 changes: 6 additions & 2 deletions src/midje/parsing/lexical_maps.clj
Expand Up @@ -33,8 +33,11 @@
(midje.parsing.util.file-position/line-number-known 2)))
) ; ---------------------------------------------------------------

;;; Example maps
;;; Checkable maps

(defn checkable-map? [value]
(and (map? value)
(::a-midje-checkable-map? value)))

(defn example
[call-form arrow expected-result overrides]
Expand All @@ -43,7 +46,8 @@
override-map `(hash-map-duplicates-ok ~@overrides)
line (:line (meta call-form))
result `(merge
{:function-under-test (fn [] ~call-form)
{::a-midje-checkable-map? true
:function-under-test (fn [] ~call-form)
:expected-result ~expected-result
:check-expectation ~(recognize/expect-match-or-mismatch arrow)
:expected-result-form '~expected-result ;; This is also part of the source details.
Expand Down
94 changes: 94 additions & 0 deletions test/as_documentation/tabular_facts.clj
@@ -0,0 +1,94 @@
(ns as-documentation.tabular-facts
(:use midje.repl
midje.test-util))

;;; When you have many similar tests, you can use *tabular facts* to
;;; clarify what's special about each test. Here, for example, is a tabular
;;; fact about addition:

(tabular "tabular facts can take a doc string"
(fact (+ ?a ?b) => ?result )
?a ?b ?result
1 2 3
1 0 1)

;;; You can use the repl tools to work with tabular facts just as you
;;; can any other top-level fact:

(fact
(map fact-description (fetch-facts "tabular facts can take a doc string"))
=> ["tabular facts can take a doc string"])

;;; On failure, the line number points to the fact, but the message
;;; is annotated with information about the substitutions.

(capturing-failure-output
(tabular
(fact (+ ?a ?b) => ?result )
?a ?b ?result
1 2 3333
1 0 11)
(fact
@fact-output => (contains "With table substitutions: [?a 1")
@fact-output => (contains " ?b 2")
@fact-output => (contains " ?result 3333]")
@fact-output => (contains "Expected: 3333")
@fact-output => (contains " Actual: 3")
@fact-output => (contains "With table substitutions: [?a 1")
@fact-output => (contains " ?b 0")
@fact-output => (contains " ?result 11]")
@fact-output => (contains "Expected: 11")
@fact-output => (contains " Actual: 1")))


;;; More about doc Strings and Metadata

;;; If you prefer, you can put the doc string on the enclosed fact rather than the
;;; tabular fact:

(tabular
(fact "Put the doc string wherever you prefer"
(+ ?a ?b) => ?result )
?a ?b ?result
1 2 3
1 0 1)

;;; You can add other metadata to tabular facts, and select ones to check with it:

(tabular
(fact :a-tabular-fact
(+ ?a ?b) => ?result )
?a ?b ?result
1 2 3
1 0 1)

(fact
(let [marked-facts (fetch-facts :a-tabular-fact)]
(count marked-facts) => 1
(:a-tabular-fact (meta (first marked-facts))) => true))


;;; Miscellany

(capturing-failure-output
(fact "you can nest tabular facts within other facts"
(+ 1 1) => 2
(tabular
(fact (+ ?a ?b) => 1)
?a ?b
1 0
0 1
1 1))
(fact
@fact-output => (contains "With table substitutions: [?a 1")
@fact-output => (contains " ?b 1]")
@fact-output => (contains "Expected: 1")
@fact-output => (contains " Actual: 2")))

;; It's natural to think of substituting values and expressions,
;; but you can substitute anything, such as Midje arrows.
(tabular
(fact (+ ?a ?b) ?arrow ?expected)
?a ?b ?arrow ?expected
1 1 => 2
1 2 =not=> 3000)
99 changes: 35 additions & 64 deletions test/midje/parsing/0_to_fact_form/t_tabular.clj
Expand Up @@ -5,27 +5,15 @@
[ordered.map :only (ordered-map)]
midje.util)
(:require [midje.util.pile :as pile]
[midje.parsing.1-to-explicit-form.facts :as facts]
[midje.parsing.lexical-maps :as maps]
[midje.parsing.1-to-explicit-form.facts :as parse-facts]
[midje.data.fact :as fact-data]
[midje.data.compendium :as compendium]
[midje.config :as config]))

(expose-testables midje.parsing.0-to-fact-form.tabular)


;; Core midje.sweet API

(tabular
(fact (+ ?a ?b) => ?result )
?a ?b ?result
1 2 3)

(tabular
(fact "some information about that"
(+ ?a ?b) => ?result)
?a ?b ?result
1 2 3)

(tabular "no longer need to prefix table variables with '?'"
(fact (+ a b) => result )

Expand Down Expand Up @@ -197,56 +185,39 @@
(table-binding-maps ['?a '?b '?result] [1 2 3])
=> [ (ordered-map '?a 1, '?b 2, '?result 3) ])

(defn as-received-by-add-binding-note [body]
(facts/wrap-with-creation-time-code
(facts/wrap-with-check-time-code
body
{:some-fact-metadata true}
'symbol-to-name-function-with)))


(tabular (fact ?comment
(let [line-no-free-original ?original
line-no-free-expected ?expected]
(add-binding-note line-no-free-original (ordered-map '?a 'a))
=> line-no-free-expected))

?comment ?original ?expected

"binding notes can be inserted"
(as-received-by-add-binding-note
'(do (midje.semi-sweet/expect (a) => b)
(do (midje.semi-sweet/expect (inc a) => M))))

(as-received-by-add-binding-note
'(do (midje.semi-sweet/expect (a) => b :binding-note "[?a a]")
(do (midje.semi-sweet/expect (inc a) => M :binding-note "[?a a]"))))

"fakes do not get insertions"
(as-received-by-add-binding-note
'(do (midje.semi-sweet/expect (a) => b
(midje.semi-sweet/fake (x) => 3))))

(as-received-by-add-binding-note
'(do (midje.semi-sweet/expect (a) => b :binding-note "[?a a]"
(midje.semi-sweet/fake (x) => 3))))

"other annotations are preserved"
(as-received-by-add-binding-note
'(do (midje.semi-sweet/expect (a) => b :line 33)))

(as-received-by-add-binding-note
'(do (midje.semi-sweet/expect (a) => b :binding-note "[?a a]" :line 33))))

(fact "binding notes are in the order of the original row - this order is maintained within the ordered-binding-map"
(let [actual (add-binding-note
(as-received-by-add-binding-note
'(do (expect 1 => 2)))
(ordered-map '?a 1, '?b 2, '?delta "0", '?result 3))

expected (as-received-by-add-binding-note
'(do (expect 1 => 2 :binding-note "[?a 1\n ?b 2\n ?delta \"0\"\n ?result 3]")))]
actual => expected))

(defn filter-checkable-maps [form]
(filter maps/checkable-map? (flatten form)))

(defn expand-and-add-binding-note
([form binding-map]
(add-binding-note (parse-facts/midjcoexpand form) binding-map))
([form]
(expand-and-add-binding-note form (ordered-map '?a 'a))))

(def binding-notes-from (comp filter-checkable-maps expand-and-add-binding-note))

(fact "binding notes"
(fact "can be inserted"
(binding-notes-from '(fact 1 => 1)) => (just (contains {:binding-note "[?a a]"})))

(fact "are inserted into every checkable"
(binding-notes-from '(fact 1 => 1
2 => 2)) => (just (contains {:binding-note "[?a a]"})
(contains {:binding-note "[?a a]"})))

(future-fact "are added to nested facts"
(binding-notes-from '(fact 1 => 1 (fact 2 => 2))) => (just (contains {:binding-note "[?a a]"})
(contains {:binding-note "[?a a]"})))

(fact "are not added to prerequisites"
(binding-notes-from '(fact (f 1) => 1 (provided (g 1) => 2))) => (just (contains {:binding-note "[?a a]"})))

(fact "are added in the left-to-right order of the original table"
(let [result (binding-notes-from '(fact (+ 1 2) => 3) (ordered-map :c 1, :b 2, :a 3))]
(count result) => 1
(:binding-note (first result)) => #"(?s):c 1.*:b 2.*:a 3")))


;; tabular doc-string prints in report

Expand Down

0 comments on commit 80d0c8f

Please sign in to comment.