jqwik-team / jqwik

Property-Based Testing on the JUnit Platform

Home Page:http://jqwik.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

support for heterogeneous arbitrary configurators in base

SimY4 opened this issue · comments

commented

Testing Problem

The current implementation of ArbitraryConfiguratorBase makes it super easy to configure a single arbitrary type using multiple annotation types but not vice versa where you might want to configure multiple arbitraries with a single annotation. i.e.

Suppose I have two arbitraries: Arbitrary<Foo> and Arbitrary<Bar> as I have a single annotation rule that can be applied to both. Unfortunately, due to erasure, there's no way to define two configure methods in base to satisfy its current contract:

class FooBarConfigurator extends ArbitraryConfiguratorBase {
  Arbitrary<Foo> configure(Arbitrary<Foo> arb, SomeAnnotation ann) { return arb; }

  Arbitrary<Bar> configure(Arbitrary<Bar> arb, SomeAnnotation ann) { return arb; } // no good since signatures collide
}

Suggested Solution

I would be keen to explore if jqwik could expand its search for potential config method signatures to include methods with random postfixes. i.e. say this would still be picked up by base as valid:

class FooBarConfigurator extends ArbitraryConfiguratorBase {
  Arbitrary<Foo> configureFoo(Arbitrary<Foo> arb, SomeAnnotation ann) { return arb; }

  Arbitrary<Bar> configureBar(Arbitrary<Bar> arb, SomeAnnotation ann) { return arb; }
}

The feature would probably be rather easy to implement. For judging if it's worthwhile I wonder why just registering two separate configurators doesn't work for you?

class FooConfigurator extends ArbitraryConfiguratorBase {
  Arbitrary<Foo> configure(Arbitrary<Foo> arb, SomeAnnotation ann) { return arb; }
}
class BarConfigurator extends ArbitraryConfiguratorBase {
  Arbitrary<Bar> configure(Arbitrary<Bar> arb, SomeAnnotation ann) { return arb; }
}

If the configurators share code use a common superclass or extract the shared functionality it into a helper.

I may be missing something in the way you apply configurators.

commented

Makes sence.

It's just the volume in my case. I have >20 that can be configured by a single annotation (like wrapper types on top of String) that basically configures a string inside).

Had a look at the implementation of ArbitraryConfiguratorBase. The enhancement is possible. Just not as straightforward as I initially thought due to some shortcuts I took.

It's on the list. Just not at the top of it.

@SimY4 The current snapshot 1.8.0-SNAPSHOT should support this feature. Maybe you can check if it fits your needs.

commented

Thank you @jlink . I only glanced through the changes on GitHub on my mobile, so could be totally wrong here. I think the code is still vulnerable to configuring the wrong thing if configuration annotation is applicable to different types. I.e. if I have @Odd applicable to Long and BigInteger it can incorrectly substitute them.

I thought it was covered, but checking shows that you're right.

Actually the code was already correct, but the presence of the Kotlin module could mess up annotation interpretation in Java classes and tests :-/

So, the following code - taken from jqwiks documentation examples - works:

public class OddConfigurator extends ArbitraryConfiguratorBase {

	public Arbitrary<Integer> configureInteger(Arbitrary<Integer> arbitrary, Odd odd) {
		return arbitrary.filter(number  -> Math.abs(number % 2) == 1);
	}

	public Arbitrary<BigInteger> configureBigInteger(Arbitrary<BigInteger> arbitrary, Odd odd) {
		return arbitrary.filter(number  -> {
            return number.mod(BigInteger.valueOf(2)).compareTo(BigInteger.ZERO) != 0;
        });
	}
}

class OddProperties {

	@Property @Report(Reporting.GENERATED)
	boolean oddIntegersOnly(@ForAll @Odd int aNumber) {
		return Math.abs(aNumber % 2) == 1;
	}

	@Property @Report(Reporting.GENERATED)
	boolean oddBigIntegersOnly(@ForAll @Odd BigInteger aNumber) {
		return Math.abs(aNumber.longValueExact() % 2) == 1;
	}
}

What I left out here is the code for the annotation and the registration of the configurator.

I redeployed 1.8.0-SNAPSHOT with a fixed Kotlin module.