-
-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
question: any way to imperatively summon a pre-configured arbitrary? #527
Comments
I’m not perfectly sure what you mean by „preconfigured“ but I assume you mean a type enriched by one or more annotation? If so, the answer is no. |
@jlink by pre-configured I mean it went through all applicable ArbitraryConfigurator instances available. My usecase is essentially creating an arbitrary that can pass down the configuration info from itself. Imagine a product type. A { b: B, c: C } if B and C can be configured with annotation D I'd like an A to also be configured with D and essentially pass down annotation info to B and C.
|
Configurators are not handed down to an arbitrary‘s children. If they were, one couldn’t be precise with where to apply them since some could be applicable to both parent and children. What if you want it only for the parent? Maybe you can show concrete use cases and why the current API is a bad fit. There may be a new feature hiding there. |
@jlink somewhat abstracted use-case of mine. Let's assume I have my own real number type: class Money(integer: long, fraction: long) extends Number And I have configurator I call small, that comes with annotation @Small that's applicable for longs. Essentially just generates only small long values. Now, I can say that my Money type can also be small if I can pass down the annotation from Money down to longs. I don't want to write a configurator for Money. If I can just summon the right instances for longs based on annotations present on the type. |
So the problem I described above holds:
I'm not sure how to mitigate that. One could introduce a meta annotation to specify if an annotation is handed down or not, but that introduces another bag of complications. Have you tried using domains for your use case? If your domain-specific long generator was defined within the domain then it would be picked up automatically in a |
They are not but they are easily accessible on a parent instance and once instantiated annotation doesn't hold any connections to the parent type so can be used as an annotation on an inner type. One way to solve my problem is by introducing a wrapper type:
Then, instead of creating configurations for small longs, i'd create an arbitrary for a This works. But wrappers are not composing really well. If you imagine a scenario where I have three ways of restricting a type:
you'd have to either use these restrictions in a specific order or provide arbitraries for exponentially growing combinations of these restrictors. Oppose to
where you just pipe them through the list of configurations.
tbh, I'm not sold on the idea of domains. because the use of domain immediately removes access for globally available arbitrary instances (you have to include them explicitly) it's easy to miss applicable instances and therefore skip valuable tests once you're in domains. |
A domain class can itself have annotation |
I still think domains are your best choice here, but you could also create an arbitrary provider yourself for types where handing down annotations is warranted: class MyProviderForA implements ArbitraryProvider {
@Override
public boolean canProvideFor(TypeUsage targetType) {
return targetType.isAssignableFrom(A.class);
}
private TypeUsage addAnnotations(List<Annotation> annotations, TypeUsage type) {
if (annotations.isEmpty()) {
return type;
}
Annotation annotation = annotations.get(0);
return addAnnotations(annotations.subList(1, annotations.size()), type.withAnnotation(annotation));
}
@Override
public Set<Arbitrary<?>> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
var annotations = targetType.getAnnotations()
Combinators.combine(
addAnnotations(annotations, Arbitraries.defaultFor(B.class)),
addAnnotations(annotations, Arbitraries.defaultFor(C.class))
).as(A::new)
}
} |
@jlink this is what I do today. With a minor tweak that my
That's why I was wondering if you can just have an api to take this responsibility into jqwik. But that's cool if it's not part of jqwik, I just decided to ask. |
I am open to considering an additional API just not to routinely handing down annotations. Do you have a suggestion how this API could look and fit into the existing ones? |
@jlink my original thought was (and TBH I still think it's the best of all options) to consider annotation instances added onto Arbitraries.defaultFor(TypeUsage.of(MyClass.class).withAnnotation(<annotation instance>)) (+) this API exists today and doesn't require new extensions Some other options (or rather state of the art in other places that I saw) that I personally think worse than this one:
Arbitraries.defaultFor(new TypeToken<@Small Long>() {}); (+) captures annotations on types
private final @Small long smallLong;
@Override
public Set<Arbitrary<?>> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
Arbitrary<Long> smallLong = Arbitraries.defaultFor(getClass().getField("smallLong"));
} (+) captures annotations on types |
This one already works, if you can grab an instance of an annotation from somewhere. I could introduce Drawing from your Junit Quickcheck example, what about a new way to create
The logic for doing that is probably already present somewhere in the engine's code, so it'd be just a matter of cleaning it up and presenting it as a public interface. |
hmmm, indeed... Though I think it messes with the cashes again in some nasty way, because: this works ok: configurer.configureB(Arbitraries.defaultFor(B.class), small) this doesn't work: Arbitraries.defaultFor(TypeUsage.of(B.class).withAnnotation(small)) In my case if fails with the configurator essentially filtering too many values (10000). Somehow the same doesn't occur if I summon first and then configure. |
Depending on how the small configurator works the order of application could play a role. E.g. first constraining the value range and then filtering might work, the other way around might lead to too many misses. |
Not sure if it will fit your use case, but I'll share how I'm doing it. It is however not annotation driven. What I'm doing is creating an interface like Then it becomes plug and play. For tests, a test class would then implement |
Quick question, maybe I'm missing something in the doc.
I can summon arbitraries in PBTs and @provide annotated methods with configuration annotations.
Is there a handy way to summon already pre-configured arbitrary instances in an imperative way? I was hoping this will do the trick:
Arbitraries.defaultFor(TypeUsage.of(MyClass.class).withAnnotation(Annotation))
but looks like it doesn't.The text was updated successfully, but these errors were encountered: