/
matcher_combinators.clj
94 lines (80 loc) · 4.05 KB
/
matcher_combinators.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
(ns state-flow.assertions.matcher-combinators
(:require [cats.core :as m]
[cats.monad.exception :as e]
[matcher-combinators.standalone :as matcher-combinators]
[state-flow.assertions.report :as report]
[state-flow.core :as core]
[state-flow.probe :as probe]
[state-flow.state :as state]))
(defn ^:private match-probe
"Internal use only.
Returns the result of calling probe/probe with a function that
uses matcher-combinators to match the actual value.
args:
- step is a monad which should produce the actual value
- expected is any valid first argument to `matcher-combinators/match?`
- params are passed directly to probe "
[step expected params]
(probe/probe step
(fn [actual] (matcher-combinators/match? expected actual))
params))
(defmacro match?
"Builds a state-flow step which uses matcher-combinators to make an
assertion.
`expected` can be a literal value or a matcher-combinators matcher
`actual` can be a literal value, a primitive step, or a flow
`params` are optional keyword-style args, supporting:
:times-to-try optional, default 1
:sleep-time optional, millis to wait between tries, default 200
Given (= times-to-try 1), match? will evaluate `actual` just once.
Given (> times-to-try 1), match? will use `state-flow-probe/probe` to
retry up to :times-to-try times, waiting :sleep-time between each try,
and stopping when `actual` produces a value that matches `expected`.
NOTE: when (> times-to-try 1), `actual` must be a step or a flow.
Returns a map (in the left value) with information about the success
or failure of the match, the details of which are used internally by
state-flow and subject to change."
[expected actual & [{:keys [times-to-try
sleep-time]
:as params}]]
;; description is here to support the
;; deprecated cljtest/match? fn. Undecided
;; whether we want to make it part of the API.
;; caller-meta is definitely not part of the API.
(let [caller-meta (assoc (meta &form) :file *file* :ns (str *ns*))
params* (merge {:description "match?"
:caller-meta caller-meta
:times-to-try 1
:sleep-time probe/default-sleep-time}
params)]
(core/flow*
{:description (:description params*)
:caller-meta (:caller-meta params*)}
;; Nesting m/do-let inside a call the function core/flow* is
;; a bit ugly, but it supports getting the correct line number
;; information from core/current-description.
`(m/do-let
(state/invoke #(when (and (not (:called-from-deprecated-match? ~params*))
((fnil > 1) ~times-to-try 1)
(not (state/state? ~actual)))
(throw (ex-info "actual must be a step or a flow when :times-to-try > 1"
{:times-to-try ~times-to-try
:actual ~actual}))))
[flow-desc# (core/current-description)
fail-fast?# core/fail-fast?
probe-res# (#'match-probe (state/ensure-step ~actual) ~expected ~params*)
:let [actual# (-> probe-res# last :value)
report# (assoc (matcher-combinators/match ~expected actual#)
:match/expected ~expected
:match/actual actual#
:probe/results probe-res#
:probe/sleep-time ~(:sleep-time params*)
:probe/times-to-try ~(:times-to-try params*))]]
(report/push report#)
(state/return (if (and fail-fast?# (= :mismatch (:match/result report#)))
(e/failure report# "match? assertion was not satisfied")
report#))))))
(defn report->actual
"Returns the actual value from the report returned by `match?`."
[report]
(:match/actual report))