Search code examples
javajqwik

How can I pass parameters to @Provide?


Is there a way to pass parameters to @Provide? I want something equivalent to the following:

@Property
void test(@ForAll("charSequence", 2, 5) CharSequence cs) {
    // test property on cs, which is an arbitrary CharSequence of length minimum 2 and maximum length 5
}

@Provide
Arbitrary<CharSequence> charSequence(int minLength, int maxLength) {
    Arbitrary<String> stringArbitrary = Arbitraries.strings().ofMinLength(minLength).ofMaxLength(maxLength).injectNull(0.01);
    Arbitrary<StringBuffer> stringBufferArbitrary = stringArbitrary.map
        (str -> null == str ? null : new StringBuffer(str));
    Arbitrary<StringBuilder> stringBuilderArbitrary = stringArbitrary.map
        (str -> null == str ? null : new StringBuilder(str));
    return Arbitraries.oneOf(stringArbitrary, stringBufferArbitrary, stringBuilderArbitrary);
}

I tried creating a custom annotation

public @interface Length {
    int min();
    int max();
}

and using it as void test(@ForAll("charSequence") @Length(min = 2, max = 5) CharSequence cs) as suggested in Provider Methods with Parameters, but TypeUsage doesn't seem to pick up the custom annotation, @Length. (Only @ForAll is picked.)


Solution

  • There's currently no mechanism in jqwik to directly pass parameters to provider methods. The annotation mechanism, however, should work as you suggested:

    @Property
    void test(@ForAll("charSequence") @Length(min = 2, max = 5)  CharSequence cs) {
        System.out.println(cs);
    }
    
    @Provide
    Arbitrary<CharSequence> charSequence(TypeUsage typeUsage) {
        Optional<Length> optionalLength = typeUsage.findAnnotation(Length.class);
        int minLength = optionalLength.map(l -> l.min()).orElse(1);
        int maxLength = optionalLength.map(l -> l.max()).orElse(255);
    
        Arbitrary<String> stringArbitrary = Arbitraries.strings().ofMinLength(minLength).ofMaxLength(maxLength);
        Arbitrary<StringBuffer> stringBufferArbitrary =
            stringArbitrary.map(str -> null == str ? null : new StringBuffer(str));
        Arbitrary<StringBuilder> stringBuilderArbitrary =
            stringArbitrary.map(str -> null == str ? null : new StringBuilder(str));
    
        return Arbitraries.oneOf(
                stringArbitrary, 
                stringBufferArbitrary, 
                stringBuilderArbitrary
        );
    }
    

    I guess you forgot to switch on runtime retention for @Length, otherwise the annotation will just be removed by the compiler:

    @Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, ElementType.TYPE_USE })
    @Retention(RetentionPolicy.RUNTIME)
    @interface Length {
        int min();
        int max();
    }
    

    The above code should generate output similar to (sequences of length between 2 and 5):

    ꭀ訂
    쯈쓁緈ﵬ
    堰⾞ᒽ뒡ՙ
    ᒽ뒡
    쳨任๔蔢
    涼嵒퓗
    ⫈ࣦ᩿쯧佺
    佺㺻
    䲠燷怟藤
    ...