Skip to content

ActionChain failure include never-executed actions since it uses wrong state object when creating transformations #426

Description

@vlsi

Testing Problem

The issue has initially been described here: #134 (comment)

State-depending transformation might use the state for, well, figuring out the available transformations.
However, after the transformation is applied, the set of "allowable" transformations might change, so jqwik should use only the valid state objects when deriving transformations.

Here's a test case that shows invalid result produced by chain.transformations().

I believe, the root cause here is that net.jqwik.engine.properties.state.ShrinkableChainIteration#transformer re-instantiates transformation shrinkable, and it uses the same state object for instantiating transformers and taking their names.

The exact seed does not matter much, however, the failure scenarios are different depending on the seed.

seed=42
// As you see the actual ".transformations()" produces a weird "add 10 to []" as if adding 7 was ignored.

[chain.transformations(), final state is []] 
expected: ["add 7 to []", "add 10 to [7]", "clear [7, 10]", "clear []"]
 but was: ["add 7 to []", "add 10 to []", "clear [7, 10]", "clear []"]

seed=43
// Here "adding elements after clear" is printed as "noop"
expected: ["clear []", "add 10 to []", "add 7 to [10]", "add 4 to [7, 10]"]
 but was: ["clear []", "noop", "noop", "noop"]
@Property(seed = "43")
void chainActionsAreProperlyDescribedEvenAfterChainExecution(@ForAll Random random) {
	List<String> actualOps = new ArrayList<>();
	ActionChainArbitrary<Set<Integer>> chains =
		ActionChain.<Set<Integer>>startWith(HashSet::new)
				   .addAction(
					   1,
					   (Action.Dependent<Set<Integer>>)
						   state ->
							   Arbitraries.just(
								   Transformer.<Set<Integer>>mutate("clear " + state, set -> {
									   actualOps.add("clear " + set);
									   set.clear();
								   })
							   )
				   )
				   .addAction(
					   2,
					   (Action.Dependent<Set<Integer>>)
						   state ->
							   Arbitraries.integers()
								   .between(1, 10)
								   .map(i -> {
											if (state.contains(i)) {
												return Transformer.noop();
											} else {
												return Transformer.mutate("add " + i + " to " + state, set -> {
													actualOps.add("add " + i + " to " + set);
													set.add(i);
												});
											}
										}
								   )
				   )
				   .withMaxTransformations(4);

	ActionChain<Set<Integer>> chain = TestingSupport.generateFirst(chains, random);
	Set<Integer> finalState = chain.run();
	assertThat(chain.transformations())
		.describedAs("chain.transformations(), final state is %s", finalState)
		.isEqualTo(actualOps);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions