AssertionError when unit testing an annotation processor with compile-testing - java

Google's compile-testing is a great tool when it comes to unit testing annotation processors. Unfortunately I'm currently facing the following error:
java.lang.AssertionError:
An expected source declared one or more top-level types that were not present.
Expected top-level types: <[test.LocalStorage]>
Declared by expected file: <LocalStorageImpl.java>
The top-level types that were present are as follows:
- [test.LocalStorageImpl] in </SOURCE_OUTPUT/test/LocalStorageImpl.java>
My processor generates the implementation of an annotated interface. So the generated code has a dependency to the interface (class LocalStorageImpl implements LocalStorage).
When I try to verify the generated code with
JavaFileObject source = /* interface definition */
JavaFileObject expected_source = /* the generated implementation */
assertAbout(javaSource()).that(source)
.processedWith(new MyProcessor())
.compilesWithoutError()
.and()
.generatesSources(expectedSource);
...the generatesSource causes the error. As the error message states, it cannot resolve the interface definition.
So my question is how to compile or write the source file of the interface defined in source, so that it is recognized by generatesSources()?

Related

Gradle Cannot have abstract method

I'm developing a custom binary Gradle plugin, following the pattern shown here.
My extension, VersionInfoExtension is abstract with abstract methods, just like the BinaryRepositoryExtension in the documentation. My task is also abstract, with abstract methods for the parameters, like the LatestArtifactVersion in the documentation. When I test, I get the following error:
An exception occurred applying plugin request [id: 'com.example.version-info']
> Failed to apply plugin 'com.example.version-info'.
> Could not create an instance of type com.example.gradle.version.VersionInfoExtension.
> Could not generate a decorated class for type VersionInfoExtension.
> Cannot have abstract method VersionInfoExtension.jars().
What am I missing? Are there any good examples of making this work?
I'm using gradle 7.X and Kotlin.
The "Cannot have abstract method myPropertyName" error is reported when the method name is prefixed by "is":
abstract val isRegistered: Property<Boolean>
That was annoying to track down. The type doesn't seem to matter.
The solution was to remove "is" from the name.
The problem here seems to be the name of the abstract method.
All configurable methods must be bean-methods - this holds for both Extensions and Tasks.
So you should have used (assuming a java class):
abstract Property<String> getJars()
instead of
abstract Property<String> jars()
In addition to #Richard Sitze answer, in my case it was a custom setter function that was used in tests only (annotated with #TestOnly) was preventing generating the class.
I had declared serializable object as #Input
#get:Input
abstract val myPropertry : Property<MyObject>
#Test
fun setMyProperty(obj : MyObject){
//...
}
Somehow the name of the function interfered with generating the property setter along generation of the setter.

clojure gen-class with annotation that require enum parameter

I want to write a clojure lib and exposes generated classes, so that other java projects could use it.
I read and followed the gen-class doc, and everything works as my expect, except class annotation with enum parameters.
(ns common.exception.unauthorized
(:gen-class :name
^{org.springframework.web.bind.annotation.ResponseStatus
org.springframework.http.HttpStatus/UNAUTHORIZED} ; <- here
com.lalala.common.exception.Unauthorized
:extends java.lang.RuntimeException
:init init
:constructors {[String] [String]}
:main false
:prefix "unauthorized-"))
(defn unauthorized-init [^String message]
[[message] nil])
This exception class is generated without any error, and it also able be used as an Exception. However, this exception is intended to be used as a http response along with spring web. The spring framework read the annotation, and find that it is HttpStatus/UNAUTHORIZED then response 401. But the spring framework throws exception complaining that java.lang.EnumConstantNotPresentException: org.springframework.http.HttpStatus.
And I had a look at the generated class, it's something like this:
#ResponseStatus(HttpStatus.401)
public class Unauthorized extends RuntimeException {
private static final Var init__var = Var.internPrivate("common.exception.unauthorized", "unauthorized-init");
private static final Var getStackTrace__var = Var.internPrivate("common.exception.unauthorized", "unauthorized-getStackTrace");
// ...... ellipsis
}
As it shown, the HttpStatus/UNAUTHORIZED is compiled into HttpStatus.401 which is invalid.
I also tried with {:code org.springframework.http.HttpStatus/UNAUTHORIZED}, {:value org.springframework.http.HttpStatus/UNAUTHORIZED}, it can be compiled into #ResponseStatus(code/value = HttpStatus.401), but the enum value itself still in invalid form HttpStatus.401.
Am I use class annotation for gen-class in a wrong way? or just Clojure compiler just have this bug?
P.S. tried with Clojure 1.9, 1.10, 1.10.1
Finally, I use native Java code instead. I realized that writing classes in clojure with only ctor forwarded to super class is making trouble for myself.
I embedded java code in the project along with :java-source-paths configured in defproject, got my problem solved.

How to verify that an annotation is only used on certain classes?

Say I have an annotation #Annotate and a class A. How can I check whether #Annotate is used on class A and its subclasses only (probably at compile time)?
So, if we have:
/* Example 1 */
public class A {
}
public class B extends A {
}
public class C {
}
How do I check that class A and class B can be annotated by #Annotate but class C is not allowed (and might raise a compile error).
If we go with the decision that this would be checked at compile time:
/* Example 2 */
public class A {
}
#Annotate
public class B extends A {
}
Example 2 will not raise a compile time error because #Annotate is used on a subclass of A. Example 3, however, will raise an compile error because #Annotate is not used on a subclass of A.
/* Example 3 */
#Annotate
public class C {
}
However, this does not have to be checked at compile time in anyway. I just personally thought that it makes sense to do so.
You should write an annotation processor. An annotation processor can generate new Java files and, more relevantly for your case, issue compile-time warnings.
You will invoke it at compile time by running the java compiler like this: javac -processor MyProcessor MyFile.java. If regular javac or your annotation processor issues any warnings, then compilation fails.
Your annotation processor's logic is specific to your #Annotate annotation and is simple: permit the annotation only on classes that subclass A. There are various tutorials on the web about how to write an annetation processor, in addition to the Oracle documentation I linked above.
You can use my checker-framework.
Just Simply add annotation #CheckType on your annotation
#Target(TYPE)
#CheckType(value = A.class, type = Type.EXTEND_ALL)
public #interface OnlyOnA {
}
Now it can check if it is annotated on A or its subclass.
It will raise a compile error
eclipse snapshot
maven snapshot

Kotlin/KAPT Generated Kotlin class is not recognized as class member, but it does inside of methods

I have written an annotation processor that generates a builder class for my classes annotated with #DataBuilder
#Target(AnnotationTarget.CLASS)
#Retention(AnnotationRetention.SOURCE)
annotation class DataBuilder
My classes annotated with this annotation are located in the com.my.package.model package and the generated builder class is also located in the same package com.my.package.model but in the generated directory of course build/generated/source/kapt/debug/com/my/package/model/MyModelBuilder.kt, I can use these generated classes fine inside of my model classes(written in Kotlin)
BUT I can NOT use the generated MyModelBuilder Kotlin class inside of a java class as a class member
package com.my.package.home;
import com.my.package.model.MyModelBuilder;
public class Home {
MyModelBuilder builder; // <=== AS recognizes the class, but I'm having an compilation issue
}
Android Studio recognizes the class, but I’m having this compilation issue
com/my/package/home/Home.java:4: error: cannot find symbol
MyModelBuilder builder;
^
symbol: class MyModelBuilder
location: class Home
it’s weird because I can use this generated builder class only inside of methods, this code compiles fine:
package com.my.package.home;
import com.my.package.model.MyModelBuilder;
public class Home {
public void hello() {
MyModelBuilder builder;
}
}
could somebody here help me to understand this behavior and how to fix this? In advance, thanks!
UPDATE
I just created this repo with the necessary code to replicate the issue
https://github.com/epool/HelloKapt
The project works fine after cloning and running, to replicate the issue please un-comment this line https://github.com/epool/HelloKapt/blob/master/app/src/main/java/com/nearsoft/hellokapt/app/MainActivity.java#L13
Note: If I convert my MainActivity.java class to Kotlin(MainActivity.kt) the issues is NOT reproducible and works fine, but I don’t want to do so due to some project limitations so far
Kotlin Issue: https://youtrack.jetbrains.net/issue/KT-24591
Looking at your Github project, I notice that you don't declare a dependency on kotlin-stdlib-jdk7 in the app module. When I build the module, compiler emits the following warnings:
warning: unknown enum constant AnnotationTarget.CLASS
reason: class file for kotlin.annotation.AnnotationTarget not found
warning: unknown enum constant AnnotationRetention.SOURCE
reason: class file for kotlin.annotation.AnnotationRetention not found
warning: unknown enum constant AnnotationTarget.CLASS
reason: class file for kotlin.annotation.AnnotationTarget not found
Since kotlin-stdlib-jdk7 is declared as implementation in the annotations module, the app module doesn't see it as a transitive dependency, that might be the reason why compilation fails. To fix it, you should probably declare the correct dependency in the app module, or at least use apiElements scope for kotlin-stdlib-jdk7 in annotations.
The fact that the IDE doesn't notify you that compilation failed might be a tools bug, but there's definitely no underlying Kotlin compiler issue.

Java 9 default methods in interfaces: from which modules are they invoked?

suppose you have moduleA and moduleB. ModuleA defines an interface (for instance for a Service) and ModuleB has a concrete class that implements the interface (provides the service).
Now if the interface has a default method and you invoke it on the class in moduleB (from another module) is this invocation supposed to be performed inside moduleA or moduleB?
Apparently it is from moduleA ... what's the rationale?
Example: suppose you have a code that does this:
InputStream is = this.getClass().getResourceAsStream(fullPath);
if this code lies in the implementation of the service in moduleB the stream will be opened. But if the code lies in the default method in moduleA then when the service is invoked on moduleB you will need to have an "open" resource in moduleB (so it seems that the invocation thinks it is from "outside" moduleB).
would like to read about the reason for that.
thanks
editing my question with an example.
Suppose you have in moduleA this code:
public interface PropertiesProvider {
public default Properties get(String domain) {
Class clazz =this.getClass();
System.out.println(" CLASS " +clazz);
InputStream is = clazz.getResourceAsStream(domain) ;
if (is != null) {
Properties props = new Properties();
try {
props.load(is);
return props;
} catch (IOException e) {
//log
}
}
return null;
}
}
and in moduleB
public class PropertiesProviderImpl implements PropertiesProvider {}
if you invoke the service from ModuleA the call is traced to come from class PropertiesProviderImpl finds the resource but does not load it if it is not "opened"
if you copy the code into PropertiesProviderImpl the calls is traced to that same class finds the ressource and loads it even when it is not "opened"
So my question is: why the difference since the call comes from the same class?
(the difference being that in one case the method is kind-of inherited from the default method in the interface)
Look at the documentation of the getResourceAsStream If this class is in a named Module then this method will attempt to find the resource in the module.
In the first case your code (in moduleA) sees the Type but cannot see the class which implements your Type, because it's in the moduleB. In the second case your code can see the class which "implements" the Type.
Look at the reference bellow, the most important sentences are:
In a modular setting the invocation of Class::forName will continue to work so long as the package containing the provider class is known to the context class loader. The invocation of the provider class’s constructor via the reflective newInstance method, however, will not work: The provider might be loaded from the class path, in which case it will be in the unnamed module, or it might be in some named module, but in either case the framework itself is in the java.xml module. That module only depends upon, and therefore reads, the base module, and so a provider class in any other module will be not be accessible to the framework.
[...]
instead, revise the reflection API simply to assume that any code that reflects upon some type is in a module that can read the module that defines that type.
[Long answer]: reflective-readability
A framework is a facility that uses reflection to load, inspect, and instantiate other classes at run time [...]
Given a class discovered at run time, a framework must be able to access one of its constructors in order to instantiate it. As things stand, however, that will usually not be the case.
The platform’s streaming XML parser, e.g., loads and instantiates the implementation of the XMLInputFactory service named by the system property javax.xml.stream.XMLInputFactory, if defined, in preference to any provider discoverable via the ServiceLoader class. Ignoring exception handling and security checks the code reads, roughly:
String providerName
= System.getProperty("javax.xml.stream.XMLInputFactory");
if (providerName != null) {
Class providerClass = Class.forName(providerName, false,
Thread.getContextClassLoader());
Object ob = providerClass.newInstance();
return (XMLInputFactory)ob;
}
// Otherwise use ServiceLoader
...
In a modular setting the invocation of Class::forName will continue to work so long as the package containing the provider class is known to the context class loader. The invocation of the provider class’s constructor via the reflective newInstance method, however, will not work: The provider might be loaded from the class path, in which case it will be in the unnamed module, or it might be in some named module, but in either case the framework itself is in the java.xml module. That module only depends upon, and therefore reads, the base module, and so a provider class in any other module will be not be accessible to the framework.
To make the provider class accessible to the framework we need to make the provider’s module readable by the framework’s module. We could mandate that every framework explicitly add the necessary readability edge to the module graph at run time, as in an earlier version of this document, but experience showed that approach to be cumbersome and a barrier to migration.
We therefore, instead, revise the reflection API simply to assume that any code that reflects upon some type is in a module that can read the module that defines that type. This enables the above example, and other code like it, to work without change. This approach does not weaken strong encapsulation: A public type must still be in an exported package in order to be accessed from outside its defining module, whether from compiled code or via reflection.
since we didn't understand precisely the previous response we carried some additional tests
in each test the resource file is not "opened"
1)
the code invoking clazz.getResouceAsStream is in default method of interface defining the service. The class implementing the interface does not defines any method.
-> this.getClass() yields the implementing class , tests fails to find resource
2)
we added this code in the default method
Object obj = clazz.getConstructor().newInstance();
and yes it fails
3) we changed the code so PropertiesProvider is abstract class and PropertiesProviderImpl inherits from it
same behaviour.
So yes it means that the same code will behave differently if you inherit from it or just invoke it directly.
This is worrying: it means the inner logic of the language is going to lead to convoluted byzantine behaviours (the reason why we dumped C++).

Categories