Open pford19 opened 6 years ago
I have been working on something which relates to the question above "(What happens with in the withSeed case?)"
What I would like to do is to have a property which executes deterministically (from test run to test run) , yet for each iteration of a forAll , generates random values using Gen composition.
The simplest, self contained example I have is
import org.scalacheck.Prop.forAll
import org.scalacheck.Properties
class DeterministicPropertyBasedExample extends Properties("DeterministicPropertyBasedExample") {
propertyWithSeed("DeterministicPropertyBasedExample", Some("H84AeXXB037k6uRDc8xUqygjoDnJ8XMdAD9TpRY4iqP=")) =
forAll { (a: Int, p: Int => Boolean) =>
println(s"a=$a")
true
}
}
When I ran this, what I expected to see was different integer values being printed out. What I do see is
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
a=-2147483648
My question is , is there any way to modify this example or otherwise be able to generate different random values for each iteration of the forAll.
Thanks
Using scalacheck 1.14.0
Summary
A
Prop
generated by a function can succeed or fail depending on how it is "registered" in the source code. This seems to be due to the handling of the lazyProp
argument toPropertySpecifier.update
(and to the fact that properties and generators encapsulate complex mutable state).Example Scenario
Assume a
Prop
generating functionmakeProp
. Suitably defined (see Detailed Example section below for an explicit example) the following source text will succeed:whereas this source text will fail:
Discussion
Properties.property
method has this signatureThat is
is syntactic sugar for
The lazy
Prop
arg, and how it is handled, leads to surprising behavior. In particular when theProp
value in the source text is a function call, it appears that the function is called for eachProp.apply
yielding a fresh version of theProp
for eachapply
.In particular, if the property's generator has a fixed non-random first value, the the effect appears to be that the property test only sees that first value.
I did a superficial inspection of the source code. One thing that jumps out is that the
=>p: Prop
argument toupdate
is wrapped in aProp.delay
. Anddelay
's docs point out that the delayed property is reevaluated each time the wrapper property is evaluated. In contrast,Prop.lzy(=>Prop)
seems to have the desired property of only evaluating once. But this is getting way beyond my ken. I defer to the experts to explain.Detailed Example
In the above example,
makeProp
creates a new generator instance and a new property instance with each call. The generator's value stream is non-random, always(0, 1, 2, ..., n, -1, -1, -1, ...)
, in particular the first value is always0
.Any
Prop
generated bymakeProp
should succeed, because the property asserts the existence ofn
in the stream of generated values, andn
is always there, by design.For any
n > 0
, the existence check requires multiple attempts sincen > 0
is never the first value generated.What appears to be happening when the
makeProp(1)
source expression is passed directly toupdate
is that it is called for eachapply
. This effectively restarts the non-random generator and it repeatedly delivers its first value, which is0
. The existence test then fails after 501 discarded attempts.Admittedly non-random generators are corner cases (even pathological?), and this behavior will probably never manifest for properties based on well-behaved random generators. (What happens with in the
withSeed
case?).Is this is a BUG or FEATURE? If a feature it is a very subtle one.
Workaround
Compute props first, then pass to
update
.