gopter icon indicating copy to clipboard operation
gopter copied to clipboard

Getting values outside of range when using gen.IntRange (probably during shrink)

Open rciorba opened this issue 4 years ago • 7 comments

Hello! Thanks for this great library and apologies in advance if this is me just missunderstanding it (go newbie).

I've got a stateful system, a bitarray and the state is a []bool. I'm trying to assert that the bitarray is equivalent to the state ([]bool).

I'm using 2 gen.IntRange:

  • for the initial state (to explore different array sizes)
  • for the command (set value at Index to be true/false, using IntRange to avoid out-of-bounds Index values)

Despite using IntRange to stay within the bounds of he SUT and state, I eventually see illegal values (I suspect during the shrink phase, as they only appear after the post condition fails), which causes the test to panic.

Is this intended behaviour for the gen.IntRange (to shrink outside the range)?

Another interesting detail: if I remove the IntRange from the initial state and only keep it in the command, the Index values for the command stay within range and I never see the panic.

See an example here: https://play.golang.org/p/xplrfKS_Nvm

Thanks!

rciorba avatar Apr 18 '21 09:04 rciorba

No, the IntRange should not shrink outside the given range. Can you give me a short sniplet about the different ways you construct the initial state, i.e. the working variant and the one that panics

untoldwind avatar Apr 26 '21 10:04 untoldwind

Hello, and thanks for following up!

I've simplified my test to try to reduce it to a condensed form. I think the a second IntRange for the InitialState was a distraction as I can reproduce the issue with a single IntRange for the command and a constant for the InitalState.

Here's the simplified example: https://gist.github.com/rciorba/79e17881b1739afe2ff504b7edbcfc73/revisions?diff=unified The initial revision is the working test (using a OneConstOf). The second revision uses the IntRange and starts to produce values outside the range once the post-condition is violated.

rciorba avatar May 01 '21 13:05 rciorba

Thanks for the detailed example. There is indeed something of with command shrinker, unluckily it is not so easy to fix.

So here the TLDR workaround: Make use of the command.PreCondition like this:

func (s setCommand) PreCondition(state commands.State) bool {
	return s.Index >= 6 && s.Index < state.(int)
}

Longer explanation: The shrinker relies on the Sieve in the gen.Result . Unluckily the commands package still uses the gen.FlatMap to create a generator for a sequence of commands from a single command generator (actually it is a sequence of FlatMaps). The gen.FlatMap has the known drawback of removing the Sieve because there is no easy way to (flat)map that. A better variant would be to use gen.DeriveGen at this place, which is unluckily not straight forward, so I'm currently playing around with several other potential solutions.

untoldwind avatar May 03 '21 07:05 untoldwind

Just after writing the comment above I came up with a new solution that required only a small change.

The current version in the master branch should fix the problem.

untoldwind avatar May 03 '21 08:05 untoldwind

@untoldwind Thanks for the fix! ~I tried it and my test no longer crashes.~

rciorba avatar Jun 06 '21 08:06 rciorba

Sorry, seems I tried it with the "intentional bug" commented out and that never triggers the shrink stage.

I can still reproduce the issue, using github.com/leanovate/gopter v0.2.10-0.20210503084252-f350002bbbe3

rciorba avatar Jun 06 '21 08:06 rciorba

I tested with this: https://gist.github.com/untoldwind/f78910c522f6efb81ac140891016a5da

According to the output the "Index" stays within its bounds during shrinking. Is this maybe a different issue?

untoldwind avatar Jun 07 '21 06:06 untoldwind