@@ -46,12 +46,13 @@ case class PropertyConfig(
4646 testLimit : SuccessCount
4747 , discardLimit : DiscardCount
4848 , shrinkLimit : ShrinkLimit
49+ , withExamples : WithExamples
4950 )
5051
5152object PropertyConfig {
5253
5354 def default : PropertyConfig =
54- PropertyConfig (SuccessCount (100 ), DiscardCount (100 ), ShrinkLimit (1000 ))
55+ PropertyConfig (SuccessCount (100 ), DiscardCount (100 ), ShrinkLimit (1000 ), WithExamples . NoExamples )
5556}
5657
5758case class PropertyT [A ](
@@ -132,38 +133,51 @@ object PropertyT {
132133trait PropertyTReporting {
133134
134135 @ annotation.tailrec
135- final def takeSmallest (n : ShrinkCount , slimit : ShrinkLimit , t : Tree [Option [(List [Log ], Option [Result ])]]): Status =
136- t.value match {
136+ final def takeSmallestG [A , B ](n : ShrinkCount , slimit : ShrinkLimit , t : Tree [A ])(p : A => Boolean )(e : (ShrinkCount , A ) => B ): B = {
137+ if (n.value < slimit.value && p(t.value)) {
138+ findMap(t.children.value)(m => if (p(m.value)) some(m) else Option .empty) match {
139+ case None =>
140+ e(n, t.value)
141+
142+ case Some (m) =>
143+ takeSmallestG(n.inc, slimit, m)(p)(e)
144+ }
145+ } else {
146+ e(n, t.value)
147+ }
148+ }
149+
150+ def takeSmallest (n : ShrinkCount , slimit : ShrinkLimit , t : Tree [Option [(Journal , Option [Result ])]]): Status =
151+ takeSmallestG(n, slimit, t) {
137152 case None =>
153+ false
154+
155+ case Some ((_, r)) =>
156+ r.forall(! _.success)
157+ } {
158+ case (_, None ) =>
138159 Status .gaveUp
139160
140- case Some ((w, r)) =>
141- if (r.forall(! _.success)) {
142- if (n.value >= slimit.value) {
143- Status .failed(n, w ++ r.map(_.logs).getOrElse(Nil ))
144- } else {
145- findMap(t.children.value)(m =>
146- m.value match {
147- case Some ((_, None )) =>
148- some(m)
149- case Some ((_, Some (r2))) =>
150- if (! r2.success)
151- some(m)
152- else
153- Option .empty
154- case None =>
155- Option .empty
156- }
157- ) match {
158- case Some (m) =>
159- takeSmallest(n.inc, slimit, m)
160- case None =>
161- Status .failed(n, w ++ r.map(_.logs).getOrElse(Nil ))
162- }
163- }
164- } else {
161+ case (n, Some ((j, r))) =>
162+ if (r.exists(_.success))
165163 Status .ok
166- }
164+ else
165+ Status .failed(n, j.logs ++ r.map(_.logs).getOrElse(Nil ))
166+ }
167+
168+ def takeSmallestExample (n : ShrinkCount , slimit : ShrinkLimit , name : LabelName , t : Tree [Option [(Journal , Option [Result ])]]): List [Log ] =
169+ takeSmallestG(n, slimit, t) {
170+ case None =>
171+ false
172+
173+ case Some ((j, r)) =>
174+ r.exists(_.success) && Coverage .covers(j.coverage, name)
175+ } {
176+ case (_, None ) =>
177+ Nil
178+
179+ case (_, Some ((j, _))) =>
180+ j.logs
167181 }
168182
169183 def report (config : PropertyConfig , size0 : Option [Size ], seed0 : Seed , p : PropertyT [Result ]): Report = {
@@ -172,17 +186,25 @@ trait PropertyTReporting {
172186 // Start the size at whatever remainder we have to ensure we run with "max" at least once
173187 val sizeInit = Size ((Size .max % Math .min(config.testLimit.value, Size .max)) + sizeInc.value)
174188 @ annotation.tailrec
175- def loop (successes : SuccessCount , discards : DiscardCount , size : Size , seed : Seed , coverage : Coverage [CoverCount ]): Report =
189+ def loop (successes : SuccessCount , discards : DiscardCount , size : Size , seed : Seed , coverage : Coverage [CoverCount ], examples : Examples ): Report =
176190 if (successes.value >= config.testLimit.value)
177191 // we've hit the test limit
178192 Coverage .split(coverage, successes) match {
179193 case (_, Nil ) =>
180- Report (successes, discards, coverage, OK )
194+ config.withExamples match {
195+ case WithExamples .WithExamples =>
196+ if (examples.examples.exists(_._2.isEmpty))
197+ Report (successes, discards, coverage, examples, Status .failed(ShrinkCount (0 ), List (" Insufficient examples." )))
198+ else
199+ Report (successes, discards, coverage, examples, OK )
200+ case WithExamples .NoExamples =>
201+ Report (successes, discards, coverage, examples, OK )
202+ }
181203 case _ =>
182- Report (successes, discards, coverage, Status .failed(ShrinkCount (0 ), List (" Insufficient coverage." )))
204+ Report (successes, discards, coverage, examples, Status .failed(ShrinkCount (0 ), List (" Insufficient coverage." )))
183205 }
184206 else if (discards.value >= config.discardLimit.value)
185- Report (successes, discards, coverage, GaveUp )
207+ Report (successes, discards, coverage, examples, GaveUp )
186208 else {
187209 val x =
188210 try {
@@ -191,23 +213,33 @@ trait PropertyTReporting {
191213 case e : Exception =>
192214 Property .error(e).run.run(size, seed)
193215 }
194- val t = x.map(_._2.map { case (l, r) => (l.logs, r) } )
216+ val t = x.map(_._2)
195217 x.value._2 match {
196218 case None =>
197- loop(successes, discards.inc, size.incBy(sizeInc), x.value._1, coverage)
198-
199- case Some ((_, None )) =>
200- Report (successes, discards, coverage, takeSmallest(ShrinkCount (0 ), config.shrinkLimit, t))
201-
202- case Some ((j, Some (r))) =>
203- if (! r.success){
204- Report (successes, discards, coverage, takeSmallest(ShrinkCount (0 ), config.shrinkLimit, t))
205- } else
206- loop(successes.inc, discards, size.incBy(sizeInc), x.value._1,
207- Coverage .union(Coverage .count(j.coverage), coverage)(_ + _))
219+ loop(successes, discards.inc, size.incBy(sizeInc), x.value._1, coverage, examples)
220+
221+ case Some ((j, r)) =>
222+ if (r.forall(! _.success)) {
223+ Report (successes, discards, coverage, examples, takeSmallest(ShrinkCount (0 ), config.shrinkLimit, t))
224+ } else {
225+ val coverage2 = Coverage .union(Coverage .count(j.coverage), coverage)(_ + _)
226+ val examples2 =
227+ config.withExamples match {
228+ case WithExamples .WithExamples =>
229+ Examples .addTo(examples, Coverage .labels(j.coverage)) { name =>
230+ if (Coverage .covers(j.coverage, name))
231+ takeSmallestExample(ShrinkCount (0 ), config.shrinkLimit, name, t)
232+ else
233+ Nil
234+ }
235+ case WithExamples .NoExamples =>
236+ examples
237+ }
238+ loop(successes.inc, discards, size.incBy(sizeInc), x.value._1, coverage2, examples2)
239+ }
208240 }
209241 }
210- loop(SuccessCount (0 ), DiscardCount (0 ), size0.getOrElse(sizeInit), seed0, Coverage .empty)
242+ loop(SuccessCount (0 ), DiscardCount (0 ), size0.getOrElse(sizeInit), seed0, Coverage .empty, Examples .empty )
211243 }
212244
213245 def recheck (config : PropertyConfig , size : Size , seed : Seed )(p : PropertyT [Result ]): Report =
@@ -247,6 +279,15 @@ case class DiscardCount(value: Int) {
247279 DiscardCount (value + 1 )
248280}
249281
282+ /** Whether the report should include an example for each label. */
283+ sealed trait WithExamples
284+
285+ object WithExamples {
286+
287+ case object WithExamples extends WithExamples
288+ case object NoExamples extends WithExamples
289+ }
290+
250291/**
251292 * The status of a property test run.
252293 *
@@ -270,4 +311,4 @@ object Status {
270311 OK
271312}
272313
273- case class Report (tests : SuccessCount , discards : DiscardCount , coverage : Coverage [CoverCount ], status : Status )
314+ case class Report (tests : SuccessCount , discards : DiscardCount , coverage : Coverage [CoverCount ], examples : Examples , status : Status )
0 commit comments