jqwik: ClassCastException when using Combinators during shrinking

Testing Problem

ClassCastException when using Combinators and arbitraries producing elements of the type hierarchy.

java.lang.ClassCastException: class mypackage.MyTypeLike$Any cannot be cast to class mypackage.MyTypeLike (mypackage.MyTypeLike$Any and mypackage.MyTypeLike are in unnamed module of loader 'app')
	at net.jqwik.engine.properties.arbitraries.combinations.DefaultCombinator5.lambda$combineFunction$0(DefaultCombinator5.java:33)
	at net.jqwik.engine.properties.shrinking.CombinedShrinkable.createValue(CombinedShrinkable.java:25)
	at net.jqwik.engine.properties.shrinking.CombinedShrinkable.value(CombinedShrinkable.java:21)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1625)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
	at net.jqwik.engine.properties.shrinking.AbstractSampleShrinker.lambda$shrink$2(AbstractSampleShrinker.java:52)

Suggested Solution

Very brief analysis points to the lack of variance in functional combinator methods. i.e.:

<R> Arbitrary<R> flatAs(F5<T1, T2, T3, T4, T5, Arbitrary<@NotNull R>> flatCombinator)

<R> Arbitrary<R> as(Combinators.F5<T1, T2, T3, T4, T5, R> combinator)

Combinators.Combinator5<T1, T2, T3, T4, T5> filter(Combinators.F5<T1, T2, T3, T4, T5, Boolean> filter)

<R> Function<List<Object>, R> combineFunction(Combinators.F5<T1, T2, T3, T4, T5, R> combinator)

should probably be relaxed to:

<R> Arbitrary<R> flatAs(F5<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, Arbitrary<? extends R>> flatCombinator)

<R> Arbitrary<R> as(Combinators.F5<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? extends R> combinator)

Combinators.Combinator5<T1, T2, T3, T4, T5> filter(Combinators.F5<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, Boolean> filter)

<R> Function<List<Object>, R> combineFunction(Combinators.F5<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? extends R> combinator)

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Comments: 21 (4 by maintainers)

Most upvoted comments

@jlink I can twist it that it is. 😀 The last one came from ArbitraryConfiguratorBase’s contract. Basically, the contract says that I should define a class like this:

class Configurator extends ArbitraryConfiguratorBase {
  public Arbitrary<MyType> configure(Arbitrary<MyType> arb, MyAnnotation ann) { ... }
}

This looks like it a correct configurator. Except it isn’t because you also shouldn’t forget to overrideboolean acceptTargetType(TypeUsage) with:

MyType.class.isAssignableFrom(targetType.getRawType());

without it it can mess up all annotated arbitraries.

This is exactly what I discovered yesterday when looking into “your” other issue: https://github.com/jqwik-team/jqwik/issues/493

At the time of implementing ArbitraryConfiguratorBase I forgot (or was too lazy to) doing it correctly. Technical debt.

@SimY4 I couldn’t reproduce it on my own. Waiting for your input then.