A possibility to define annotation processing order would be nice #646

Vampire opened this Issue Sep 16, 2016 · 0 comments


None yet

1 participant

Vampire commented Sep 16, 2016

I just learned about Spock and now I'm playing a bit with it.
What I currently try is to have two AnnotationDrivenExtensions that have an order constraint.
But I didn't see any possibility for this except for using respectively different interceptors which is only of limited value.

Here some more exemplary details:

If annotation A is applied to a specification or feature method, it adds an iteration interceptor that sets some String fields of the specification to some value.
If annotation B is applied, it does the same, but it needs to be given somehow the value that was set by annotation A.

The best I got so far is giving annotation B a value like

Class<Closure<? extends String>> value()

Then in the specification apply the annotation like

@B({ fieldSetByA })

and then in the interceptor use

bAnnotation.value().newInstance(it.instance, it.instance)()

to call that annotation and get the value.

But this only works if annotation B is applied after annotation A or rather if annotation B is evaluated by Spock after annotation A and thus the interceptor of A runs before the interceptor of B and the value thus is present and can be delivered by the closure given to the B annotation. If B is applied first, then the value will still be null.

The solution I currently use is to make A add an iteration interceptor and make B add a setup interceptor. This way the effect of A is guaranteed to happen before the effect of B. But if you add more dependency levels, you will at some point come to a point where you don't have enough different interceptor levels that you can leverage, especially if you need the bottom-most effect to be done before the setup methods are executed. I think the deepest level would be 4 by using iteration listener before proceed(), initialization listener before proceed(), initialization listener after proceed() and setup listener before proceed(). And this only works if you do not need cleanup after the iteration ran, otherwise you have to attach additionally a cleanup listener after proceed() and it reduces to a deepness level of 2 and makes the implementation highly complex and fragile and you also cannot configure this by case easily but have to implement it hard-coded.

What I would love to have is a possibility to define an annotation processing order that works throughout annotated elements. One concrete example:

@B({ fieldSetByA })
class MySpecification extends Specification {
    private String fieldSetByA
    private String fieldSetByB

    def setup() {
        assert fieldSetByA != null
        assert fieldSetByB != null
        assert fieldSetByB == "$fieldSetByA - I am B"

    def 'my feature'() {
        fieldSetByA != null
        fieldSetByB != null
        fieldSetByB == "$fieldSetByA - I am B"

So here A and B should both add iteration interceptors to my feature, but when the interceptor added by B is run, the interceptor of A must already be ran, so that the field is filled an can be accessed. This should work in any combination, both annotations added to the spec, both annotations added to the feature, A is added to the spec, B is added to the feature or B is added to the spec, A is added to the spec. I imagine something like @AnnotationProcessingOrder(A, B) that can be applied anywhere or at least at the spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment