@@ -94,8 +94,8 @@ open SlimCheck Decorations in
94
94
Checks a `Checkable` prop. Note that `mk_decorations` is here simply to improve error messages
95
95
and if `p` is Checkable, then so is `p'`.
96
96
-/
97
- def check (descr : String) (p : Prop )
98
- (next : TestSeq := .done) ( p' : Decorations. DecorationsOf p := by mk_decorations) [Checkable p']: TestSeq :=
97
+ def check (descr : String) (p : Prop ) (next : TestSeq := .done)
98
+ (p' : DecorationsOf p := by mk_decorations) [Checkable p'] : TestSeq :=
99
99
test descr p' next
100
100
101
101
inductive ExpectationFailure (exp got : String) : Prop
@@ -131,10 +131,6 @@ def withExceptError (descr : String) (exc : Except ε α) [ToString α]
131
131
| .error e => test descr true $ f e
132
132
| .ok a => test descr (ExpectationFailure "error _" s! "ok { a} " )
133
133
134
- end TestSequences
135
-
136
- section PureTesting
137
-
138
134
/-- A generic runner for `TestSeq` -/
139
135
def TestSeq.run (tSeq : TestSeq) (indent := 0 ) : Bool × String :=
140
136
let pad := String.mk $ List.replicate indent ' '
@@ -144,18 +140,17 @@ def TestSeq.run (tSeq : TestSeq) (indent := 0) : Bool × String :=
144
140
let (pass, msg) := ts.run (indent + 2 )
145
141
let (b, m) := aux s! "{ acc}{ pad}{ d} :\n { msg} " n
146
142
(pass && b, m)
147
- | .individual d _ (.isTrue _) n =>
148
- let (b, m) := aux s! "{ acc}{ pad} ✓ { d} \n " n
149
- (true && b, m)
143
+ | .individual d _ (.isTrue _) n => aux s! "{ acc}{ pad} ✓ { d} \n " n
150
144
| .individual d _ (.isMaybe msg) n =>
151
- let (b, m) := aux s! "{ acc}{ pad} ? { d}{ formatErrorMsg msg} \n " n
152
- (true && b, m)
145
+ aux s! "{ acc}{ pad} ? { d}{ formatErrorMsg msg} \n " n
153
146
| .individual d _ (.isFalse _ msg) n
154
147
| .individual d _ (.isFailure msg) n =>
155
- let (b , m) := aux s! "{ acc}{ pad} × { d}{ formatErrorMsg msg} \n " n
156
- (false && b , m)
148
+ let (_b , m) := aux s! "{ acc}{ pad} × { d}{ formatErrorMsg msg} \n " n
149
+ (false , m)
157
150
aux "" tSeq
158
151
152
+ end TestSequences
153
+
159
154
/--
160
155
Runs a `TestSeq` with an output meant for the Lean Infoview.
161
156
This function is meant to be called from a custom command. It runs in
@@ -176,97 +171,67 @@ A custom command to run `LSpec` tests. Example:
176
171
macro "#lspec " term:term : command =>
177
172
`(#eval LSpec.runInTermElabMAsUnit $term)
178
173
179
- end PureTesting
180
-
181
- section MonadicTesting
182
-
183
- class TestMonadEmit (m) [Monad m] where
184
- emit : String → m Unit
185
- fail : String → m Unit
186
-
187
- /-- A monadic runner that emits test outputs as they're produced. -/
188
- def TestSeq.runM (tSeq : TestSeq) (indent := 0 ) [Monad m] [h : TestMonadEmit m] :
189
- m Bool :=
190
- let pad := String.mk $ List.replicate indent ' '
191
- match tSeq with
192
- | .done => return true
193
- | .group d ts n => do
194
- h.emit s! "{ d} :"
195
- let gb ← ts.runM (indent + 2 )
196
- return gb && (← n.runM indent)
197
- | .individual d _ (.isTrue _) n => do
198
- h.emit s! "{ pad} ✓ { d} "
199
- return true && (← n.runM indent)
200
- | .individual d _ (.isMaybe msg) n => do
201
- h.emit s! "{ pad} ? { d}{ formatErrorMsg msg} "
202
- return true && (← n.runM indent)
203
- | .individual d _ (.isFalse _ msg) n
204
- | .individual d _ (.isFailure msg) n => do
205
- let msg := s! "{ pad} × { d}{ formatErrorMsg msg} "
206
- h.emit msg; h.fail msg -- also emitting messages from failed tests
207
- return false && (← n.runM indent)
208
-
209
- class MonadTest (m) [Monad m] (α) where
210
- success : α
211
- failure : α
212
- nEq : success ≠ failure
213
-
214
- def succeed [Monad m] [h : MonadTest m α] : m α :=
215
- return h.success
216
-
217
- def fail [Monad m] [h : MonadTest m α] : m α :=
218
- return h.failure
219
-
220
- /-- Runs a `TestSeq` in a monad with `TestMonadEmit` and `MonadTest`. -/
221
- def lspecM [Monad m] [TestMonadEmit m] [MonadTest m α] (t : TestSeq) : m α := do
222
- if ← t.runM then succeed
223
- else fail
224
-
225
- /--
226
- Interspersedly creates a `TestSeq` from each element `β` of a list with a
227
- function `β → m TestSeq` and runs the test sequence.
228
- -/
229
- def lspecEachM [Monad m] [TestMonadEmit m] [MonadTest m α]
230
- (l : List β) (f : β → m TestSeq) : m α := do
231
- let success ← l.foldlM (init := true ) fun acc a => do
232
- pure $ acc && (← ( ← f a).runM)
233
- if success then succeed else fail
234
-
235
- section IOTesting
236
-
237
- instance : TestMonadEmit IO :=
238
- ⟨IO.println, IO.eprintln⟩
239
-
240
- instance : MonadTest IO UInt32 :=
241
- ⟨0 , 1 , by decide⟩
242
-
174
+ open Std (HashMap) in
243
175
/--
244
- Runs a `TestSeq` with an output meant for the terminal.
176
+ Consumes a map of string-keyed test suites and returns a test function meant to
177
+ be used via CLI.
245
178
246
- This function is designed to be plugged to a `main` function from a Lean file
247
- that can be compiled. Example:
179
+ The arguments `args` are matched against the test suite keys. If a key starts
180
+ with one of the elements in `args`, then its respective test suite will be
181
+ marked to run.
248
182
249
- ```lean
250
- def main := lspecIO $
251
- test "four equals four" (4 = 4)
252
- ```
183
+ If the empty list is provided, all test suites will run.
253
184
-/
254
- def lspecIO (t : TestSeq) : IO UInt32 :=
255
- lspecM t
185
+ def lspecIO (map : HashMap String (List TestSeq)) (args : List String) : IO UInt32 := do
186
+ -- Compute the filtered map containing the test suites to run
187
+ let filteredMap :=
188
+ if args.isEmpty then map
189
+ else Id.run do
190
+ let mut acc := .empty
191
+ for arg in args do
192
+ for (key, tSeq) in map do
193
+ if key.startsWith arg then
194
+ acc := acc.insert key tSeq
195
+ pure acc
196
+
197
+ -- Accumulate error messages
198
+ let mut testsWithErrors : HashMap String (Array String) := .empty
199
+ for (key, tSeqs) in filteredMap do
200
+ IO.println key
201
+ for tSeq in tSeqs do
202
+ let (success, msg) := tSeq.run (indent := 2 )
203
+ if success then IO.println msg
204
+ else
205
+ IO.eprintln msg
206
+ if let some msgs := testsWithErrors[key]? then
207
+ testsWithErrors := testsWithErrors.insert key $ msgs.push msg
208
+ else
209
+ testsWithErrors := testsWithErrors.insert key #[msg]
210
+
211
+ -- Early return 0 when there are no errors
212
+ if testsWithErrors.isEmpty then return 0
213
+
214
+ -- Print error messages and then return 1
215
+ IO.eprintln "-------------------------------- Failing tests ---------------------------------"
216
+ for (key, msgs) in testsWithErrors do
217
+ IO.eprintln key
218
+ for msg in msgs do
219
+ IO.eprintln msg
220
+ return 1
256
221
257
222
/--
258
223
Runs a sequence of tests that are created from a `List α` and a function
259
224
`α → IO TestSeq`. Instead of creating all tests and running them consecutively,
260
225
this function alternates between test creation and test execution.
261
226
262
- It's rather useful for when the test creation process involves heavy
263
- computations in `IO` (e.g. when `f` reads data from files and processes it).
227
+ It's useful when the test creation process involves heavy computations in `IO`
228
+ (e.g. when `f` reads data from files and processes it).
264
229
-/
265
- def lspecEachIO (l : List α) (f : α → IO TestSeq) : IO UInt32 :=
266
- lspecEachM l f
267
-
268
- end IOTesting
269
-
270
- end MonadicTesting
230
+ def lspecEachIO (l : List α) (f : α → IO TestSeq) : IO UInt32 := do
231
+ let success ← l.foldlM (init := true ) fun acc a => do
232
+ match (← f a).run with
233
+ | ( true , msg) => IO.println msg; pure acc
234
+ | ( false , msg) => IO.eprintln msg; pure false
235
+ if success then return 0 else return 1
271
236
272
237
end LSpec
0 commit comments