Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow specifying seed so that generate can be repeatable. #280

Closed
johanatan opened this issue Oct 12, 2018 · 14 comments
Closed

allow specifying seed so that generate can be repeatable. #280

johanatan opened this issue Oct 12, 2018 · 14 comments

Comments

@johanatan
Copy link

johanatan commented Oct 12, 2018

I have a situation where the length of an array that I need to generate will be determined dynamically by the code under test. I was attempting to use the variant method, passing it an arbitrary Int which is an input to my test case to reliably and deterministically set the value passed to .proliferate. Upon closer inspection, I see that variant doesn't actually reset the seed by rather just mixes it with a pre-existing seed which is out of my control. Is there any way to either set the original seed or add a new method, perhaps called say resetSeed, which would allow this?

@CodaFi
Copy link
Member

CodaFi commented Oct 18, 2018

I'm hesitant to add a feature that allows users to control the seed beyond the initial seed in the checker arguments structure. It's an implementation detail of the underlying RNG that it exists at all.

@CodaFi
Copy link
Member

CodaFi commented Oct 18, 2018

I'll note that if you have a deterministic idea of how something should be constructed, write out the function that describes it.

@johanatan
Copy link
Author

johanatan commented Oct 18, 2018

Here it is:

extension Gen {
    /// Initializes the generator's internal Random Number Generator with a specified seed.
    public func withSeed<S : BinaryInteger>(_ seed : S) -> Gen<A> {
           ...
    }
}

If you do not want to implement this, can you provide a workaround or some other way to achieve the deterministic/reproducible behavior that I'd like to see for this case?

i.e., the case is:
"where the length of an array that I need to generate will be determined dynamically by the code under test". i.e., SwiftCheck generates some values, I run some of my code-under-test and verify that it is correct. Then I need to generate an array of values the length of which is functionally determined by the already verified correct output of the code-under-test.

@CodaFi
Copy link
Member

CodaFi commented Oct 19, 2018

I’m still confused by this setup. You have a property that generates a size to pass to a generator so it can build you an array of that size to feed to your program, right? Where along this chain do you capture the RNG’s seed to make it generate “deterministic” outputs?

@johanatan
Copy link
Author

As I mentioned previously, the seed would be passed as input to the property via an Arbitrary.Int. This way there would still be randomness (otherwise I could just fix the values via the repro CheckerArg).

@CodaFi
Copy link
Member

CodaFi commented Oct 19, 2018

But why use an arbitrary integer to generate a seed value at all? Could you show me a little pseudo-code of your setup, I think it'll help me comprehend this better.

@johanatan
Copy link
Author

So that it will be random yet repeatable if needed.

@johanatan
Copy link
Author

johanatan commented Oct 19, 2018

Here is some pseudo-code for it:

property("findCorrelations produces correct results.") <-
    forAll(CustomEntityArrayGen(upperBound: 100),
           CustomEntityArrayGen(upperBound: 75),
           Int.arbitrary) {

        let (array1, array2, checkIndicesSeed) = $0
        let res: Array<AnotherCustomEntity> = CodeUnderTest.f(array1, array2)

        // res is not easily predictable without generating a lot more data and doing
        // basically a reimplementation of `CodeUnderTest.f` in the tests.
        // res is also not able to be exhaustively checked.
        // thus, I'd like to generate a random sampling of indices into `res`
        // to perform swaps on and see if some function g(swapped_res) is less
        // than g(res). (f is supposed to produce a res that maximizes g(res)).

        let last = res.index(before: res.endIndex)
        let indexGen = Gen<Int>.choose((0, last))

        // note that i'm using `variant` in the following. I'd prefer to use `withSeed`
        // as that would make the code fully deterministic/repeatable.
        //

        let swapGen = indexGen.flatMap { (i) in indexGen.suchThat { $0 != i }.flatMap {
            Gen.pure((i, $0))
        }.variant(checkIndicesSeed)

        let numSwaps = h(res.count) // numSwaps is some function of res.count

        // generate numSwaps swaps with swapGen, do the swaps on res and
        // check g() values thereof.
}

@johanatan
Copy link
Author

@CodaFi Have you had enough time to digest that code yet?

@CodaFi
Copy link
Member

CodaFi commented Nov 17, 2018

@johanatan I'm sorry for neglecting this. I'm a student in university and it's exam season. I owe you a comprehensive reply, and I simply haven't found the time to do so.

@johanatan
Copy link
Author

Oh, gotcha. No worries. Just wanted to make sure it was still slated for attention at some point.

@CodaFi
Copy link
Member

CodaFi commented Nov 17, 2018

Let me try something quick and dirty: It seems like you have an unexpressed dependency between the collection generator and the indices generator:

property("findCorrelations produces correct results.") <-
    forAll(CustomEntityArrayGen(upperBound: 100),
           CustomEntityArrayGen(upperBound: 75),
           Int.arbitrary) {

        let (array1, array2, checkIndicesSeed) = $0
        let res: Array<AnotherCustomEntity> = CodeUnderTest.f(array1, array2)

        // res is not easily predictable without generating a lot more data and doing
        // basically a reimplementation of `CodeUnderTest.f` in the tests.
        // res is also not able to be exhaustively checked.
        // thus, I'd like to generate a random sampling of indices into `res`
        // to perform swaps on and see if some function g(swapped_res) is less
        // than g(res). (f is supposed to produce a res that maximizes g(res)).

        let indexGen = Gen.fromElements(of: res.indices)
        return forAll(Gen.zip(indexGen, indexGen)) { (i, j) in
          // Apply swap, check properties
        }
}

@johanatan
Copy link
Author

Ah, so forAll can be nested? Yea, that should work if so.

@CodaFi
Copy link
Member

CodaFi commented Aug 4, 2019

Going to close this due to inactivity. Please feel free to reopen it if there is an update.

@CodaFi CodaFi closed this as completed Aug 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants