spock icon indicating copy to clipboard operation
spock copied to clipboard

NodeInfo#getAnnotation returns instance of Cglib-generated that does not actually implement Annotation

Open renatoathaydes opened this issue 5 years ago • 11 comments

Issue description

org.spockframework.runtime.model.NodeInfo#getAnnotation returns an instance of a generated class which is not implementing Annotation, hence it doesn't actually work when it's used as a ClassCastException is thrown.

How to reproduce

In a Spock extension, call getAnnotation on a SpecInfo instance as shown:

Title t = node.getAnnotation( Title )

This error will happen (when there's no annotation on the spec, at least):

org.spockframework.runtime.model.SpecInfo$$EnhancerByCGLIB$$b2e871b8 cannot be cast to java.lang.annotation.Annotation

I would've expected null to be returned and there should be no error.

Java/JDK

openjdk version "1.8.0_232"

Groovy version

  • Groovy 3.0.1
  • Spock 2.0-M2-groovy-3.0

renatoathaydes avatar May 15 '20 16:05 renatoathaydes

Can you maybe provide a minimal reproducer project? And do you have the same problem when you use ByteBuddy instead of cglib?

Vampire avatar May 15 '20 16:05 Vampire

Ah!! The problem seems to be on a test I have!!! The SpecInfo instance was an actual mock! So, what's changed is the mocking done by Spock, not SpecInfo itself!

Sorry about the misunderstanding. Not sure if this is still a bug, but I would expect a Mock to return null on non-mocked methods?

renatoathaydes avatar May 15 '20 16:05 renatoathaydes


import org.spockframework.runtime.model.SpecInfo
import spock.lang.Specification
import spock.lang.Title

class MySpec extends Specification {

    def "my first test"() {
        given:
        def spec = Stub(SpecInfo)

        when:
        def t = spec.getAnnotation(Title)

        then:
        t == null
    }

}

This reproduces the problem. It works if I use Mock instead.

renatoathaydes avatar May 15 '20 16:05 renatoathaydes

Now I'm a bit confused. You say in your next to last comment you expect a Mock to return null. But in your last comment you show that you use a Stub and say with Mock it works as expected. Mocks are expected to return 0 or null on unmocked methods, Stubs are expectec to do their very best to return some empty or dummy response, so for example Optional.empty(), an empty list, an empty array or for non-collections another stub. This also is not new to 2.0 but is the same since I know Spock.

Vampire avatar May 15 '20 16:05 Vampire

Mock and Stub should both return null for this method in my opinion:

public <T extends Annotation> T getAnnotation(Class<T> clazz)

renatoathaydes avatar May 15 '20 16:05 renatoathaydes

This broke my tests after upgrading to 2.0, so I am pretty sure it's new.

renatoathaydes avatar May 15 '20 16:05 renatoathaydes

Ok, just tested with both Cglib and ByteBuddy using Spock version 1.3-groovy-2.5, and they both throw this Exception. Really not sure why my tests had previously been working with that Stub, I suppose it's because the methods being mocked and the methods being called have changed after the Spock upgrade, and somehow this came up...

Closing the ticket as the behaviour is indeed the same as before.

renatoathaydes avatar May 15 '20 17:05 renatoathaydes

You got me slightly wrong and should reopen the issue. Yes, Stub should return a dummy object there, namely another stub. But it should work properly and not result in a ClassCastException.

Vampire avatar May 15 '20 17:05 Vampire

I guess the code thinks this is Object return type, not seing the bound on the type parameter and thus producing a wrong stub as return value.

Vampire avatar May 15 '20 17:05 Vampire

Looks like org.spockframework.mock.runtime.StaticMockMethod#getReturnType doesn't handle generics with bounds correctly, it just returns java.lang.Object here. The code in the whole org.spockframework.gentyref.* package is from 2010/13 there is probably more information available in Java 8, at least the java.lang.reflect#getReturnType returns interface java.lang.annotation.Annotation. Also resolving generics based on method parameters is another task in itself.

leonard84 avatar Jul 03 '20 12:07 leonard84

I've updated the gentyref classes to the latest version and encountered the following problem https://github.com/coekie/gentyref/issues/5

leonard84 avatar Jul 03 '20 18:07 leonard84

I have opened the PR #1731 which fixes the specific issue with the getAnnotation() method not returning a stub instanceof Annotation.

But as @leonard84 already mentioned, this will not solve the "resolving generics based on method parameters" problem. So the method <T extends Annotation> T getAnnotation(Class<T>) with a call like stub.getAnnotation(Nullable) should return a new stub instanceof Nullable, but is only instanceof Annotation after the PR #1731.

AndreasTu avatar Jul 31 '23 15:07 AndreasTu